├── .babelrc ├── .gitignore ├── README.md ├── demo.js ├── lib ├── Draggable.js ├── Droppable.js ├── index.js └── utils.js ├── package-lock.json ├── package.json ├── src ├── Draggable.js ├── Droppable.js ├── index.js └── utils.js └── test └── spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-transform-react-jsx" 4 | ], 5 | "presets": [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | "useBuiltIns": "entry" 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Drag and Drop 2 | 3 | This library contains some very basic draggable and droppable components. 4 | 5 | You probably want to use something more stable and feature rich like [react-dnd](https://github.com/gaearon/react-dnd). 6 | 7 | ## Install 8 | 9 | ```sh 10 | npm install react-drag-and-drop 11 | ``` 12 | 13 | ### Use 14 | 15 | ```js 16 | import { Draggable, Droppable } from 'react-drag-and-drop' 17 | 18 | class App extends React.Component { 19 | render() { 20 | return( 21 |
22 | 27 | 30 | 31 | 32 |
33 | ) 34 | } 35 | onDrop(data) { 36 | console.log(data) 37 | // => banana 38 | } 39 | } 40 | ``` 41 | 42 | So the idea is that you wrap your components in *Draggable* and *Droppable* containers (instead of using mixins), define *types* and *data* to carry. You can also hook into the different drag events to create more funk. The best way (for now) to figure out how is to peak inside the src directory. The implementation is quite minimal. 43 | 44 | ## Changelog 45 | 46 | ### 3.0.0 47 | 48 | * Support for React 16 49 | * Updated most dev deps to latest 50 | 51 | ### 2.4.0 52 | 53 | * Support for `wrapperComponent` prop where one can pass a component to be used instead of the standard components for `Draggable` and `Droppable` :tada: Thanks to @aaa707 for this one :rocket: 54 | 55 | ### 2.3.0 56 | 57 | * Support for `enabled` prop for Droppable component 58 | 59 | ### 2.2.0 60 | 61 | * Droppable now accepts `className` as prop - thanks @abdennour :tada: 62 | 63 | ### 2.1.0 64 | 65 | * Support for React 15 66 | 67 | ### 2.0.2 68 | 69 | * Support for enable prop in Draggable component 70 | 71 | ### 2.0.1 72 | 73 | * Did a build (forgot for 2.0.0 release) :facepalm: 74 | 75 | ### 2.0.0 76 | 77 | * Updated to remove warning for React v0.14 78 | 79 | ### 1.1.0 80 | 81 | * Spreading this.props on both Draggable and Droppable 82 | 83 | ### 1.0.1 84 | 85 | * Added FireFox support (DOMStringList -> Array) 86 | 87 | ### 1.0.0 88 | 89 | * Initial release :tada: 90 | 91 | enjoy 92 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Draggable from './src/Draggable' 4 | import Droppable from './src/Droppable' 5 | 6 | class Demo extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | this.state = { 10 | draggable : ['drag','us','plz','but-not-me'], 11 | dropped : '', 12 | hovering : false 13 | } 14 | } 15 | render() { 16 | let draggable = this.state.draggable.map((title, index) => { 17 | return ( 18 |
  • 19 | {title} 20 |
  • 21 | ) 22 | }) 23 | let droppableStyle = { 24 | height : '200px' 25 | } 26 | if (this.state.hovering) droppableStyle.backgroundColor = 'pink' 27 | return ( 28 |
    29 | 30 |
    31 | Drop here... 32 | 38 |
    {this.state.dropped}
    39 |
    40 |
    41 |
    42 | But not here... 43 | 48 |
    {this.state.dropped}
    49 |
    50 |
    51 |
    52 | ) 53 | } 54 | onDragEnter() { 55 | this.setState({ hovering : true }) 56 | } 57 | onDragLeave() { 58 | this.setState({ hovering : false }) 59 | } 60 | onDrop(e) { 61 | this.setState({ hovering : false, dropped : e.yolo }) 62 | clearTimeout(this.dropTimeout) 63 | this.dropTimeout = setTimeout(() => { 64 | this.setState({ dropped : '' }) 65 | },1500) 66 | } 67 | } 68 | 69 | const app = document.createElement('div') 70 | document.body.append(app) 71 | 72 | ReactDOM.render(, app) 73 | -------------------------------------------------------------------------------- /lib/Draggable.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 11 | 12 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 19 | 20 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 21 | 22 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 23 | 24 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 25 | 26 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 27 | 28 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 29 | 30 | var Draggable = 31 | /*#__PURE__*/ 32 | function (_React$Component) { 33 | _inherits(Draggable, _React$Component); 34 | 35 | function Draggable() { 36 | _classCallCheck(this, Draggable); 37 | 38 | return _possibleConstructorReturn(this, _getPrototypeOf(Draggable).apply(this, arguments)); 39 | } 40 | 41 | _createClass(Draggable, [{ 42 | key: "render", 43 | value: function render() { 44 | var Tag = 'div'; 45 | var props = Object.assign({}, this.props); 46 | 47 | if (this.props.wrapperComponent) { 48 | Tag = this.props.wrapperComponent.type; 49 | props = Object.assign(props, this.props.wrapperComponent.props); 50 | delete props.wrapperComponent; 51 | } 52 | 53 | if (this.props.enabled) { 54 | props.draggable = 'true'; 55 | props.onDragEnd = this.onDragEnd.bind(this); 56 | props.onDragStart = this.onDragStart.bind(this); 57 | } 58 | 59 | delete props.enabled; 60 | return _react.default.createElement(Tag, props, props.children); 61 | } 62 | }, { 63 | key: "onDragStart", 64 | value: function onDragStart(e) { 65 | if (typeof this.props.onDragStart === 'function') this.props.onDragStart(e); 66 | var props = Object.assign({}, this.props); 67 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props); 68 | e.dataTransfer.setData(props.type, props.data); 69 | } 70 | }, { 71 | key: "onDragEnd", 72 | value: function onDragEnd(e) { 73 | if (typeof this.props.onDragEnd === 'function') this.props.onDragEnd(e); 74 | } 75 | }]); 76 | 77 | return Draggable; 78 | }(_react.default.Component); 79 | 80 | exports.default = Draggable; 81 | Draggable.defaultProps = { 82 | enabled: true 83 | }; -------------------------------------------------------------------------------- /lib/Droppable.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _utils = _interopRequireDefault(require("./utils")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 15 | 16 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 17 | 18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 19 | 20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 21 | 22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 23 | 24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 25 | 26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 27 | 28 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 29 | 30 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 31 | 32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 33 | 34 | function pickTypes(e) { 35 | return e.dataTransfer ? e.dataTransfer.types : []; 36 | } 37 | 38 | function filterProps(props) { 39 | var forbidden = ['types', 'className', 'enabled', 'wrapperComponent']; 40 | return Object.keys(props).reduce(function (p, c) { 41 | if (!forbidden.includes(c)) { 42 | p[c] = props[c]; 43 | } 44 | 45 | return p; 46 | }, {}); 47 | } 48 | 49 | var Droppable = 50 | /*#__PURE__*/ 51 | function (_React$Component) { 52 | _inherits(Droppable, _React$Component); 53 | 54 | function Droppable(props) { 55 | var _this; 56 | 57 | _classCallCheck(this, Droppable); 58 | 59 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Droppable).call(this, props)); 60 | _this.state = { 61 | over: false 62 | }; 63 | _this.droppable = _react.default.createRef(); 64 | return _this; 65 | } 66 | 67 | _createClass(Droppable, [{ 68 | key: "render", 69 | value: function render() { 70 | var Tag = 'div'; 71 | var props = Object.assign({}, this.props); 72 | 73 | if (this.props.wrapperComponent) { 74 | Tag = this.props.wrapperComponent.type; 75 | props = Object.assign(props, this.props.wrapperComponent.props); 76 | } 77 | 78 | var classes = 'Droppable'; 79 | if (props.className) classes += " ".concat(props.className); 80 | if (this.state.over) classes += ' over'; 81 | return _react.default.createElement(Tag, _extends({ 82 | ref: this.droppable, 83 | className: classes 84 | }, filterProps(props), { 85 | onDrop: this.onDrop.bind(this), 86 | onDragOver: this.onDragOver.bind(this), 87 | onDragEnter: this.onDragEnter.bind(this), 88 | onDragLeave: this.onDragLeave.bind(this), 89 | onDragExit: this.onDragLeave.bind(this) 90 | }), props.children); 91 | } 92 | }, { 93 | key: "onDragOver", 94 | value: function onDragOver(e) { 95 | e.preventDefault(); 96 | if (!this.allowed(pickTypes(e))) return; 97 | if (typeof this.props.onDragOver === 'function') this.props.onDragOver(e); 98 | } 99 | }, { 100 | key: "onDragEnter", 101 | value: function onDragEnter(e) { 102 | e.preventDefault(); 103 | if (this.state.over) return; 104 | if (!this.allowed(pickTypes(e))) return; 105 | if (typeof this.props.onDragEnter === 'function') this.props.onDragEnter(e); 106 | this.setState({ 107 | over: true 108 | }); 109 | } 110 | }, { 111 | key: "onDragLeave", 112 | value: function onDragLeave(e) { 113 | e.preventDefault(); 114 | if (!this.allowed(pickTypes(e))) return; 115 | var over = true; 116 | if (e.clientX <= this.position.left || e.clientX >= this.position.right) over = false; 117 | if (e.clientY <= this.position.top || e.clientY >= this.position.bottom) over = false; 118 | if (over) return; 119 | this.setState({ 120 | over: false 121 | }); 122 | if (typeof this.props.onDragLeave === 'function') this.props.onDragLeave(e); 123 | } 124 | }, { 125 | key: "onDrop", 126 | value: function onDrop(e) { 127 | e.preventDefault(); 128 | if (!this.allowed(pickTypes(e))) return; 129 | this.setState({ 130 | over: false 131 | }); 132 | var props = Object.assign({}, this.props); 133 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props); 134 | var data = !props.types ? null : [].concat(props.types).reduce(function (d, type) { 135 | d[type] = e.dataTransfer.getData(type); 136 | return d; 137 | }, {}); 138 | if (typeof this.props.onDrop === 'function') this.props.onDrop(data, e); 139 | } 140 | }, { 141 | key: "allowed", 142 | value: function allowed(attemptingTypes) { 143 | var props = Object.assign({}, this.props); 144 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props); 145 | if (!props.enabled) return false; 146 | 147 | var _attemptingTypes = _utils.default.toArray(attemptingTypes); 148 | 149 | if (!props.types) return true; 150 | return [].concat(props.types).reduce(function (sum, type) { 151 | if (_attemptingTypes.indexOf(type) >= 0) return true; 152 | return sum; 153 | }, false); 154 | } 155 | }, { 156 | key: "componentDidMount", 157 | value: function componentDidMount() { 158 | // TODO: Listen for window resize? 159 | var node = this.droppable.current; 160 | this.position = { 161 | top: node.offsetTop + 5, 162 | left: node.offsetLeft + 5, 163 | right: node.offsetLeft + node.offsetWidth - 5, 164 | bottom: node.offsetTop + node.offsetHeight - 5 165 | }; 166 | } 167 | }]); 168 | 169 | return Droppable; 170 | }(_react.default.Component); 171 | 172 | exports.default = Droppable; 173 | Droppable.defaultProps = { 174 | enabled: true 175 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "Draggable", { 7 | enumerable: true, 8 | get: function get() { 9 | return _Draggable.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "Droppable", { 13 | enumerable: true, 14 | get: function get() { 15 | return _Droppable.default; 16 | } 17 | }); 18 | 19 | var _Draggable = _interopRequireDefault(require("./Draggable")); 20 | 21 | var _Droppable = _interopRequireDefault(require("./Droppable")); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | function toArray(obj) { 9 | var array = []; // iterate backwards ensuring that length is an UInt32 10 | 11 | for (var i = obj.length >>> 0; i--;) { 12 | array[i] = obj[i]; 13 | } 14 | 15 | return array; 16 | } 17 | 18 | var _default = { 19 | toArray: toArray 20 | }; 21 | exports.default = _default; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-drag-and-drop", 3 | "version": "3.0.0", 4 | "description": "Basic Drag and Drop for React", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "budo demo.js --live -- -t babelify", 11 | "test": "mocha -R nyan -w --check-leaks --require @babel/register", 12 | "build": "babel src/ --out-dir lib", 13 | "prepublish": "npm run build" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/asbjornenge/react-drag-and-drop.git" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "react-component", 22 | "drag-and-drop" 23 | ], 24 | "author": "Asbjorn Enge ", 25 | "license": "BSD-3-Clause", 26 | "bugs": { 27 | "url": "https://github.com/asbjornenge/react-drag-and-drop/issues" 28 | }, 29 | "homepage": "https://github.com/asbjornenge/react-drag-and-drop", 30 | "devDependencies": { 31 | "@babel/cli": "^7.2.0", 32 | "@babel/core": "^7.2.0", 33 | "@babel/plugin-transform-react-jsx": "^7.2.0", 34 | "@babel/preset-env": "^7.2.0", 35 | "@babel/register": "^7.0.0", 36 | "babelify": "^10.0.0", 37 | "budo": "^11.5.0", 38 | "enzyme": "^3.7.0", 39 | "enzyme-adapter-react-16": "^1.7.1", 40 | "expect": "^23.6.0", 41 | "mocha": "^5.2.0", 42 | "nanodom": "0.0.3", 43 | "react": "^16.6.3", 44 | "react-dom": "^16.6.3", 45 | "sinon": "^7.2.0", 46 | "testdom": "^3.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Draggable.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Draggable extends React.Component { 4 | render() { 5 | let Tag = 'div' 6 | let props = Object.assign({}, this.props) 7 | if (this.props.wrapperComponent) { 8 | Tag = this.props.wrapperComponent.type 9 | props = Object.assign(props, this.props.wrapperComponent.props) 10 | delete props.wrapperComponent 11 | } 12 | if (this.props.enabled) { 13 | props.draggable = 'true' 14 | props.onDragEnd = this.onDragEnd.bind(this) 15 | props.onDragStart = this.onDragStart.bind(this) 16 | } 17 | delete props.enabled 18 | return ( 19 | 20 | {props.children} 21 | 22 | ) 23 | } 24 | onDragStart(e) { 25 | if (typeof this.props.onDragStart === 'function') this.props.onDragStart(e) 26 | let props = Object.assign({}, this.props) 27 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props) 28 | e.dataTransfer.setData(props.type, props.data) 29 | } 30 | onDragEnd(e) { 31 | if (typeof this.props.onDragEnd === 'function') this.props.onDragEnd(e) 32 | } 33 | } 34 | 35 | Draggable.defaultProps = { 36 | enabled: true 37 | } 38 | -------------------------------------------------------------------------------- /src/Droppable.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import utils from './utils' 3 | 4 | function pickTypes(e) { 5 | return e.dataTransfer ? e.dataTransfer.types : [] 6 | } 7 | 8 | function filterProps(props) { 9 | let forbidden = ['types', 'className', 'enabled', 'wrapperComponent'] 10 | return Object.keys(props).reduce((p, c) => { 11 | if (!forbidden.includes(c)) { 12 | p[c] = props[c] 13 | } 14 | return p 15 | }, {}) 16 | } 17 | 18 | export default class Droppable extends React.Component { 19 | constructor(props) { 20 | super(props) 21 | this.state = { 22 | over : false 23 | } 24 | this.droppable = React.createRef() 25 | } 26 | render() { 27 | let Tag = 'div' 28 | let props = Object.assign({}, this.props) 29 | if (this.props.wrapperComponent) { 30 | Tag = this.props.wrapperComponent.type 31 | props = Object.assign(props, this.props.wrapperComponent.props) 32 | } 33 | let classes = 'Droppable'; 34 | if(props.className) classes+=` ${props.className}`; 35 | if (this.state.over) classes+=' over'; 36 | return ( 37 | 43 | {props.children} 44 | 45 | ) 46 | } 47 | onDragOver(e) { 48 | e.preventDefault() 49 | if (!this.allowed(pickTypes(e))) return 50 | if (typeof this.props.onDragOver === 'function') this.props.onDragOver(e) 51 | } 52 | onDragEnter(e) { 53 | e.preventDefault() 54 | if (this.state.over) return 55 | if (!this.allowed(pickTypes(e))) return 56 | if (typeof this.props.onDragEnter === 'function') this.props.onDragEnter(e) 57 | this.setState({ over : true }) 58 | } 59 | onDragLeave(e) { 60 | e.preventDefault() 61 | if (!this.allowed(pickTypes(e))) return 62 | let over = true 63 | if (e.clientX <= this.position.left || e.clientX >= this.position.right) over = false 64 | if (e.clientY <= this.position.top || e.clientY >= this.position.bottom) over = false 65 | if (over) return 66 | this.setState({ over : false }) 67 | if (typeof this.props.onDragLeave === 'function') this.props.onDragLeave(e) 68 | } 69 | onDrop(e) { 70 | e.preventDefault() 71 | if (!this.allowed(pickTypes(e))) return 72 | this.setState({ over : false }) 73 | let props = Object.assign({}, this.props) 74 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props) 75 | const data = !props.types ? null : [].concat(props.types).reduce((d, type) => { 76 | d[type] = e.dataTransfer.getData(type) 77 | return d 78 | },{}) 79 | if (typeof this.props.onDrop === 'function') this.props.onDrop(data, e) 80 | } 81 | allowed(attemptingTypes) { 82 | let props = Object.assign({}, this.props) 83 | if (this.props.wrapperComponent) props = Object.assign(props, this.props.wrapperComponent.props) 84 | if (!props.enabled) return false 85 | let _attemptingTypes = utils.toArray(attemptingTypes) 86 | if (!props.types) return true 87 | return [].concat(props.types).reduce((sum, type) => { 88 | if (_attemptingTypes.indexOf(type) >= 0) return true 89 | return sum 90 | }, false) 91 | } 92 | componentDidMount() { 93 | // TODO: Listen for window resize? 94 | var node = this.droppable.current 95 | this.position = { 96 | top : node.offsetTop+5, 97 | left : node.offsetLeft+5, 98 | right : node.offsetLeft+node.offsetWidth-5, 99 | bottom : node.offsetTop+node.offsetHeight-5 100 | } 101 | } 102 | } 103 | 104 | Droppable.defaultProps = { 105 | enabled: true 106 | } 107 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Draggable from './Draggable' 2 | import Droppable from './Droppable' 3 | 4 | export { 5 | Draggable, 6 | Droppable 7 | } 8 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function toArray(obj) { 2 | var array = []; 3 | // iterate backwards ensuring that length is an UInt32 4 | for (var i = obj.length >>> 0; i--;) { 5 | array[i] = obj[i]; 6 | } 7 | return array; 8 | } 9 | 10 | export default { 11 | toArray : toArray 12 | } 13 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | import testdom from 'testdom' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import sinon from 'sinon' 5 | import expect from 'expect' 6 | import assert from 'assert' 7 | import nanodom from 'nanodom' 8 | import * as enzyme from 'enzyme' 9 | import Adapter from 'enzyme-adapter-react-16' 10 | import { Draggable, Droppable } from '../src/index' 11 | enzyme.configure({ adapter: new Adapter() }) 12 | 13 | testdom('
    ') 14 | 15 | class App extends React.Component { 16 | render() { 17 | return ( 18 |
    19 | 20 |
    I am draggable
    21 |
    22 | 23 |
    I am droppable
    24 |
    25 |
    26 | ) 27 | } 28 | } 29 | 30 | describe('drag-and-drop', () => { 31 | 32 | before((done) => { 33 | ReactDOM.render(, document.querySelector('#app'), done) 34 | }) 35 | 36 | it('wraps droppable in a container', () => { 37 | let drop = nanodom('div').filter((div) => { 38 | return div.innerHTML == 'I am droppable' 39 | }) 40 | assert(drop[0].parentNode.className == 'Droppable') 41 | }) 42 | 43 | it(`appends "props className" with Droppable class`, () => { 44 | let anyOtherClass = "anyotherclass-something"; 45 | const wrapper = enzyme.mount(); 46 | expect(wrapper.find(`.Droppable.${anyOtherClass}`).length).toEqual(1); 47 | }) 48 | 49 | it('wraps draggable in a container and marks it as draggable', () => { 50 | let drag = nanodom('div').filter((div) => { 51 | return div.innerHTML == 'I am draggable' 52 | }) 53 | assert(drag[0].parentNode.getAttribute('draggable')) 54 | }) 55 | 56 | // TODO: Add more and relevant tests 57 | 58 | it('supports disabling Droppable', () => { 59 | const onDrop = sinon.spy() 60 | const disabled = enzyme.mount() 61 | disabled.find('.Droppable').simulate('drop') 62 | assert(!onDrop.calledOnce) 63 | const enabled = enzyme.mount() 64 | enabled.find('.Droppable').simulate('drop') 65 | assert(onDrop.calledOnce) 66 | }) 67 | 68 | it('supports disabling Draggable', () => { 69 | const enabled = enzyme.mount() 70 | assert(enabled.find('div').props('draggable').draggable) 71 | const disabled = enzyme.mount() 72 | assert(!disabled.find('div').props('draggable').draggable) 73 | }) 74 | 75 | it('supports wrapper component for Droppable', () => { 76 | const onDrop = sinon.spy() 77 | const wrapper = enzyme.mount(} onDrop={onDrop} />) 78 | wrapper.find('section.Droppable').simulate('drop') 79 | assert(onDrop.calledOnce) 80 | }) 81 | 82 | it('supports wrapper component for Draggable', () => { 83 | const wrapper = enzyme.mount(} />) 84 | assert(wrapper.find('span').props('draggable').draggable) 85 | }) 86 | 87 | }) 88 | --------------------------------------------------------------------------------