├── .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 | [![npm version](https://badge.fury.io/js/react-dnd-mouse-backend.svg)](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 | Storybook

No Preview

Sorry, but you either have no stories or none are selected somehow.

59 |     
60 |   
-------------------------------------------------------------------------------- /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 | 57 | {Object.keys(circles).map(key => { 58 | const { left, top } = circles[key] 59 | return ( 60 | 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.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 | 23 | 25 | 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 | 57 | {Object.keys(circles).map(key => { 58 | const { left, top } = circles[key] 59 | return ( 60 | 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.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 | --------------------------------------------------------------------------------