├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
├── favicon.ico
├── iframe.html
├── index.html
├── main.2dcb9aa617a1e12e1b44.bundle.js
├── main.85f208740978715ff675.bundle.js
├── main.85f208740978715ff675.bundle.js.map
├── main.b72f8dff83b55fb47350.bundle.js
├── main.b72f8dff83b55fb47350.bundle.js.map
├── runtime~main.85f208740978715ff675.bundle.js
├── runtime~main.85f208740978715ff675.bundle.js.map
├── runtime~main.b72f8dff83b55fb47350.bundle.js
├── runtime~main.b72f8dff83b55fb47350.bundle.js.map
├── runtime~main.bf7f1bbd18bd2f1ec8df.bundle.js
├── sb_dll
│ ├── storybook_ui-manifest.json
│ ├── storybook_ui_dll.LICENCE
│ └── storybook_ui_dll.js
├── vendors~main.85f208740978715ff675.bundle.js
├── vendors~main.85f208740978715ff675.bundle.js.map
├── vendors~main.99ad87f9e3d4577e9b07.bundle.js
├── vendors~main.b72f8dff83b55fb47350.bundle.js
└── vendors~main.b72f8dff83b55fb47350.bundle.js.map
├── example
├── .babelrc
├── .storybook
│ ├── addons.js
│ ├── config.js
│ └── webpack.config.js
├── index.html
├── index.js
├── package.json
├── src
│ ├── App.js
│ ├── CSV
│ │ ├── Source.js
│ │ ├── Target.js
│ │ └── index.js
│ ├── ItemTypes.js
│ ├── MultipleTargets
│ │ ├── Source.js
│ │ ├── Sources.js
│ │ ├── Target.js
│ │ ├── Targets.js
│ │ └── index.js
│ ├── NestedTargets
│ │ ├── Source.js
│ │ ├── Sources.js
│ │ ├── Target.js
│ │ ├── Targets.js
│ │ └── index.js
│ ├── NormalDiv
│ │ ├── Source.js
│ │ ├── Target.js
│ │ └── index.js
│ └── WithDragPreview
│ │ ├── DragPreview.js
│ │ ├── Source.js
│ │ ├── Target.js
│ │ └── index.js
├── stories
│ └── index.stories.js
├── webpack.config.js
└── yarn.lock
├── package-lock.json
├── package.json
├── src
├── MouseBackend.js
└── index.js
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/
2 | webpack.config.js
3 | **/lib/
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "ecmaFeatures": {
6 | "jsx": true,
7 | "module": true
8 | },
9 | "sourceType": "module"
10 | },
11 | "env": {
12 | "browser": true,
13 | "node": true,
14 | "es6": true
15 | },
16 | "plugins": [
17 | "react"
18 | ],
19 | "rules": {
20 | // Ignore
21 | "new-cap": 0,
22 | "strict": 0,
23 |
24 | // Warnings
25 | "no-console": 1,
26 | "no-unused-vars": 1,
27 | "no-debugger": 1,
28 | "no-empty": 1,
29 | "no-invalid-regexp": 1,
30 | "no-unused-expressions": 1,
31 | "no-native-reassign": 1,
32 | "no-fallthrough": 1,
33 | "camelcase": 1,
34 | "indent": [1, 2, {"SwitchCase": 0}],
35 | "jsx-quotes": 1,
36 | "max-len": [1, 80, 4, {"ignoreUrls": true}],
37 | "max-depth": [1, 4],
38 |
39 | // React rules
40 | "react/jsx-no-undef": 1,
41 | "react/no-did-mount-set-state": 1,
42 | "react/no-did-update-set-state": 1,
43 | "react/react-in-jsx-scope": 1,
44 | "react/self-closing-comp": 1,
45 | "react/wrap-multilines": 1,
46 | "react/jsx-uses-react": 1,
47 | "react/jsx-uses-vars": 1,
48 |
49 | // Errors
50 | "no-spaced-func": 2,
51 | "semi-spacing": 2,
52 | "no-var": 2,
53 | "semi": [2, "never"],
54 | "no-use-before-define": 2,
55 | "eol-last": 2,
56 | "quotes": [2, "single"],
57 | "no-trailing-spaces": 2,
58 | "linebreak-style": [2, "unix"],
59 | "no-multiple-empty-lines": [2, {"max": 1}]
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/experiments/
2 | **/node_modules/
3 | **/dist/
4 | **/lib/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .eslintrc
2 | .eslintignore
3 | .gitignore
4 | .babelrc
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 DANG HAI AN
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-dnd-mouse-backend
2 |
3 | http://zyzo.github.io/react-dnd-mouse-backend/
4 |
5 | [](https://badge.fury.io/js/react-dnd-mouse-backend)
6 |
7 | Mouse Backend for React Drag and Drop library http://gaearon.github.io/react-dnd
8 |
9 | - [Usage](#Usage)
10 | - [Playground](#Playground)
11 | - [Development](#Development)
12 | - [Credits](#Credits)
13 |
14 | ### Usage
15 |
16 | ```js
17 | import { DragDropContext } from 'react-dnd'
18 | import MouseBackEnd from 'react-dnd-mouse-backend'
19 |
20 | const App = {...}
21 |
22 | const AppContainer = DragDropContext(MouseBackEnd)(App)
23 | ```
24 |
25 | ### Playground
26 |
27 | First, prepare the playground:
28 |
29 | ```sh
30 | cd example;
31 | yarn; yarn start
32 | ```
33 |
34 | Then head to `http://localhost:3030/` to start some fun drag and dropping.
35 |
36 | ### Development
37 |
38 | First, install the project locally:
39 |
40 | ```sh
41 | git clone git@github.com:zyzo/react-dnd-mouse-backend.git
42 | cd react-dnd-mouse-backend; npm install
43 | # (Optional) prepare example project
44 | cd example; npm install
45 | ```
46 |
47 | Then, link react-dnd-mouse-backend to example project (or your js project):
48 | ```sh
49 | # in ./react-dnd-mouse-backend
50 | npm link
51 | cd example; npm link react-dnd-mouse-backend
52 | ```
53 |
54 | Finally you can begin to make changes in `src` folder, and rebuild the lib:
55 |
56 | ```sh
57 | npm run build
58 | ```
59 |
60 |
61 | ### Credits
62 | Inspired by [HTML5 Backend](https://github.com/gaearon/react-dnd-html5-backend) & [Touch Backend](https://github.com/yahoo/react-dnd-touch-backend) to support only mouse events, which work much better in some cases, like svg.
63 |
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zyzo/react-dnd-mouse-backend/5729572d13b77fa2a0bb430750772d8792f5d9a2/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/iframe.html:
--------------------------------------------------------------------------------
1 |
StorybookNo Preview
Sorry, but you either have no stories or none are selected somehow.
- Please check the storybook config.
- Try reloading the page.
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | Storybook
--------------------------------------------------------------------------------
/docs/main.2dcb9aa617a1e12e1b44.bundle.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{0:function(n,o,p){p(321),p(402),n.exports=p(419)},402:function(n,o,p){"use strict";p.r(o);p(403)}},[[0,1,2]]]);
--------------------------------------------------------------------------------
/docs/main.85f208740978715ff675.bundle.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{277:function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__(2),__webpack_require__(5),__webpack_require__(7),__webpack_require__(274),__webpack_require__(16);var _style,react=__webpack_require__(0),react_default=__webpack_require__.n(react),update=__webpack_require__(35),update_default=__webpack_require__.n(update),ItemTypes=(__webpack_require__(11),__webpack_require__(30),__webpack_require__(14),__webpack_require__(9)),lib=__webpack_require__(6);function _objectSpread(target){for(var i=1;i\n {children}\n \n )\n }\n})\n\nexport default DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({\n connectDragSource: connect.dragSource(),\n isDragging: monitor.isDragging()\n}))(Source)\n"],"mappings":"AAIA","sourceRoot":""}
--------------------------------------------------------------------------------
/docs/main.b72f8dff83b55fb47350.bundle.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{277:function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__(2),__webpack_require__(5),__webpack_require__(7),__webpack_require__(274),__webpack_require__(16);var _style,react=__webpack_require__(0),react_default=__webpack_require__.n(react),update=__webpack_require__(35),update_default=__webpack_require__.n(update),ItemTypes=(__webpack_require__(11),__webpack_require__(30),__webpack_require__(14),__webpack_require__(9)),lib=__webpack_require__(6);function _objectSpread(target){for(var i=1;i\n {children}\n \n )\n }\n})\n\nexport default DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({\n connectDragSource: connect.dragSource(),\n isDragging: monitor.isDragging()\n}))(Source)\n"],"mappings":"AAIA","sourceRoot":""}
--------------------------------------------------------------------------------
/docs/runtime~main.85f208740978715ff675.bundle.js:
--------------------------------------------------------------------------------
1 | !function(modules){function webpackJsonpCallback(data){for(var moduleId,chunkId,chunkIds=data[0],moreModules=data[1],executeModules=data[2],i=0,resolves=[];i req(filename));
14 | }
15 |
16 | configure(loadStories, module);
17 |
--------------------------------------------------------------------------------
/example/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | module.exports = {
10 | plugins: [
11 | // your custom plugins
12 | ],
13 | module: {
14 | rules: [
15 | // add your custom rules.
16 | ],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React DnD example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 |
4 | import App from './src/App'
5 | render(
6 | ,
7 | document.getElementById('root')
8 | )
9 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dnd-mouse-backend-usage-example",
3 | "version": "0.1.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node node_modules/webpack-dev-server/bin/webpack-dev-server.js --hot --port 3030",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "storybook": "start-storybook -p 6006",
10 | "build-storybook": "build-storybook --output-dir ../docs"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/zyzo/react-dnd-mouse-back-end/example"
15 | },
16 | "keywords": [
17 | "react",
18 | "reactdnd",
19 | "mouse",
20 | "backend"
21 | ],
22 | "author": "zyzo",
23 | "license": "MIT",
24 | "devDependencies": {
25 | "@babel/core": "^7.3.3",
26 | "@babel/preset-env": "^7.3.1",
27 | "@babel/preset-react": "^7.0.0",
28 | "@storybook/addon-actions": "^4.1.11",
29 | "@storybook/addon-links": "^4.1.11",
30 | "@storybook/addons": "^4.1.11",
31 | "@storybook/react": "^4.1.11",
32 | "babel-eslint": "^6.0.2",
33 | "babel-loader": "^8.0.5",
34 | "eslint": "^2.3.0",
35 | "eslint-plugin-react": "^4.2.1",
36 | "react-hot-loader": "^1.3.0",
37 | "webpack": "^4.29.4",
38 | "webpack-dev-server": "^1.14.1",
39 | "webpack-hot-middleware": "^2.10.0"
40 | },
41 | "dependencies": {
42 | "@storybook/addon-options": "^4.1.11",
43 | "react": "^0.14.7",
44 | "react-dnd": "^2.1.2",
45 | "react-dnd-mouse-backend": "^0.1.0",
46 | "react-dom": "^0.14.7"
47 | },
48 | "private": true
49 | }
50 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { DragDropContext } from 'react-dnd'
3 | import MouseBackend from 'react-dnd-mouse-backend'
4 | import CSV from './CSV'
5 | import NormalDiv from './NormalDiv'
6 | import MultipleTargets from './MultipleTargets'
7 | import WithDragPreview from './WithDragPreview'
8 | import NestedTargets from './NestedTargets'
9 |
10 | const App = React.createClass({
11 | render() {
12 | return (
13 |
17 |
18 |
19 | )
20 | }
21 | })
22 |
23 | export default DragDropContext(MouseBackend)(App)
24 |
--------------------------------------------------------------------------------
/example/src/CSV/Source.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DragSource } from 'react-dnd'
4 |
5 | const rand0To255 = () => Math.floor(Math.random() * 256)
6 | const randomColor = () =>
7 | `rgb(${rand0To255()}, ${rand0To255()}, ${rand0To255()})`
8 |
9 | const boxSource = {
10 | beginDrag(props) {
11 | const { id, left, top } = props
12 | return { id, left, top }
13 | }
14 | }
15 |
16 | const Source = React.createClass({
17 | propTypes: {
18 | connectDragSource: PropTypes.func.isRequired,
19 | isDragging: PropTypes.bool.isRequired,
20 | id: PropTypes.any.isRequired,
21 | left: PropTypes.number.isRequired,
22 | top: PropTypes.number.isRequired,
23 | hideSourceOnDrag: PropTypes.bool.isRequired,
24 | children: PropTypes.node
25 | },
26 |
27 | render() {
28 | const {
29 | hideSourceOnDrag, left, top, connectDragSource, isDragging
30 | } = this.props
31 | if (isDragging && hideSourceOnDrag) {
32 | return null
33 | }
34 |
35 | return connectDragSource(
36 |
38 | )
39 | }
40 | })
41 |
42 | const connect = (connect, monitor) => (
43 | {
44 | connectDragSource: connect.dragSource(),
45 | isDragging: monitor.isDragging()
46 | }
47 | )
48 |
49 | export default DragSource(ItemTypes.CSV, boxSource, connect)(Source)
50 |
--------------------------------------------------------------------------------
/example/src/CSV/Target.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import update from 'react/lib/update'
3 | import ItemTypes from '../ItemTypes'
4 | import Source from './Source'
5 | import { DropTarget } from 'react-dnd'
6 |
7 | const styles = {
8 | width: 300,
9 | height: 300,
10 | border: '1px solid black',
11 | position: 'relative',
12 | flex: 1
13 | }
14 |
15 | const boxTarget = {
16 | drop(props, monitor, component) {
17 | const item = monitor.getItem()
18 | const delta = monitor.getDifferenceFromInitialOffset()
19 | const left = Math.round(item.left + delta.x)
20 | const top = Math.round(item.top + delta.y)
21 |
22 | component.moveBox(item.id, left, top)
23 | }
24 | }
25 |
26 | const Target = React.createClass({
27 | getInitialState() {
28 | return {
29 | circles: {
30 | 'a': { top: 20, left: 80 },
31 | 'b': { top: 180, left: 20 },
32 | 'c': { top: 130, left: 250 }
33 | }
34 | }
35 | },
36 |
37 | moveBox(id, left, top) {
38 | this.setState(update(this.state, {
39 | circles: {
40 | [id]: {
41 | $merge: {
42 | left: left,
43 | top: top
44 | }
45 | }
46 | }
47 | }))
48 | },
49 |
50 | render() {
51 | const { hideSourceOnDrag, connectDropTarget } = this.props
52 | const { circles } = this.state
53 |
54 | return connectDropTarget(
55 |
56 |
68 |
69 | )
70 | }
71 | })
72 |
73 | Target.propTypes = {
74 | hideSourceOnDrag: PropTypes.bool.isRequired,
75 | connectDropTarget: PropTypes.func.isRequired
76 | }
77 |
78 | export default DropTarget(ItemTypes.CSV, boxTarget, (connect, monitor) => ({
79 | connectDropTarget: connect.dropTarget(),
80 | isOver: monitor.isOver(),
81 | }))(Target)
82 |
--------------------------------------------------------------------------------
/example/src/CSV/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react'
3 |
4 | import Target from './Target'
5 |
6 | const DragAroundCSV = React.createClass({
7 | getInitialState() {
8 | return {
9 | hideSourceOnDrag: true
10 | }
11 | },
12 |
13 | handleHideSourceClick() {
14 | this.setState({
15 | hideSourceOnDrag: !this.state.hideSourceOnDrag
16 | })
17 | },
18 |
19 | render() {
20 | const { hideSourceOnDrag } = this.state
21 |
22 | return (
23 |
24 |
CSV Elements
25 |
26 |
27 |
33 |
34 |
35 | )
36 | }
37 | })
38 |
39 | export default DragAroundCSV
40 |
--------------------------------------------------------------------------------
/example/src/ItemTypes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | BOX: 'box',
3 | CSV: 'csv'
4 | }
5 |
--------------------------------------------------------------------------------
/example/src/MultipleTargets/Source.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DragSource } from 'react-dnd'
4 |
5 | const style = {
6 | position: 'absolute',
7 | border: '1px dashed gray',
8 | backgroundColor: 'white',
9 | padding: '4px',
10 | cursor: 'move',
11 | zIndex: 1,
12 | cursor: 'pointer',
13 | userSelect: 'none'
14 | }
15 |
16 | const boxSource = {
17 | beginDrag(props) {
18 | const { id, left, top } = props
19 | return { id, left, top }
20 | }
21 | }
22 |
23 | const Source = React.createClass({
24 | propTypes: {
25 | connectDragSource: PropTypes.func.isRequired,
26 | isDragging: PropTypes.bool.isRequired,
27 | id: PropTypes.any.isRequired,
28 | left: PropTypes.number.isRequired,
29 | top: PropTypes.number.isRequired,
30 | hideSourceOnDrag: PropTypes.bool.isRequired,
31 | children: PropTypes.node
32 | },
33 | render() {
34 | const {
35 | hideSourceOnDrag, left, top, connectDragSource, isDragging, children
36 | } = this.props
37 | if (isDragging && hideSourceOnDrag) {
38 | return null
39 | }
40 |
41 | return connectDragSource(
42 |
43 | {children}
44 |
45 | )
46 | }
47 | })
48 |
49 | export default DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
50 | connectDragSource: connect.dragSource(),
51 | isDragging: monitor.isDragging()
52 | }))(Source)
53 |
--------------------------------------------------------------------------------
/example/src/MultipleTargets/Sources.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Source from './Source'
3 |
4 | const Sources = React.createClass({
5 |
6 | render() {
7 | const { hideSourceOnDrag, boxes } = this.props
8 |
9 | return (
10 |
11 | {Object.keys(boxes).map(key => {
12 | const { left, top, title } = boxes[key]
13 | return (
14 |
19 | {title}
20 |
21 | )
22 | })}
23 |
24 | )
25 | }
26 | })
27 |
28 | export default Sources
29 |
--------------------------------------------------------------------------------
/example/src/MultipleTargets/Target.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DropTarget } from 'react-dnd'
4 |
5 | const styles = {
6 | position: 'absolute',
7 | width: 80,
8 | height: 80,
9 | border: '1px solid #ccc',
10 | flex: 1
11 | }
12 |
13 | const boxTarget = {
14 | drop(props, monitor, component) {
15 | const item = monitor.getItem()
16 | const delta = monitor.getDifferenceFromInitialOffset()
17 | const left = Math.round(item.left + delta.x)
18 | const top = Math.round(item.top + delta.y)
19 |
20 | component.moveBox(item.id, left, top)
21 | }
22 | }
23 |
24 | const Target = React.createClass({
25 | moveBox(id, left, top) {
26 | if (this.props.isOccupied()) {
27 | setTimeout(() => {
28 | alert('This box is occupied !')
29 | }, 0)
30 | return
31 | }
32 | this.props.moveBox(id, left, top, this.props.id)
33 | },
34 | render() {
35 | const { connectDropTarget, left, top } = this.props
36 |
37 | return connectDropTarget(
38 |
39 |
40 |
41 | )
42 | }
43 | })
44 |
45 | Target.propTypes = {
46 | connectDropTarget: PropTypes.func.isRequired
47 | }
48 |
49 | export default DropTarget(ItemTypes.BOX, boxTarget, (connect) => ({
50 | connectDropTarget: connect.dropTarget()
51 | }))(Target)
52 |
--------------------------------------------------------------------------------
/example/src/MultipleTargets/Targets.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Target from './Target'
3 |
4 | const Targets = React.createClass({
5 |
6 | render() {
7 | const { moveBox, isOccupied, } = this.props
8 | const targets = [{
9 | left: 10, top: 10, id: '1'
10 | }, {
11 | left: 10, top: 205, id: '2'
12 | }, {
13 | left: 205, top: 205, id: '3'
14 | }, {
15 | left: 205, top: 10, id: '4'
16 | }]
17 |
18 | return (
19 |
20 | {targets.map(target => {
21 | return (
22 | isOccupied(target.id)}
26 | />
27 | )
28 | })}
29 |
30 | )
31 | }
32 | })
33 |
34 | export default Targets
35 |
--------------------------------------------------------------------------------
/example/src/MultipleTargets/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react'
3 | import update from 'react/lib/update'
4 |
5 | import Sources from './Sources'
6 | import Targets from './Targets'
7 |
8 | const DragAroundNaive = React.createClass({
9 | getInitialState() {
10 | return {
11 | boxes: {
12 | 'a': { top: 50, left: 140, title: '0' },
13 | 'b': { top: 180, left: 20, title: '0' },
14 | 'c': { top: 90, left: 40, title: '0' },
15 | 'd': { top: 230, left: 160, title: '0' },
16 | 'e': { top: 140, left: 150, title: '0' }
17 | }
18 | }
19 | },
20 |
21 | moveBox(id, left, top, title) {
22 | this.setState(update(this.state, {
23 | boxes: {
24 | [id]: {
25 | $merge: {
26 | left: left,
27 | top: top,
28 | title: title
29 | }
30 | }
31 | }
32 | }))
33 | },
34 |
35 | isOccupied(id) {
36 | return Object.values(this.state.boxes).filter(box => box.title === id).length >= 1
37 | },
38 |
39 | render() {
40 | const { boxes } = this.state
41 |
42 | return (
43 |
46 |
Multiple Drop Targets
47 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 | })
56 |
57 | export default DragAroundNaive
58 |
--------------------------------------------------------------------------------
/example/src/NestedTargets/Source.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DragSource } from 'react-dnd'
4 |
5 | const style = {
6 | position: 'absolute',
7 | border: '1px dashed gray',
8 | backgroundColor: 'white',
9 | padding: '4px',
10 | cursor: 'move',
11 | zIndex: 1,
12 | cursor: 'pointer',
13 | userSelect: 'none'
14 | }
15 |
16 | const boxSource = {
17 | beginDrag(props) {
18 | const { id, left, top } = props
19 | return { id, left, top }
20 | }
21 | }
22 |
23 | const Source = React.createClass({
24 | propTypes: {
25 | connectDragSource: PropTypes.func.isRequired,
26 | isDragging: PropTypes.bool.isRequired,
27 | id: PropTypes.any.isRequired,
28 | left: PropTypes.number.isRequired,
29 | top: PropTypes.number.isRequired,
30 | hideSourceOnDrag: PropTypes.bool.isRequired,
31 | children: PropTypes.node
32 | },
33 | render() {
34 | const {
35 | hideSourceOnDrag, left, top, connectDragSource, isDragging, children
36 | } = this.props
37 | if (isDragging && hideSourceOnDrag) {
38 | return null
39 | }
40 |
41 | return connectDragSource(
42 |
43 | {children}
44 |
45 | )
46 | }
47 | })
48 |
49 | export default DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
50 | connectDragSource: connect.dragSource(),
51 | isDragging: monitor.isDragging()
52 | }))(Source)
53 |
--------------------------------------------------------------------------------
/example/src/NestedTargets/Sources.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Source from './Source'
3 |
4 | const Sources = React.createClass({
5 |
6 | render() {
7 | const { hideSourceOnDrag, boxes } = this.props
8 |
9 | return (
10 |
11 | {Object.keys(boxes).map(key => {
12 | const { left, top, title } = boxes[key]
13 | return (
14 |
19 | {title}
20 |
21 | )
22 | })}
23 |
24 | )
25 | }
26 | })
27 |
28 | export default Sources
29 |
--------------------------------------------------------------------------------
/example/src/NestedTargets/Target.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DropTarget } from 'react-dnd'
4 |
5 | const styles = {
6 | position: 'absolute',
7 | border: '1px solid #ccc',
8 | flex: 1
9 | }
10 |
11 | const boxTarget = {
12 | drop(props, monitor, component) {
13 | console.log(monitor.didDrop())
14 | if (monitor.didDrop()) return
15 | const item = monitor.getItem()
16 | const delta = monitor.getDifferenceFromInitialOffset()
17 | const left = Math.round(item.left + delta.x)
18 | const top = Math.round(item.top + delta.y)
19 | component.moveBox(item.id, left, top)
20 | }
21 | }
22 |
23 | const Target = React.createClass({
24 | moveBox(id, left, top) {
25 | this.props.moveBox(id, left, top, this.props.id)
26 | },
27 | render() {
28 | const { connectDropTarget, left, top, id, size } = this.props
29 |
30 | return connectDropTarget(
31 |
32 | {id}
33 |
34 | )
35 | }
36 | })
37 |
38 | Target.propTypes = {
39 | connectDropTarget: PropTypes.func.isRequired
40 | }
41 |
42 | export default DropTarget(ItemTypes.BOX, boxTarget, (connect) => ({
43 | connectDropTarget: connect.dropTarget()
44 | }))(Target)
45 |
--------------------------------------------------------------------------------
/example/src/NestedTargets/Targets.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Target from './Target'
3 |
4 | const Targets = React.createClass({
5 |
6 | render() {
7 | const { moveBox, isOccupied, } = this.props
8 | const targets = [{
9 | left: 60, top: 60, size: 180, id: '2'
10 | }, {
11 | left: 30, top: 30, size: 240, id: '1'
12 | }, {
13 | left: 90, top: 90, size: 120, id: '3'
14 | }, {
15 | left: 120, top: 120, size: 60, id: '4'
16 | }, {
17 | left: 170, top: 40, size: 80, id: '5'
18 | },]
19 |
20 | return (
21 |
22 | {targets.map(target => {
23 | return (
24 | isOccupied(target.id)}
28 | />
29 | )
30 | })}
31 |
32 | )
33 | }
34 | })
35 |
36 | export default Targets
37 |
--------------------------------------------------------------------------------
/example/src/NestedTargets/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react'
3 | import update from 'react/lib/update'
4 |
5 | import Sources from './Sources'
6 | import Targets from './Targets'
7 |
8 | const DragAroundNaive = React.createClass({
9 | getInitialState() {
10 | return {
11 | boxes: {
12 | 'a': { top: 50, left: 10, title: '0' },
13 | 'b': { top: 180, left: 10, title: '0' },
14 | 'c': { top: 90, left: 10, title: '0' },
15 | 'd': { top: 230, left: 10, title: '0' },
16 | 'e': { top: 140, left: 10, title: '0' }
17 | }
18 | }
19 | },
20 |
21 | moveBox(id, left, top, title) {
22 | this.setState(update(this.state, {
23 | boxes: {
24 | [id]: {
25 | $merge: {
26 | left: left,
27 | top: top,
28 | title: title
29 | }
30 | }
31 | }
32 | }))
33 | },
34 |
35 | isOccupied(id) {
36 | return Object.values(this.state.boxes).filter(box => box.title === id).length >= 1
37 | },
38 |
39 | render() {
40 | const { boxes } = this.state
41 |
42 | return (
43 |
46 |
Nested Drop Targets
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 | })
60 |
61 | export default DragAroundNaive
62 |
--------------------------------------------------------------------------------
/example/src/NormalDiv/Source.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DragSource } from 'react-dnd'
4 |
5 | const style = {
6 | position: 'absolute',
7 | border: '1px dashed gray',
8 | backgroundColor: 'white',
9 | padding: '0.5rem 1rem',
10 | cursor: 'move',
11 | }
12 |
13 | const boxSource = {
14 | beginDrag(props) {
15 | const { id, left, top } = props
16 | return { id, left, top }
17 | }
18 | }
19 |
20 | const Source = React.createClass({
21 | propTypes: {
22 | connectDragSource: PropTypes.func.isRequired,
23 | isDragging: PropTypes.bool.isRequired,
24 | id: PropTypes.any.isRequired,
25 | left: PropTypes.number.isRequired,
26 | top: PropTypes.number.isRequired,
27 | hideSourceOnDrag: PropTypes.bool.isRequired,
28 | children: PropTypes.node
29 | },
30 | render() {
31 | const {
32 | hideSourceOnDrag, left, top, connectDragSource, isDragging, children
33 | } = this.props
34 | if (isDragging && hideSourceOnDrag) {
35 | return null
36 | }
37 |
38 | return connectDragSource(
39 |
40 | {children}
41 |
42 | )
43 | }
44 | })
45 |
46 | export default DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
47 | connectDragSource: connect.dragSource(),
48 | isDragging: monitor.isDragging()
49 | }))(Source)
50 |
--------------------------------------------------------------------------------
/example/src/NormalDiv/Target.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import update from 'react/lib/update'
3 | import ItemTypes from '../ItemTypes'
4 | import Source from './Source'
5 | import { DropTarget } from 'react-dnd'
6 |
7 | const styles = {
8 | width: 300,
9 | height: 300,
10 | border: '1px solid black',
11 | position: 'relative',
12 | flex: 1
13 | }
14 |
15 | const boxTarget = {
16 | drop(props, monitor, component) {
17 | const item = monitor.getItem()
18 | const delta = monitor.getDifferenceFromInitialOffset()
19 | const left = Math.round(item.left + delta.x)
20 | const top = Math.round(item.top + delta.y)
21 |
22 | component.moveBox(item.id, left, top)
23 | }
24 | }
25 |
26 | const Target = React.createClass({
27 |
28 | getInitialState() {
29 | return {
30 | boxes: {
31 | 'a': { top: 20, left: 80, title: 'Drag me around' },
32 | 'b': { top: 180, left: 20, title: 'Drag me too' }
33 | }
34 | }
35 | },
36 |
37 | moveBox(id, left, top) {
38 | this.setState(update(this.state, {
39 | boxes: {
40 | [id]: {
41 | $merge: {
42 | left: left,
43 | top: top
44 | }
45 | }
46 | }
47 | }))
48 | },
49 |
50 | render() {
51 | const { hideSourceOnDrag, connectDropTarget } = this.props
52 | const { boxes} = this.state
53 |
54 | return connectDropTarget(
55 |
56 | {Object.keys(boxes).map(key => {
57 | const { left, top, title } = boxes[key]
58 | return (
59 |
64 | {title}
65 |
66 | )
67 | })}
68 |
69 | )
70 | }
71 | })
72 |
73 | Target.propTypes = {
74 | hideSourceOnDrag: PropTypes.bool.isRequired,
75 | connectDropTarget: PropTypes.func.isRequired
76 | }
77 |
78 | export default DropTarget(ItemTypes.BOX, boxTarget, (connect) => ({
79 | connectDropTarget: connect.dropTarget()
80 | }))(Target)
81 |
--------------------------------------------------------------------------------
/example/src/NormalDiv/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react'
3 | import Target from './Target'
4 |
5 | const DragAroundNaive = React.createClass({
6 | render() {
7 |
8 | return (
9 |
10 |
Normal Div
11 |
12 |
13 | )
14 | }
15 | })
16 |
17 | export default DragAroundNaive
18 |
--------------------------------------------------------------------------------
/example/src/WithDragPreview/DragPreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { DragLayer } from 'react-dnd'
3 |
4 | const defaultStyle = (item, currentOffset) => (
5 | {
6 | left: currentOffset.x,
7 | top: currentOffset.y,
8 | position: 'fixed'
9 | }
10 | )
11 |
12 | const DragPreview = React.createClass({
13 | render() {
14 | const {
15 | isDragging,
16 | currentOffset,
17 | item
18 | } = this.props
19 | return !isDragging || !currentOffset || !item.withDragPreview ?
20 | null
21 | :
22 |
26 |
27 | }
28 | })
29 |
30 | export default DragLayer(monitor => ({
31 | item: monitor.getItem(),
32 | itemType: monitor.getItemType(),
33 | currentOffset: monitor.getSourceClientOffset(),
34 | isDragging: monitor.isDragging()
35 | }))(DragPreview)
36 |
--------------------------------------------------------------------------------
/example/src/WithDragPreview/Source.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import ItemTypes from '../ItemTypes'
3 | import { DragSource } from 'react-dnd'
4 |
5 | const rand0To255 = () => Math.floor(Math.random() * 256)
6 | const randomColor = () =>
7 | `rgb(${rand0To255()}, ${rand0To255()}, ${rand0To255()})`
8 |
9 | const boxSource = {
10 | beginDrag(props, monitor, component) {
11 | const { id, left, top } = props
12 | return { id, left, top, color: component.state.color, withDragPreview: true }
13 | }
14 | }
15 |
16 | const Source = React.createClass({
17 | getInitialState() {
18 | return {
19 | color: randomColor()
20 | }
21 | },
22 | propTypes: {
23 | connectDragSource: PropTypes.func.isRequired,
24 | isDragging: PropTypes.bool.isRequired,
25 | id: PropTypes.any.isRequired,
26 | left: PropTypes.number.isRequired,
27 | top: PropTypes.number.isRequired,
28 | hideSourceOnDrag: PropTypes.bool.isRequired,
29 | children: PropTypes.node
30 | },
31 |
32 | render() {
33 | const {
34 | hideSourceOnDrag, left, top, connectDragSource, isDragging
35 | } = this.props
36 | if (isDragging && hideSourceOnDrag) {
37 | return null
38 | }
39 |
40 | return connectDragSource(
41 |
43 | )
44 | }
45 | })
46 |
47 | const connect = (connect, monitor) => (
48 | {
49 | connectDragSource: connect.dragSource(),
50 | isDragging: monitor.isDragging()
51 | }
52 | )
53 |
54 | export default DragSource(ItemTypes.CSV, boxSource, connect)(Source)
55 |
--------------------------------------------------------------------------------
/example/src/WithDragPreview/Target.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import update from 'react/lib/update'
3 | import ItemTypes from '../ItemTypes'
4 | import Source from './Source'
5 | import { DropTarget } from 'react-dnd'
6 |
7 | const styles = {
8 | width: 300,
9 | height: 300,
10 | border: '1px solid black',
11 | position: 'relative',
12 | flex: 1
13 | }
14 |
15 | const boxTarget = {
16 | drop(props, monitor, component) {
17 | const item = monitor.getItem()
18 | const delta = monitor.getDifferenceFromInitialOffset()
19 | const left = Math.round(item.left + delta.x)
20 | const top = Math.round(item.top + delta.y)
21 |
22 | component.moveBox(item.id, left, top)
23 | }
24 | }
25 |
26 | const Target = React.createClass({
27 | getInitialState() {
28 | return {
29 | circles: {
30 | 'a': { top: 20, left: 80 },
31 | 'b': { top: 180, left: 20 },
32 | 'c': { top: 130, left: 250 }
33 | }
34 | }
35 | },
36 |
37 | moveBox(id, left, top) {
38 | this.setState(update(this.state, {
39 | circles: {
40 | [id]: {
41 | $merge: {
42 | left: left,
43 | top: top
44 | }
45 | }
46 | }
47 | }))
48 | },
49 |
50 | render() {
51 | const { hideSourceOnDrag, connectDropTarget } = this.props
52 | const { circles } = this.state
53 |
54 | return connectDropTarget(
55 |
56 |
68 |
69 | )
70 | }
71 | })
72 |
73 | Target.propTypes = {
74 | hideSourceOnDrag: PropTypes.bool.isRequired,
75 | connectDropTarget: PropTypes.func.isRequired
76 | }
77 |
78 | export default DropTarget(ItemTypes.CSV, boxTarget, (connect, monitor) => ({
79 | connectDropTarget: connect.dropTarget(),
80 | isOver: monitor.isOver(),
81 | }))(Target)
82 |
--------------------------------------------------------------------------------
/example/src/WithDragPreview/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react'
3 |
4 | import DragPreview from './DragPreview'
5 | import Target from './Target'
6 |
7 | const DragAroundCSV = React.createClass({
8 |
9 | render() {
10 |
11 | return (
12 |
13 |
Custom Drag Preview
14 |
15 |
16 |
17 | )
18 | }
19 | })
20 |
21 | export default DragAroundCSV
22 |
--------------------------------------------------------------------------------
/example/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { DragDropContext } from 'react-dnd'
4 | import MouseBackend from 'react-dnd-mouse-backend'
5 | import { storiesOf } from '@storybook/react'
6 | import CSV from '../src/CSV'
7 | import NormalDiv from '../src/NormalDiv'
8 | import MultipleTargets from '../src/MultipleTargets'
9 | import WithDragPreview from '../src/WithDragPreview'
10 | import NestedTargets from '../src/NestedTargets'
11 |
12 | const withDnDContext = (Component) => {
13 | return DragDropContext(MouseBackend)(Component)
14 | }
15 |
16 | const CSVStory = withDnDContext(CSV)
17 | const NormalDivStory = withDnDContext(NormalDiv)
18 | const MultipleTargetsStory = withDnDContext(MultipleTargets)
19 | const WithDragPreviewStory = withDnDContext(WithDragPreview)
20 | const NestedTargetsStory = withDnDContext(NestedTargets)
21 |
22 | storiesOf('Basic', module)
23 | .add('CSV', () => )
24 | .add('Normal Div', () => )
25 |
26 | storiesOf('With Drag Preview', module)
27 | .add('Custom Drag Preview', () => )
28 |
29 | storiesOf('Advanced', module)
30 | .add('Multiple Targets', () => )
31 | .add('Nested / Overlapping Targets', () => )
32 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | devtool: 'source-map',
6 | entry: [
7 | './index'
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/static/'
13 | },
14 | plugins: [
15 | new webpack.optimize.OccurenceOrderPlugin(),
16 | new webpack.HotModuleReplacementPlugin(),
17 | new webpack.NoErrorsPlugin()
18 | ],
19 | module: {
20 | loaders: [
21 | {
22 | test: /\.jsx?$/,
23 | loaders: [ 'react-hot', 'babel?presets[]=es2015&presets[]=react' ],
24 | exclude: /node_modules/,
25 | include: __dirname
26 | }
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dnd-mouse-backend",
3 | "version": "1.0.0-rc.2",
4 | "description": "A lightweight attempt for solving the whole HTML5 DnD - svg tags compability issue",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "lint": "node node_modules/eslint/bin/eslint.js .",
8 | "clean": "rimraf lib",
9 | "build:lib": "node node_modules/babel-cli/bin/babel.js --presets es2015 src --out-dir lib",
10 | "build:umd": "node node_modules/webpack/bin/webpack.js",
11 | "build": "npm run build:lib && npm run build:umd",
12 | "prepublish": "npm run build",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/zyzo/react-dnd-mouse-backend.git"
18 | },
19 | "keywords": [
20 | "react",
21 | "mouse",
22 | "dragndrop",
23 | "svg"
24 | ],
25 | "author": "danghaian168@gmail.com",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/zyzo/react-dnd-mouse-backend/issues"
29 | },
30 | "homepage": "https://github.com/zyzo/react-dnd-mouse-backend#readme",
31 | "devDependencies": {
32 | "babel": "^6.5.2",
33 | "babel-cli": "^6.6.5",
34 | "babel-eslint": "^5.0.0",
35 | "babel-loader": "^6.2.4",
36 | "babel-preset-es2015": "^6.6.0",
37 | "eslint": "~2.2.0",
38 | "eslint-plugin-react": "^4.2.0",
39 | "react": "^0.14.7",
40 | "react-redux": "^4.4.1",
41 | "redux": "^3.3.1",
42 | "webpack": "^1.12.14"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/MouseBackend.js:
--------------------------------------------------------------------------------
1 |
2 | function getEventClientOffset (e) {
3 | return {
4 | x: e.clientX,
5 | y: e.clientY
6 | }
7 | }
8 |
9 | const ELEMENT_NODE = 1
10 | function getNodeClientOffset (node) {
11 | const el = node.nodeType === ELEMENT_NODE
12 | ? node
13 | : node.parentElement
14 |
15 | if (!el) {
16 | return null
17 | }
18 |
19 | const { top, left } = el.getBoundingClientRect()
20 | return { x: left, y: top }
21 | }
22 |
23 | function isRightClick (e) {
24 | if ('which' in e) {
25 | return e.which === 3
26 | } else if ('button' in e) {
27 | return e.button === 2
28 | }
29 | return false
30 | }
31 |
32 | export default class MouseBackend {
33 | constructor(manager) {
34 | this.actions = manager.getActions()
35 | this.monitor = manager.getMonitor()
36 | this.registry = manager.getRegistry()
37 |
38 | this.sourceNodes = {}
39 | this.sourceNodesOptions = {}
40 | this.sourcePreviewNodes = {}
41 | this.sourcePreviewNodesOptions = {}
42 | this.targetNodes = {}
43 | this.targetNodeOptions = {}
44 | this.mouseClientOffset = {}
45 |
46 | this.getSourceClientOffset = this.getSourceClientOffset.bind(this)
47 |
48 | this.handleWindowMoveStart =
49 | this.handleWindowMoveStart.bind(this)
50 | this.handleWindowMoveStartCapture =
51 | this.handleWindowMoveStartCapture.bind(this)
52 | this.handleWindowMoveCapture =
53 | this.handleWindowMoveCapture.bind(this)
54 | this.handleWindowMoveEndCapture =
55 | this.handleWindowMoveEndCapture.bind(this)
56 | }
57 |
58 | setup() {
59 | if (typeof window === 'undefined') {
60 | return
61 | }
62 |
63 | if (this.constructor.isSetUp) {
64 | throw new Error('Cannot have two DnD Mouse backend at the same time')
65 | }
66 |
67 | this.constructor.isSetUp = true
68 | window.addEventListener('mousedown',
69 | this.handleWindowMoveStartCapture, true)
70 | window.addEventListener('mousedown',
71 | this.handleWindowMoveStart)
72 | window.addEventListener('mousemove',
73 | this.handleWindowMoveCapture, true)
74 | window.addEventListener('mouseup',
75 | this.handleWindowMoveEndCapture, true)
76 | }
77 |
78 | getSourceClientOffset (sourceId) {
79 | return getNodeClientOffset(this.sourceNodes[sourceId])
80 | }
81 |
82 | teardown() {
83 | if (typeof window === 'undefined') {
84 | return
85 | }
86 |
87 | this.constructor.isSetUp = false
88 |
89 | this.mouseClientOffset = {}
90 | window.removeEventListener(
91 | 'mousedown', this.handleWindowMoveStartCapture, true)
92 | window.removeEventListener(
93 | 'mousedown', this.handleWindowMoveStart)
94 | window.removeEventListener(
95 | 'mousemove', this.handleWindowMoveCapture, true)
96 | window.removeEventListener(
97 | 'mouseup', this.handleWindowMoveEndCapture, true)
98 | }
99 |
100 | connectDragSource(sourceId, node) {
101 | this.sourceNodes[sourceId] = node
102 |
103 | const handleMoveStart =
104 | this.handleMoveStart.bind(this, sourceId)
105 | node.addEventListener('mousedown',
106 | handleMoveStart)
107 |
108 | return () => {
109 | delete this.sourceNodes[sourceId]
110 | node.removeEventListener('mousedown', handleMoveStart)
111 | }
112 | }
113 |
114 | connectDragPreview(sourceId, node, options) {
115 | this.sourcePreviewNodesOptions[sourceId] = options
116 | this.sourcePreviewNodes[sourceId] = node
117 |
118 | return () => {
119 | delete this.sourcePreviewNodes[sourceId]
120 | delete this.sourcePreviewNodesOptions[sourceId]
121 | }
122 | }
123 |
124 | connectDropTarget(targetId, node) {
125 | this.targetNodes[targetId] = node
126 |
127 | return () => {
128 | delete this.targetNodes[targetId]
129 | }
130 | }
131 |
132 | handleWindowMoveStartCapture() {
133 | this.moveStartSourceIds = []
134 | }
135 |
136 | handleMoveStart (sourceId, e) {
137 | // Ignore right mouse button.
138 | if (isRightClick(e)) return
139 | this.moveStartSourceIds.unshift(sourceId)
140 | }
141 |
142 | handleWindowMoveStart(e) {
143 | const clientOffset = getEventClientOffset(e)
144 | if (clientOffset) {
145 | this.mouseClientOffset = clientOffset
146 | }
147 | }
148 |
149 | handleWindowMoveCapture (e) {
150 | const { moveStartSourceIds } = this
151 | const clientOffset = getEventClientOffset(e)
152 | if (!clientOffset)
153 | return
154 | if (!this.monitor.isDragging()
155 | && this.mouseClientOffset.hasOwnProperty('x') && moveStartSourceIds &&
156 | (
157 | this.mouseClientOffset.x !== clientOffset.x ||
158 | this.mouseClientOffset.y !== clientOffset.y
159 | )) {
160 | this.moveStartSourceIds = null
161 | this.actions.beginDrag(moveStartSourceIds, {
162 | clientOffset: this.mouseClientOffset,
163 | getSourceClientOffset: this.getSourceClientOffset,
164 | publishSource: false
165 | })
166 | }
167 | if (!this.monitor.isDragging()) {
168 | return
169 | }
170 |
171 | const sourceNode = this.sourceNodes[this.monitor.getSourceId()]
172 | this.installSourceNodeRemovalObserver(sourceNode)
173 |
174 | this.actions.publishDragSource()
175 |
176 | e.preventDefault()
177 |
178 | const matchingTargetIds = Object.keys(this.targetNodes)
179 | .filter((targetId) =>
180 | {
181 | const boundingRect =
182 | this.targetNodes[targetId].getBoundingClientRect()
183 | return clientOffset.x >= boundingRect.left &&
184 | clientOffset.x <= boundingRect.right &&
185 | clientOffset.y >= boundingRect.top &&
186 | clientOffset.y <= boundingRect.bottom
187 | })
188 |
189 | this.actions.hover(matchingTargetIds, {
190 | clientOffset
191 | })
192 | }
193 |
194 | handleWindowMoveEndCapture (e) {
195 | if (!this.monitor.isDragging() || this.monitor.didDrop()) {
196 | this.moveStartSourceIds = null
197 | return
198 | }
199 |
200 | e.preventDefault()
201 |
202 | this.mouseClientOffset = {}
203 |
204 | this.uninstallSourceNodeRemovalObserver()
205 | this.actions.drop()
206 | this.actions.endDrag()
207 | }
208 |
209 | installSourceNodeRemovalObserver (node) {
210 | this.uninstallSourceNodeRemovalObserver()
211 |
212 | this.draggedSourceNode = node
213 | this.draggedSourceNodeRemovalObserver = new window.MutationObserver(() => {
214 | if (!node.parentElement) {
215 | this.resurrectSourceNode()
216 | this.uninstallSourceNodeRemovalObserver()
217 | }
218 | })
219 |
220 | if (!node || !node.parentElement) {
221 | return
222 | }
223 |
224 | this.draggedSourceNodeRemovalObserver.observe(
225 | node.parentElement,
226 | { childList: true }
227 | )
228 | }
229 |
230 | resurrectSourceNode () {
231 | this.draggedSourceNode.style.display = 'none'
232 | this.draggedSourceNode.removeAttribute('data-reactid')
233 | document.body.appendChild(this.draggedSourceNode)
234 | }
235 |
236 | uninstallSourceNodeRemovalObserver () {
237 | if (this.draggedSourceNodeRemovalObserver) {
238 | this.draggedSourceNodeRemovalObserver.disconnect()
239 | }
240 |
241 | this.draggedSourceNodeRemovalObserver = null
242 | this.draggedSourceNode = null
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import MouseBackend from './MouseBackend'
2 |
3 | const createMouseBackend = (manager) => new MouseBackend(manager)
4 |
5 | export default createMouseBackend
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 |
3 | module.exports = {
4 | entry: './lib/index',
5 | devtool: 'cheap-source-map',
6 | module: {
7 | loaders: [
8 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
9 | ]
10 | },
11 | output: {
12 | filename: 'dist/ReactDnDMouseMoveBackend.min.js',
13 | libraryTarget: 'umd',
14 | library: 'ReactDnDMouseMoveBackend'
15 | },
16 | plugins: [
17 | new webpack.optimize.OccurenceOrderPlugin(),
18 | new webpack.DefinePlugin({
19 | 'process.env': {
20 | 'NODE_ENV': JSON.stringify('production')
21 | }
22 | }),
23 | new webpack.optimize.UglifyJsPlugin({
24 | compressor: {
25 | warnings: false
26 | }
27 | })
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------