├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── bower.json ├── dist ├── react-draggable.js ├── react-draggable.js.map ├── react-draggable.min.js └── react-draggable.min.js.map ├── example └── index.html ├── karma.conf.js ├── lib ├── draggable.js └── styles.css ├── package.json ├── test └── draggable_test.jsx └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | bower.json 3 | example 4 | karma.conf.js 5 | script 6 | specs 7 | src 8 | webpack.config.js 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | email: 8 | on_failure: change 9 | on_success: never 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 0.1.0 (Jul 25, 2014) 4 | 5 | - Initial release 6 | 7 | ### 0.1.1 (Jul 26, 2014) 8 | 9 | - Fixing dragging not stopping on mouseup in some cases 10 | 11 | ### 0.2.0 (Sep 10, 2014) 12 | 13 | - Adding support for snapping to a grid 14 | - Adding support for specifying start position 15 | - Ensure event handlers are destroyed on unmount 16 | - Adding browserify support 17 | - Adding bower support 18 | 19 | ### 0.2.1 (Sep 10, 2014) 20 | 21 | - Exporting as ReactDraggable 22 | 23 | ### 0.3.0 (Oct 21, 2014) 24 | 25 | - Adding support for touch devices 26 | 27 | ### 0.4.1 (Dec 7, 2014) 28 | 29 | - Adding support for bounding movement 30 | 31 | ### 0.4.2 (Dec 7, 2014) 32 | 33 | - Prevent errors when accessing browser globals 34 | 35 | ### 0.4.3 (Mar 2, 2015) 36 | 37 | - Update dependencides 38 | - Fix an issue where browser may be detected as touch-enabled but touch event isn't thrown. @STRML 39 | 40 | ### 0.5.0 (Mar 5, 2015) 41 | 42 | - Remove dependency on Reactify for Browserify users 43 | [#2](https://github.com/mikepb/react-draggable/issues/2) 44 | - Use Webpack directly for minification 45 | - Source map files now have the `.js.map` file extention 46 | 47 | ### 0.5.1 (Mar 5, 2015) 48 | 49 | - Remove `peerDependencies` from `package.json` #4 50 | 51 | ### 0.6.0 (June 24, 2015) 52 | 53 | - Fix: event forwarding and preventDefault() logic. @motiz88 54 | - Fix: use React.cloneElement instead of cloneWithProps. @m0x72 55 | - Update dependencies 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-draggable [![Build Status](https://travis-ci.org/mikepb/react-draggable.svg?branch=master)](https://travis-ci.org/mikepb/react-draggable) 2 | 3 | React draggable component 4 | 5 | Based on https://github.com/mzabriskie/react-draggable 6 | 7 | ## Installing 8 | 9 | ```bash 10 | $ npm install react react-draggable2 11 | ``` 12 | 13 | You will also need install React to your top-level project due to 14 | [#2](https://github.com/mzabriskie/react-draggable/issues/22). 15 | 16 | ## Demo 17 | 18 | http://mzabriskie.github.io/react-draggable/example/ 19 | 20 | ## Example 21 | 22 | ```js 23 | /** @jsx React.DOM */ 24 | var React = require('react'), 25 | Draggable = require('react-draggable'); 26 | 27 | var App = React.createClass({ 28 | handleStart: function (event, ui) { 29 | console.log('Event: ', event); 30 | console.log('Position: ', ui.position); 31 | }, 32 | 33 | handleDrag: function (event, ui) { 34 | console.log('Event: ', event); 35 | console.log('Position: ', ui.position); 36 | }, 37 | 38 | handleStop: function (event, ui) { 39 | console.log('Event: ', event); 40 | console.log('Position: ', ui.position); 41 | }, 42 | 43 | render: function () { 44 | return ( 45 | // transparently adds draggable interactivity 46 | // to whatever element is supplied as `this.props.children`. 47 | // Only a single element is allowed or an Error will be thrown. 48 | // 49 | // `axis` determines which axis the draggable can move. 50 | // - 'both' allows movement horizontally and vertically (default). 51 | // - 'x' limits movement to horizontal axis. 52 | // - 'y' limits movement to vertical axis. 53 | // 54 | // `handle` specifies a selector to be used as the handle that initiates drag. 55 | // 56 | // `cancel` specifies a selector to be used to prevent drag initialization. 57 | // 58 | // `bound` determines whether to bound the movement to the parent box. 59 | // - 'top' bounds movement to the top edge of the parent box. 60 | // - 'right' bounds movement to the right edge of the parent box. 61 | // - 'bottom' bounds movement to the bottom edge of the parent box. 62 | // - 'left' bounds movement to the left edge of the parent box. 63 | // - 'all' bounds movement to all edges (default if not specified). 64 | // - 'point' to constrain only the top-left corner. 65 | // - 'box' to constrain the entire box (default if not specified). 66 | // 67 | // `constrain` takes a function to constrain the dragging. 68 | // 69 | // `start` specifies the x and y that the dragged item should start at 70 | // 71 | // `zIndex` specifies the zIndex to use while dragging. 72 | // 73 | // `onStart` is called when dragging starts. 74 | // 75 | // `onDrag` is called while dragging. 76 | // 77 | // `onStop` is called when dragging stops. 78 | 79 | 89 |
90 |
Drag from here
91 |
Lorem ipsum...
92 |
93 |
94 | ); 95 | } 96 | }); 97 | 98 | function constrain (snap) { 99 | function constrainOffset (offset, prev) { 100 | var delta = offset - prev; 101 | if (Math.abs(delta) >= snap) { 102 | return prev + parseInt(delta / snap, 10) * snap; 103 | } 104 | return prev; 105 | } 106 | return function (pos) { 107 | return { 108 | top: constrainOffset(pos.top, pos.prevTop), 109 | left: constrainOffset(pos.left, pos.prevLeft) 110 | }; 111 | }; 112 | } 113 | 114 | React.renderComponent(, document.body); 115 | ``` 116 | 117 | ## Contributing 118 | 119 | - Fork the project 120 | - `$ npm install && npm run watch` 121 | - Make changes 122 | - Add appropriate tests 123 | - `$ npm test` 124 | - If tests don't pass, make them pass. 125 | - Update README with appropriate docs. 126 | - Commit and PR 127 | 128 | ## License 129 | 130 | MIT 131 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-draggable2", 3 | "version": "0.5.0", 4 | "homepage": "https://github.com/mikepb/react-draggable", 5 | "authors": [ 6 | "Matt Zabriskie", 7 | "Michael Phan-Ba" 8 | ], 9 | "description": "React draggable component", 10 | "main": "./lib/draggable.js", 11 | "keywords": [ 12 | "react", 13 | "draggable" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "example", 19 | "lib", 20 | "node_modules", 21 | "script", 22 | "specs", 23 | "index.js", 24 | "karma.conf.js", 25 | "webpack.config.js", 26 | "package.json" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /dist/react-draggable.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("React")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["React"], factory); 6 | else if(typeof exports === 'object') 7 | exports["ReactDraggable"] = factory(require("React")); 8 | else 9 | root["ReactDraggable"] = factory(root["React"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var React = __webpack_require__(1); 60 | var emptyFunction = function () {}; 61 | 62 | function updateBoundState (state, bound) { 63 | if (!bound) return state; 64 | bound = String(bound); 65 | var boundTop = !!~bound.indexOf('top'); 66 | var boundRight = !!~bound.indexOf('right'); 67 | var boundBottom = !!~bound.indexOf('bottom'); 68 | var boundLeft = !!~bound.indexOf('left'); 69 | var boundAll = !!~bound.indexOf('all') || 70 | !(boundTop || boundRight || boundBottom || boundLeft); 71 | var boundBox = !~bound.indexOf('point'); 72 | state.boundTop = boundAll || boundTop; 73 | state.boundRight = boundAll || boundRight; 74 | state.boundBottom = boundAll || boundBottom; 75 | state.boundLeft = boundAll || boundLeft; 76 | state.boundBox = boundBox; 77 | return state; 78 | }; 79 | 80 | function createUIEvent(draggable) { 81 | return { 82 | position: { 83 | top: draggable.state.offsetTop, 84 | left: draggable.state.offsetLeft 85 | } 86 | }; 87 | } 88 | 89 | function canDragY(draggable) { 90 | return draggable.props.axis === 'both' || 91 | draggable.props.axis === 'y'; 92 | } 93 | 94 | function canDragX(draggable) { 95 | return draggable.props.axis === 'both' || 96 | draggable.props.axis === 'x'; 97 | } 98 | 99 | function isFunction(func) { 100 | return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]' 101 | } 102 | 103 | // @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc 104 | function findInArray(array, callback) { 105 | for (var i = 0, length = array.length, element = null; i < length, element = array[i]; i++) { 106 | if (callback.apply(callback, [element, i, array])) return element; 107 | } 108 | } 109 | 110 | function matchesSelector(el, selector) { 111 | var method = findInArray([ 112 | 'matches', 113 | 'webkitMatchesSelector', 114 | 'mozMatchesSelector', 115 | 'msMatchesSelector', 116 | 'oMatchesSelector' 117 | ], function(method){ 118 | return isFunction(el[method]); 119 | }); 120 | 121 | return el[method].call(el, selector); 122 | } 123 | 124 | // look ::handleDragStart 125 | //function isMultiTouch(e) { 126 | // return e.touches && Array.isArray(e.touches) && e.touches.length > 1 127 | //} 128 | 129 | /** 130 | * simple abstraction for dragging events names 131 | * */ 132 | var dragEventsFor = { 133 | touch: { 134 | start: 'touchstart', 135 | move: 'touchmove', 136 | end: 'touchend' 137 | }, 138 | mouse: { 139 | start: 'mousedown', 140 | move: 'mousemove', 141 | end: 'mouseup' 142 | } 143 | }; 144 | 145 | /** 146 | * get {clientX, clientY} positions of control 147 | * */ 148 | function getControlPosition(e) { 149 | var position = (e.touches && e.touches[0]) || e; 150 | return { 151 | clientX: position.clientX, 152 | clientY: position.clientY 153 | } 154 | } 155 | 156 | function addEvent(el, event, handler) { 157 | if (!el) { return; } 158 | if (el.attachEvent) { 159 | el.attachEvent('on' + event, handler); 160 | } else if (el.addEventListener) { 161 | el.addEventListener(event, handler, true); 162 | } else { 163 | el['on' + event] = handler; 164 | } 165 | } 166 | 167 | function removeEvent(el, event, handler) { 168 | if (!el) { return; } 169 | if (el.detachEvent) { 170 | el.detachEvent('on' + event, handler); 171 | } else if (el.removeEventListener) { 172 | el.removeEventListener(event, handler, true); 173 | } else { 174 | el['on' + event] = null; 175 | } 176 | } 177 | 178 | module.exports = React.createClass({ 179 | displayName: 'Draggable', 180 | mixins: [React.addons.PureRenderMixin], 181 | 182 | propTypes: { 183 | /** 184 | * `axis` determines which axis the draggable can move. 185 | * 186 | * 'both' allows movement horizontally and vertically. 187 | * 'x' limits movement to horizontal axis. 188 | * 'y' limits movement to vertical axis. 189 | * 190 | * Defaults to 'both'. 191 | */ 192 | axis: React.PropTypes.oneOf(['both', 'x', 'y']), 193 | 194 | /** 195 | * `handle` specifies a selector to be used as the handle that initiates drag. 196 | * 197 | * Example: 198 | * 199 | * ```jsx 200 | * var App = React.createClass({ 201 | * render: function () { 202 | * return ( 203 | * 204 | *
205 | *
Click me to drag
206 | *
This is some other content
207 | *
208 | *
209 | * ); 210 | * } 211 | * }); 212 | * ``` 213 | */ 214 | handle: React.PropTypes.string, 215 | 216 | /** 217 | * `cancel` specifies a selector to be used to prevent drag initialization. 218 | * 219 | * Example: 220 | * 221 | * ```jsx 222 | * var App = React.createClass({ 223 | * render: function () { 224 | * return( 225 | * 226 | *
227 | *
You can't drag from here
228 | *
Dragging here works fine
229 | *
230 | *
231 | * ); 232 | * } 233 | * }); 234 | * ``` 235 | */ 236 | cancel: React.PropTypes.string, 237 | 238 | /** 239 | * `bound` determines whether to bound the movement to the parent box. 240 | * 241 | * The property takes a list of space-separated strings. The Draggable 242 | * is bounded by the nearest DOMNode.offsetParent. To set the offset 243 | * parent, give it a position value other than 'static'. 244 | * 245 | * Optionally choose one or more bounds from: 246 | * 'top' bounds movement to the top edge of the parent box. 247 | * 'right' bounds movement to the right edge of the parent box. 248 | * 'bottom' bounds movement to the bottom edge of the parent box. 249 | * 'left' bounds movement to the left edge of the parent box. 250 | * 'all' bounds movement to all edges (default if not specified). 251 | * 252 | * Optionally choose one anchor from: 253 | * 'point' to constrain only the top-left corner. 254 | * 'box' to constrain the entire box (default if not specified). 255 | * 256 | * You may use more than one bound, e.g. 'top left point'. Set to a 257 | * falsy value to disable. 258 | * 259 | * Defaults to 'all box'. 260 | */ 261 | bound: React.PropTypes.string, 262 | 263 | /** 264 | * `grid` specifies the x and y that dragging should snap to. 265 | * 266 | * Example: 267 | * 268 | * ```jsx 269 | * var App = React.createClass({ 270 | * render: function () { 271 | * return ( 272 | * 273 | *
I snap to a 25 x 25 grid
274 | *
275 | * ); 276 | * } 277 | * }); 278 | * ``` 279 | */ 280 | grid: React.PropTypes.arrayOf(React.PropTypes.number), 281 | 282 | /** 283 | * `constrain` takes a function to constrain the dragging. 284 | * 285 | * Example: 286 | * 287 | * ```jsx 288 | * function constrain (snap) { 289 | * function constrainOffset (offset, prev) { 290 | * var delta = offset - prev; 291 | * if (Math.abs(delta) >= snap) { 292 | * return prev + (delta < 0 ? -snap : snap); 293 | * } 294 | * return prev; 295 | * } 296 | * return function (pos) { 297 | * return { 298 | * top: constrainOffset(pos.top, pos.prevTop), 299 | * left: constrainOffset(pos.left, pos.prevLeft) 300 | * }; 301 | * }; 302 | * } 303 | * var App = React.createClass({ 304 | * render: function () { 305 | * return ( 306 | * 307 | *
I snap to a 25 x 25 grid
308 | *
309 | * ); 310 | * } 311 | * }); 312 | * ``` 313 | */ 314 | constrain: React.PropTypes.func, 315 | 316 | /** 317 | * `start` specifies the x and y that the dragged item should start at 318 | * 319 | * Example: 320 | * 321 | * ```jsx 322 | * var App = React.createClass({ 323 | * render: function () { 324 | * return ( 325 | * 326 | *
I start with left: 25px; top: 25px;
327 | *
328 | * ); 329 | * } 330 | * }); 331 | * ``` 332 | */ 333 | start: React.PropTypes.object, 334 | 335 | /** 336 | * `zIndex` specifies the zIndex to use while dragging. 337 | * 338 | * Example: 339 | * 340 | * ```jsx 341 | * var App = React.createClass({ 342 | * render: function () { 343 | * return ( 344 | * 345 | *
I have a zIndex
346 | *
347 | * ); 348 | * } 349 | * }); 350 | * ``` 351 | */ 352 | zIndex: React.PropTypes.number, 353 | 354 | /** 355 | * `useChild` determines whether to use the first child as root. 356 | * 357 | * If false, a div is created. This option is required if any children 358 | * have a ref. 359 | * 360 | * Defaults to true. 361 | */ 362 | useChild: React.PropTypes.bool, 363 | 364 | /** 365 | * Called when dragging starts. 366 | * 367 | * Example: 368 | * 369 | * ```js 370 | * function (event, ui) {} 371 | * ``` 372 | * 373 | * `event` is the Event that was triggered. 374 | * `ui` is an object: 375 | * 376 | * ```js 377 | * { 378 | * position: {top: 0, left: 0} 379 | * } 380 | * ``` 381 | */ 382 | onStart: React.PropTypes.func, 383 | 384 | /** 385 | * Called while dragging. 386 | * 387 | * Example: 388 | * 389 | * ```js 390 | * function (event, ui) {} 391 | * ``` 392 | * 393 | * `event` is the Event that was triggered. 394 | * `ui` is an object: 395 | * 396 | * ```js 397 | * { 398 | * position: {top: 0, left: 0} 399 | * } 400 | * ``` 401 | */ 402 | onDrag: React.PropTypes.func, 403 | 404 | /** 405 | * Called when dragging stops. 406 | * 407 | * Example: 408 | * 409 | * ```js 410 | * function (event, ui) {} 411 | * ``` 412 | * 413 | * `event` is the Event that was triggered. 414 | * `ui` is an object: 415 | * 416 | * ```js 417 | * { 418 | * position: {top: 0, left: 0} 419 | * } 420 | * ``` 421 | */ 422 | onStop: React.PropTypes.func, 423 | 424 | /** 425 | * A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally. 426 | * 427 | */ 428 | onMouseDown: React.PropTypes.func, 429 | onTouchStart: React.PropTypes.func, 430 | onMouseUp: React.PropTypes.func, 431 | onTouchEnd: React.PropTypes.func, 432 | }, 433 | 434 | componentWillMount: function() { 435 | this._dragHandlers = { 436 | mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp}, 437 | touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd} 438 | }; 439 | }, 440 | 441 | getDefaultProps: function () { 442 | return { 443 | axis: 'both', 444 | bound: null, 445 | handle: null, 446 | cancel: null, 447 | grid: null, 448 | start: {}, 449 | zIndex: NaN, 450 | useChild: true, 451 | onStart: emptyFunction, 452 | onDrag: emptyFunction, 453 | onStop: emptyFunction, 454 | onMouseDown: emptyFunction, 455 | onMouseUp: emptyFunction, 456 | onTouchStart: emptyFunction, 457 | onTouchEnd: emptyFunction, 458 | }; 459 | }, 460 | 461 | getInitialState: function () { 462 | var state = { 463 | // Whether or not currently dragging 464 | dragging: false, 465 | 466 | // Pointer offset on screen 467 | clientX: 0, clientY: 0, 468 | 469 | // DOMNode offset relative to parent 470 | offsetLeft: this.props.start.x || 0, offsetTop: this.props.start.y || 0 471 | }; 472 | 473 | updateBoundState(state, this.props.bound); 474 | 475 | return state; 476 | }, 477 | 478 | componentWillReceiveProps: function (nextProps) { 479 | var state = updateBoundState({}, nextProps.bound); 480 | if (nextProps.start) { 481 | if (nextProps.start.x != null) { 482 | state.offsetLeft = nextProps.start.x || 0; 483 | } 484 | if (nextProps.start.y != null) { 485 | state.offsetTop = nextProps.start.y || 0; 486 | } 487 | } 488 | this.setState(state); 489 | }, 490 | 491 | componentWillUnmount: function() { 492 | // Remove any leftover event handlers 493 | var bodyElement = this.getDOMNode().ownerDocument.body; 494 | removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move); 495 | removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end); 496 | removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move); 497 | removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end); 498 | }, 499 | 500 | handleMouseDown: function(e) { 501 | if (typeof this.props.onMouseDown == 'function') { 502 | this.props.onMouseDown(e); 503 | } 504 | if (!e.defaultPrevented) { 505 | this.handleDragStart(e, 'mouse'); 506 | } 507 | }, 508 | 509 | handleMouseMove: function(e) { 510 | this.handleDrag(e, 'mouse'); 511 | }, 512 | 513 | handleMouseUp: function(e) { 514 | if (typeof this.props.onMouseUp == 'function') { 515 | this.props.onMouseUp(e); 516 | } 517 | if (!e.defaultPrevented) { 518 | this.handleDragEnd(e, 'mouse'); 519 | } 520 | }, 521 | 522 | handleTouchStart: function(e) { 523 | if (typeof this.props.onTouchStart == 'function') { 524 | this.props.onTouchStart(e); 525 | } 526 | if (!e.defaultPrevented) { 527 | this.handleDragStart(e, 'touch'); 528 | } 529 | }, 530 | 531 | handleTouchMove: function(e) { 532 | this.handleDrag(e, 'touch'); 533 | }, 534 | 535 | handleTouchEnd: function(e) { 536 | if (typeof this.props.onTouchEnd == 'function') { 537 | this.props.onTouchEnd(e); 538 | } 539 | if (!e.defaultPrevented) { 540 | this.handleDragEnd(e, 'touch'); 541 | } 542 | }, 543 | 544 | handleDragStart: function (e, device) { 545 | if (this.state.dragging) { 546 | return; 547 | } 548 | 549 | // todo: write right implementation to prevent multitouch drag 550 | // prevent multi-touch events 551 | // if (isMultiTouch(e)) { 552 | // this.handleDragEnd.apply(e, arguments); 553 | // return 554 | // } 555 | 556 | // Short circuit if handle or cancel prop was provided and selector doesn't match 557 | if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) || 558 | (this.props.cancel && matchesSelector(e.target, this.props.cancel))) { 559 | return; 560 | } 561 | 562 | e.preventDefault(); 563 | 564 | var dragPoint = getControlPosition(e); 565 | 566 | // Initiate dragging 567 | this.setState({ 568 | dragging: device, 569 | clientX: dragPoint.clientX, 570 | clientY: dragPoint.clientY 571 | }); 572 | 573 | // Call event handler 574 | this.props.onStart(e, createUIEvent(this)); 575 | 576 | var bodyElement = this.getDOMNode().ownerDocument.body; 577 | 578 | // Add event handlers 579 | addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move); 580 | addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end); 581 | 582 | // Add dragging class to body element 583 | if (bodyElement) bodyElement.className += ' react-draggable-dragging'; 584 | }, 585 | 586 | handleDragEnd: function (e, device) { 587 | // Short circuit if not currently dragging 588 | if (!this.state.dragging || (this.state.dragging !== device)) { 589 | return; 590 | } 591 | 592 | e.preventDefault(); 593 | 594 | // Turn off dragging 595 | this.setState({ 596 | dragging: false 597 | }); 598 | 599 | // Call event handler 600 | this.props.onStop(e, createUIEvent(this)); 601 | 602 | 603 | var bodyElement = this.getDOMNode().ownerDocument.body; 604 | 605 | // Remove event handlers 606 | removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move); 607 | removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end); 608 | 609 | 610 | // Remove dragging class from body element 611 | if (bodyElement) { 612 | var className = bodyElement.className; 613 | bodyElement.className = 614 | className.replace(/(?:^|\s+)react-draggable-dragging\b/, ' '); 615 | } 616 | }, 617 | 618 | handleDrag: function (e, device) { 619 | var dragPoint = getControlPosition(e); 620 | var offsetLeft = this._toPixels(this.state.offsetLeft); 621 | var offsetTop = this._toPixels(this.state.offsetTop); 622 | 623 | var state = { 624 | offsetLeft: offsetLeft, 625 | offsetTop: offsetTop 626 | }; 627 | 628 | // Get parent DOM node 629 | var node = this.getDOMNode(); 630 | var offsetParent = node.offsetParent; 631 | var offset, boundingValue; 632 | 633 | if (canDragX(this)) { 634 | // Calculate updated position 635 | offset = offsetLeft + dragPoint.clientX - this.state.clientX; 636 | 637 | // Bound movement to parent box 638 | if (this.state.boundLeft) { 639 | boundingValue = state.offsetLeft - node.offsetLeft; 640 | if (offset < boundingValue) { 641 | offset = boundingValue; 642 | } 643 | } 644 | if (this.state.boundRight) { 645 | boundingValue += offsetParent.clientWidth; 646 | if (this.state.boundBox) { 647 | boundingValue -= node.offsetWidth; 648 | } 649 | if (offset > boundingValue) { 650 | offset = boundingValue; 651 | } 652 | } 653 | // Update left 654 | state.offsetLeft = offset; 655 | } 656 | 657 | if (canDragY(this)) { 658 | // Calculate updated position 659 | offset = offsetTop + dragPoint.clientY - this.state.clientY; 660 | // Bound movement to parent box 661 | if (this.state.boundTop) { 662 | boundingValue = state.offsetTop - node.offsetTop; 663 | if (offset < boundingValue) { 664 | offset = boundingValue; 665 | } 666 | } 667 | if (this.state.boundBottom) { 668 | boundingValue += offsetParent.clientHeight; 669 | if (this.state.boundBox) { 670 | boundingValue -= node.offsetHeight; 671 | } 672 | if (offset > boundingValue) { 673 | offset = boundingValue; 674 | } 675 | } 676 | // Update top 677 | state.offsetTop = offset; 678 | } 679 | 680 | var constrain = this.props.constrain; 681 | var grid = this.props.grid; 682 | 683 | // Backwards-compatibility for snap to grid 684 | if (!constrain && Array.isArray(grid)) { 685 | var constrainOffset = function (offset, prev, snap) { 686 | var delta = offset - prev; 687 | if (Math.abs(delta) >= snap) { 688 | return prev + parseInt(delta / snap, 10) * snap; 689 | } 690 | return prev; 691 | }; 692 | constrain = function (pos) { 693 | return { 694 | left: constrainOffset(pos.left, pos.prevLeft, grid[0]), 695 | top: constrainOffset(pos.top, pos.prevTop, grid[1]) 696 | }; 697 | }; 698 | } 699 | 700 | // Constrain if function has been provided 701 | var positions; 702 | if (constrain) { 703 | // Constrain positions 704 | positions = constrain({ 705 | prevLeft: this.state.offsetLeft, 706 | prevTop: this.state.offsetTop, 707 | left: state.offsetLeft, 708 | top: state.offsetTop 709 | }); 710 | if (positions) { 711 | // Update left 712 | if ('left' in positions && !isNaN(positions.left)) { 713 | state.offsetLeft = positions.left; 714 | } 715 | // Update top 716 | if ('top' in positions && !isNaN(positions.top)) { 717 | state.offsetTop = positions.top; 718 | } 719 | } 720 | } 721 | 722 | // Save new state 723 | state.clientX = this.state.clientX + (state.offsetLeft - offsetLeft); 724 | state.clientY = this.state.clientY + (state.offsetTop - offsetTop); 725 | this.setState(state); 726 | 727 | // Call event handler 728 | this.props.onDrag(e, createUIEvent(this)); 729 | }, 730 | 731 | render: function () { 732 | var style = { 733 | top: this.state.offsetTop, 734 | left: this.state.offsetLeft 735 | }; 736 | 737 | // Set zIndex if currently dragging and prop has been provided 738 | if (this.state.dragging && !isNaN(this.props.zIndex)) { 739 | style.zIndex = this.props.zIndex; 740 | } 741 | 742 | var props = { 743 | style: style, 744 | className: 'react-draggable', 745 | 746 | onMouseDown: this._dragHandlers.mouse.start, 747 | onTouchStart: this._dragHandlers.touch.start, 748 | onMouseUp: this._dragHandlers.mouse.end, 749 | onTouchEnd: this._dragHandlers.touch.end, 750 | }; 751 | 752 | // Reuse the child provided 753 | // This makes it flexible to use whatever element is wanted (div, ul, etc) 754 | if (this.props.useChild) { 755 | return React.cloneElement(React.Children.only(this.props.children), props); 756 | } 757 | 758 | return React.DOM.div(props, this.props.children); 759 | }, 760 | 761 | _toPixels: function (value) { 762 | 763 | // Support percentages 764 | if (typeof value == 'string' && value.slice(-1) == '%') { 765 | return parseInt((+value.replace('%', '') / 100) * 766 | this.getDOMNode().offsetParent.clientWidth, 10) || 0; 767 | } 768 | 769 | // Invalid values become zero 770 | var i = parseInt(value, 10); 771 | if (isNaN(i) || !isFinite(i)) return 0; 772 | 773 | return i; 774 | } 775 | 776 | }); 777 | 778 | 779 | /***/ }, 780 | /* 1 */ 781 | /***/ function(module, exports) { 782 | 783 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 784 | 785 | /***/ } 786 | /******/ ]) 787 | }); 788 | ; 789 | //# sourceMappingURL=react-draggable.js.map -------------------------------------------------------------------------------- /dist/react-draggable.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap a719536962efd2b9a279","webpack:///./lib/draggable.js","webpack:///external \"React\""],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;ACVA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;;;;;;ACtCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,yDAAwD,gCAAgC;AACxF;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;AACH;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,SAAQ,iBAAiB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,aAAY,QAAQ;AACpB;AACA;AACA,IAAG;AACH;AACA,IAAG;AACH;AACA;AACA;;AAEA;AACA,aAAY,QAAQ;AACpB;AACA;AACA,IAAG;AACH;AACA,IAAG;AACH;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAS;AACT;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAS;AACT;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAqC,SAAS;AAC9C;AACA;AACA;AACA;AACA,WAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA2C,UAAU;AACrD;AACA;AACA;AACA;AACA,WAAU;AACV;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAuC,cAAc;AACrD,sDAAqD,WAAW;AAChE;AACA;AACA;AACA,UAAS;AACT;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAuC,IAAI;AAC3C;AACA;AACA;AACA;AACA,UAAS;AACT;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAqB;AACrB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAqB;AACrB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAqB;AACrB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA,eAAc,iFAAiF;AAC/F,eAAc;AACd;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA,IAAG;;AAEH;AACA,oCAAmC;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAK;;AAEL;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA,MAAK;;AAEL;AACA;;;AAGA;;AAEA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,IAAG;;AAEH;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,IAAG;;AAEH;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA,EAAC;;;;;;;AC/sBD,gD","file":"react-draggable.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"React\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"React\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ReactDraggable\"] = factory(require(\"React\"));\n\telse\n\t\troot[\"ReactDraggable\"] = factory(root[\"React\"]);\n})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {\nreturn \n\n\n/** WEBPACK FOOTER **\n ** webpack/universalModuleDefinition\n **/"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap a719536962efd2b9a279\n **/","'use strict';\n\nvar React = require('react/addons');\nvar emptyFunction = function () {};\n\nfunction updateBoundState (state, bound) {\n if (!bound) return state;\n bound = String(bound);\n var boundTop = !!~bound.indexOf('top');\n var boundRight = !!~bound.indexOf('right');\n var boundBottom = !!~bound.indexOf('bottom');\n var boundLeft = !!~bound.indexOf('left');\n var boundAll = !!~bound.indexOf('all') ||\n !(boundTop || boundRight || boundBottom || boundLeft);\n var boundBox = !~bound.indexOf('point');\n state.boundTop = boundAll || boundTop;\n state.boundRight = boundAll || boundRight;\n state.boundBottom = boundAll || boundBottom;\n state.boundLeft = boundAll || boundLeft;\n state.boundBox = boundBox;\n return state;\n};\n\nfunction createUIEvent(draggable) {\n return {\n position: {\n top: draggable.state.offsetTop,\n left: draggable.state.offsetLeft\n }\n };\n}\n\nfunction canDragY(draggable) {\n return draggable.props.axis === 'both' ||\n draggable.props.axis === 'y';\n}\n\nfunction canDragX(draggable) {\n return draggable.props.axis === 'both' ||\n draggable.props.axis === 'x';\n}\n\nfunction isFunction(func) {\n return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]'\n}\n\n// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc\nfunction findInArray(array, callback) {\n for (var i = 0, length = array.length, element = null; i < length, element = array[i]; i++) {\n if (callback.apply(callback, [element, i, array])) return element;\n }\n}\n\nfunction matchesSelector(el, selector) {\n var method = findInArray([\n 'matches',\n 'webkitMatchesSelector',\n 'mozMatchesSelector',\n 'msMatchesSelector',\n 'oMatchesSelector'\n ], function(method){\n return isFunction(el[method]);\n });\n\n return el[method].call(el, selector);\n}\n\n// look ::handleDragStart\n//function isMultiTouch(e) {\n// return e.touches && Array.isArray(e.touches) && e.touches.length > 1\n//}\n\n/**\n * simple abstraction for dragging events names\n * */\n var dragEventsFor = {\n touch: {\n start: 'touchstart',\n move: 'touchmove',\n end: 'touchend'\n },\n mouse: {\n start: 'mousedown',\n move: 'mousemove',\n end: 'mouseup'\n }\n};\n\n/**\n * get {clientX, clientY} positions of control\n * */\nfunction getControlPosition(e) {\n var position = (e.touches && e.touches[0]) || e;\n return {\n clientX: position.clientX,\n clientY: position.clientY\n }\n}\n\nfunction addEvent(el, event, handler) {\n if (!el) { return; }\n if (el.attachEvent) {\n el.attachEvent('on' + event, handler);\n } else if (el.addEventListener) {\n el.addEventListener(event, handler, true);\n } else {\n el['on' + event] = handler;\n }\n}\n\nfunction removeEvent(el, event, handler) {\n if (!el) { return; }\n if (el.detachEvent) {\n el.detachEvent('on' + event, handler);\n } else if (el.removeEventListener) {\n el.removeEventListener(event, handler, true);\n } else {\n el['on' + event] = null;\n }\n}\n\nmodule.exports = React.createClass({\n displayName: 'Draggable',\n mixins: [React.addons.PureRenderMixin],\n\n propTypes: {\n /**\n * `axis` determines which axis the draggable can move.\n *\n * 'both' allows movement horizontally and vertically.\n * 'x' limits movement to horizontal axis.\n * 'y' limits movement to vertical axis.\n *\n * Defaults to 'both'.\n */\n axis: React.PropTypes.oneOf(['both', 'x', 'y']),\n\n /**\n * `handle` specifies a selector to be used as the handle that initiates drag.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
\n *
Click me to drag
\n *
This is some other content
\n *
\n *
\n * );\n * }\n * });\n * ```\n */\n handle: React.PropTypes.string,\n\n /**\n * `cancel` specifies a selector to be used to prevent drag initialization.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return(\n * \n *
\n *
You can't drag from here
\n *
Dragging here works fine
\n *
\n *
\n * );\n * }\n * });\n * ```\n */\n cancel: React.PropTypes.string,\n\n /**\n * `bound` determines whether to bound the movement to the parent box.\n *\n * The property takes a list of space-separated strings. The Draggable\n * is bounded by the nearest DOMNode.offsetParent. To set the offset\n * parent, give it a position value other than 'static'.\n *\n * Optionally choose one or more bounds from:\n * 'top' bounds movement to the top edge of the parent box.\n * 'right' bounds movement to the right edge of the parent box.\n * 'bottom' bounds movement to the bottom edge of the parent box.\n * 'left' bounds movement to the left edge of the parent box.\n * 'all' bounds movement to all edges (default if not specified).\n *\n * Optionally choose one anchor from:\n * 'point' to constrain only the top-left corner.\n * 'box' to constrain the entire box (default if not specified).\n *\n * You may use more than one bound, e.g. 'top left point'. Set to a\n * falsy value to disable.\n *\n * Defaults to 'all box'.\n */\n bound: React.PropTypes.string,\n\n /**\n * `grid` specifies the x and y that dragging should snap to.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I snap to a 25 x 25 grid
\n *
\n * );\n * }\n * });\n * ```\n */\n grid: React.PropTypes.arrayOf(React.PropTypes.number),\n\n /**\n * `constrain` takes a function to constrain the dragging.\n *\n * Example:\n *\n * ```jsx\n * function constrain (snap) {\n * function constrainOffset (offset, prev) {\n * var delta = offset - prev;\n * if (Math.abs(delta) >= snap) {\n * return prev + (delta < 0 ? -snap : snap);\n * }\n * return prev;\n * }\n * return function (pos) {\n * return {\n * top: constrainOffset(pos.top, pos.prevTop),\n * left: constrainOffset(pos.left, pos.prevLeft)\n * };\n * };\n * }\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I snap to a 25 x 25 grid
\n *
\n * );\n * }\n * });\n * ```\n */\n constrain: React.PropTypes.func,\n\n /**\n * `start` specifies the x and y that the dragged item should start at\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I start with left: 25px; top: 25px;
\n *
\n * );\n * }\n * });\n * ```\n */\n start: React.PropTypes.object,\n\n /**\n * `zIndex` specifies the zIndex to use while dragging.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I have a zIndex
\n *
\n * );\n * }\n * });\n * ```\n */\n zIndex: React.PropTypes.number,\n\n /**\n * `useChild` determines whether to use the first child as root.\n *\n * If false, a div is created. This option is required if any children\n * have a ref.\n *\n * Defaults to true.\n */\n useChild: React.PropTypes.bool,\n\n /**\n * Called when dragging starts.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onStart: React.PropTypes.func,\n\n /**\n * Called while dragging.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onDrag: React.PropTypes.func,\n\n /**\n * Called when dragging stops.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onStop: React.PropTypes.func,\n\n /**\n * A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally.\n *\n */\n onMouseDown: React.PropTypes.func,\n onTouchStart: React.PropTypes.func,\n onMouseUp: React.PropTypes.func,\n onTouchEnd: React.PropTypes.func,\n },\n\n componentWillMount: function() {\n this._dragHandlers = {\n mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp},\n touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd}\n };\n },\n\n getDefaultProps: function () {\n return {\n axis: 'both',\n bound: null,\n handle: null,\n cancel: null,\n grid: null,\n start: {},\n zIndex: NaN,\n useChild: true,\n onStart: emptyFunction,\n onDrag: emptyFunction,\n onStop: emptyFunction,\n onMouseDown: emptyFunction,\n onMouseUp: emptyFunction,\n onTouchStart: emptyFunction,\n onTouchEnd: emptyFunction,\n };\n },\n\n getInitialState: function () {\n var state = {\n // Whether or not currently dragging\n dragging: false,\n\n // Pointer offset on screen\n clientX: 0, clientY: 0,\n\n // DOMNode offset relative to parent\n offsetLeft: this.props.start.x || 0, offsetTop: this.props.start.y || 0\n };\n\n updateBoundState(state, this.props.bound);\n\n return state;\n },\n\n componentWillReceiveProps: function (nextProps) {\n var state = updateBoundState({}, nextProps.bound);\n if (nextProps.start) {\n if (nextProps.start.x != null) {\n state.offsetLeft = nextProps.start.x || 0;\n }\n if (nextProps.start.y != null) {\n state.offsetTop = nextProps.start.y || 0;\n }\n }\n this.setState(state);\n },\n\n componentWillUnmount: function() {\n // Remove any leftover event handlers\n var bodyElement = this.getDOMNode().ownerDocument.body;\n removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move);\n removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end);\n removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move);\n removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end);\n },\n\n handleMouseDown: function(e) {\n if (typeof this.props.onMouseDown == 'function') {\n this.props.onMouseDown(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragStart(e, 'mouse');\n }\n },\n\n handleMouseMove: function(e) {\n this.handleDrag(e, 'mouse');\n },\n\n handleMouseUp: function(e) {\n if (typeof this.props.onMouseUp == 'function') {\n this.props.onMouseUp(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragEnd(e, 'mouse');\n }\n },\n\n handleTouchStart: function(e) {\n if (typeof this.props.onTouchStart == 'function') {\n this.props.onTouchStart(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragStart(e, 'touch');\n }\n },\n\n handleTouchMove: function(e) {\n this.handleDrag(e, 'touch');\n },\n\n handleTouchEnd: function(e) {\n if (typeof this.props.onTouchEnd == 'function') {\n this.props.onTouchEnd(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragEnd(e, 'touch');\n }\n },\n\n handleDragStart: function (e, device) {\n if (this.state.dragging) {\n return;\n }\n\n // todo: write right implementation to prevent multitouch drag\n // prevent multi-touch events\n // if (isMultiTouch(e)) {\n // this.handleDragEnd.apply(e, arguments);\n // return\n // }\n\n // Short circuit if handle or cancel prop was provided and selector doesn't match\n if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||\n (this.props.cancel && matchesSelector(e.target, this.props.cancel))) {\n return;\n }\n\n e.preventDefault();\n\n var dragPoint = getControlPosition(e);\n\n // Initiate dragging\n this.setState({\n dragging: device,\n clientX: dragPoint.clientX,\n clientY: dragPoint.clientY\n });\n\n // Call event handler\n this.props.onStart(e, createUIEvent(this));\n\n var bodyElement = this.getDOMNode().ownerDocument.body;\n\n // Add event handlers\n addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\n // Add dragging class to body element\n if (bodyElement) bodyElement.className += ' react-draggable-dragging';\n },\n\n handleDragEnd: function (e, device) {\n // Short circuit if not currently dragging\n if (!this.state.dragging || (this.state.dragging !== device)) {\n return;\n }\n\n e.preventDefault();\n\n // Turn off dragging\n this.setState({\n dragging: false\n });\n\n // Call event handler\n this.props.onStop(e, createUIEvent(this));\n\n\n var bodyElement = this.getDOMNode().ownerDocument.body;\n\n // Remove event handlers\n removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\n\n // Remove dragging class from body element\n if (bodyElement) {\n var className = bodyElement.className;\n bodyElement.className =\n className.replace(/(?:^|\\s+)react-draggable-dragging\\b/, ' ');\n }\n },\n\n handleDrag: function (e, device) {\n var dragPoint = getControlPosition(e);\n var offsetLeft = this._toPixels(this.state.offsetLeft);\n var offsetTop = this._toPixels(this.state.offsetTop);\n\n var state = {\n offsetLeft: offsetLeft,\n offsetTop: offsetTop\n };\n\n // Get parent DOM node\n var node = this.getDOMNode();\n var offsetParent = node.offsetParent;\n var offset, boundingValue;\n\n if (canDragX(this)) {\n // Calculate updated position\n offset = offsetLeft + dragPoint.clientX - this.state.clientX;\n\n // Bound movement to parent box\n if (this.state.boundLeft) {\n boundingValue = state.offsetLeft - node.offsetLeft;\n if (offset < boundingValue) {\n offset = boundingValue;\n }\n }\n if (this.state.boundRight) {\n boundingValue += offsetParent.clientWidth;\n if (this.state.boundBox) {\n boundingValue -= node.offsetWidth;\n }\n if (offset > boundingValue) {\n offset = boundingValue;\n }\n }\n // Update left\n state.offsetLeft = offset;\n }\n\n if (canDragY(this)) {\n // Calculate updated position\n offset = offsetTop + dragPoint.clientY - this.state.clientY;\n // Bound movement to parent box\n if (this.state.boundTop) {\n boundingValue = state.offsetTop - node.offsetTop;\n if (offset < boundingValue) {\n offset = boundingValue;\n }\n }\n if (this.state.boundBottom) {\n boundingValue += offsetParent.clientHeight;\n if (this.state.boundBox) {\n boundingValue -= node.offsetHeight;\n }\n if (offset > boundingValue) {\n offset = boundingValue;\n }\n }\n // Update top\n state.offsetTop = offset;\n }\n\n var constrain = this.props.constrain;\n var grid = this.props.grid;\n\n // Backwards-compatibility for snap to grid\n if (!constrain && Array.isArray(grid)) {\n var constrainOffset = function (offset, prev, snap) {\n var delta = offset - prev;\n if (Math.abs(delta) >= snap) {\n return prev + parseInt(delta / snap, 10) * snap;\n }\n return prev;\n };\n constrain = function (pos) {\n return {\n left: constrainOffset(pos.left, pos.prevLeft, grid[0]),\n top: constrainOffset(pos.top, pos.prevTop, grid[1])\n };\n };\n }\n\n // Constrain if function has been provided\n var positions;\n if (constrain) {\n // Constrain positions\n positions = constrain({\n prevLeft: this.state.offsetLeft,\n prevTop: this.state.offsetTop,\n left: state.offsetLeft,\n top: state.offsetTop\n });\n if (positions) {\n // Update left\n if ('left' in positions && !isNaN(positions.left)) {\n state.offsetLeft = positions.left;\n }\n // Update top\n if ('top' in positions && !isNaN(positions.top)) {\n state.offsetTop = positions.top;\n }\n }\n }\n\n // Save new state\n state.clientX = this.state.clientX + (state.offsetLeft - offsetLeft);\n state.clientY = this.state.clientY + (state.offsetTop - offsetTop);\n this.setState(state);\n\n // Call event handler\n this.props.onDrag(e, createUIEvent(this));\n },\n\n render: function () {\n var style = {\n top: this.state.offsetTop,\n left: this.state.offsetLeft\n };\n\n // Set zIndex if currently dragging and prop has been provided\n if (this.state.dragging && !isNaN(this.props.zIndex)) {\n style.zIndex = this.props.zIndex;\n }\n\n var props = {\n style: style,\n className: 'react-draggable',\n\n onMouseDown: this._dragHandlers.mouse.start,\n onTouchStart: this._dragHandlers.touch.start,\n onMouseUp: this._dragHandlers.mouse.end,\n onTouchEnd: this._dragHandlers.touch.end,\n };\n\n // Reuse the child provided\n // This makes it flexible to use whatever element is wanted (div, ul, etc)\n if (this.props.useChild) {\n return React.cloneElement(React.Children.only(this.props.children), props);\n }\n\n return React.DOM.div(props, this.props.children);\n },\n\n _toPixels: function (value) {\n\n // Support percentages\n if (typeof value == 'string' && value.slice(-1) == '%') {\n return parseInt((+value.replace('%', '') / 100) *\n this.getDOMNode().offsetParent.clientWidth, 10) || 0;\n }\n\n // Invalid values become zero\n var i = parseInt(value, 10);\n if (isNaN(i) || !isFinite(i)) return 0;\n\n return i;\n }\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./lib/draggable.js\n ** module id = 0\n ** module chunks = 0\n **/","module.exports = __WEBPACK_EXTERNAL_MODULE_1__;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** external \"React\"\n ** module id = 1\n ** module chunks = 0\n **/"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/react-draggable.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("React")):"function"==typeof define&&define.amd?define(["React"],e):"object"==typeof exports?exports.ReactDraggable=e(require("React")):t.ReactDraggable=e(t.React)}(this,function(t){return function(t){function e(n){if(o[n])return o[n].exports;var s=o[n]={exports:{},id:n,loaded:!1};return t[n].call(s.exports,s,s.exports,e),s.loaded=!0,s.exports}var o={};return e.m=t,e.c=o,e.p="",e(0)}([function(t,e,o){"use strict";function n(t,e){if(!e)return t;e=String(e);var o=!!~e.indexOf("top"),n=!!~e.indexOf("right"),s=!!~e.indexOf("bottom"),r=!!~e.indexOf("left"),i=!!~e.indexOf("all")||!(o||n||s||r),a=!~e.indexOf("point");return t.boundTop=i||o,t.boundRight=i||n,t.boundBottom=i||s,t.boundLeft=i||r,t.boundBox=a,t}function s(t){return{position:{top:t.state.offsetTop,left:t.state.offsetLeft}}}function r(t){return"both"===t.props.axis||"y"===t.props.axis}function i(t){return"both"===t.props.axis||"x"===t.props.axis}function a(t){return"function"==typeof t||"[object Function]"===Object.prototype.toString.call(t)}function u(t,e){for(var o=0,n=(t.length,null);n=t[o];o++)if(e.apply(e,[n,o,t]))return n}function p(t,e){var o=u(["matches","webkitMatchesSelector","mozMatchesSelector","msMatchesSelector","oMatchesSelector"],function(e){return a(t[e])});return t[o].call(t,e)}function f(t){var e=t.touches&&t.touches[0]||t;return{clientX:e.clientX,clientY:e.clientY}}function h(t,e,o){t&&(t.attachEvent?t.attachEvent("on"+e,o):t.addEventListener?t.addEventListener(e,o,!0):t["on"+e]=o)}function d(t,e,o){t&&(t.detachEvent?t.detachEvent("on"+e,o):t.removeEventListener?t.removeEventListener(e,o,!0):t["on"+e]=null)}var c=o(1),l=function(){},g={touch:{start:"touchstart",move:"touchmove",end:"touchend"},mouse:{start:"mousedown",move:"mousemove",end:"mouseup"}};t.exports=c.createClass({displayName:"Draggable",mixins:[c.addons.PureRenderMixin],propTypes:{axis:c.PropTypes.oneOf(["both","x","y"]),handle:c.PropTypes.string,cancel:c.PropTypes.string,bound:c.PropTypes.string,grid:c.PropTypes.arrayOf(c.PropTypes.number),constrain:c.PropTypes.func,start:c.PropTypes.object,zIndex:c.PropTypes.number,useChild:c.PropTypes.bool,onStart:c.PropTypes.func,onDrag:c.PropTypes.func,onStop:c.PropTypes.func,onMouseDown:c.PropTypes.func,onTouchStart:c.PropTypes.func,onMouseUp:c.PropTypes.func,onTouchEnd:c.PropTypes.func},componentWillMount:function(){this._dragHandlers={mouse:{start:this.handleMouseDown,move:this.handleMouseMove,end:this.handleMouseUp},touch:{start:this.handleTouchStart,move:this.handleTouchMove,end:this.handleTouchEnd}}},getDefaultProps:function(){return{axis:"both",bound:null,handle:null,cancel:null,grid:null,start:{},zIndex:0/0,useChild:!0,onStart:l,onDrag:l,onStop:l,onMouseDown:l,onMouseUp:l,onTouchStart:l,onTouchEnd:l}},getInitialState:function(){var t={dragging:!1,clientX:0,clientY:0,offsetLeft:this.props.start.x||0,offsetTop:this.props.start.y||0};return n(t,this.props.bound),t},componentWillReceiveProps:function(t){var e=n({},t.bound);t.start&&(null!=t.start.x&&(e.offsetLeft=t.start.x||0),null!=t.start.y&&(e.offsetTop=t.start.y||0)),this.setState(e)},componentWillUnmount:function(){var t=this.getDOMNode().ownerDocument.body;d(t,g.mouse.move,this._dragHandlers.mouse.move),d(t,g.mouse.end,this._dragHandlers.mouse.end),d(t,g.touch.move,this._dragHandlers.touch.move),d(t,g.touch.end,this._dragHandlers.touch.end)},handleMouseDown:function(t){"function"==typeof this.props.onMouseDown&&this.props.onMouseDown(t),t.defaultPrevented||this.handleDragStart(t,"mouse")},handleMouseMove:function(t){this.handleDrag(t,"mouse")},handleMouseUp:function(t){"function"==typeof this.props.onMouseUp&&this.props.onMouseUp(t),t.defaultPrevented||this.handleDragEnd(t,"mouse")},handleTouchStart:function(t){"function"==typeof this.props.onTouchStart&&this.props.onTouchStart(t),t.defaultPrevented||this.handleDragStart(t,"touch")},handleTouchMove:function(t){this.handleDrag(t,"touch")},handleTouchEnd:function(t){"function"==typeof this.props.onTouchEnd&&this.props.onTouchEnd(t),t.defaultPrevented||this.handleDragEnd(t,"touch")},handleDragStart:function(t,e){if(!this.state.dragging&&!(this.props.handle&&!p(t.target,this.props.handle)||this.props.cancel&&p(t.target,this.props.cancel))){t.preventDefault();var o=f(t);this.setState({dragging:e,clientX:o.clientX,clientY:o.clientY}),this.props.onStart(t,s(this));var n=this.getDOMNode().ownerDocument.body;h(n,g[e].move,this._dragHandlers[e].move),h(n,g[e].end,this._dragHandlers[e].end),n&&(n.className+=" react-draggable-dragging")}},handleDragEnd:function(t,e){if(this.state.dragging&&this.state.dragging===e){t.preventDefault(),this.setState({dragging:!1}),this.props.onStop(t,s(this));var o=this.getDOMNode().ownerDocument.body;if(d(o,g[e].move,this._dragHandlers[e].move),d(o,g[e].end,this._dragHandlers[e].end),o){var n=o.className;o.className=n.replace(/(?:^|\s+)react-draggable-dragging\b/," ")}}},handleDrag:function(t){var e,o,n=f(t),a=this._toPixels(this.state.offsetLeft),u=this._toPixels(this.state.offsetTop),p={offsetLeft:a,offsetTop:u},h=this.getDOMNode(),d=h.offsetParent;i(this)&&(e=a+n.clientX-this.state.clientX,this.state.boundLeft&&(o=p.offsetLeft-h.offsetLeft,o>e&&(e=o)),this.state.boundRight&&(o+=d.clientWidth,this.state.boundBox&&(o-=h.offsetWidth),e>o&&(e=o)),p.offsetLeft=e),r(this)&&(e=u+n.clientY-this.state.clientY,this.state.boundTop&&(o=p.offsetTop-h.offsetTop,o>e&&(e=o)),this.state.boundBottom&&(o+=d.clientHeight,this.state.boundBox&&(o-=h.offsetHeight),e>o&&(e=o)),p.offsetTop=e);var c=this.props.constrain,l=this.props.grid;if(!c&&Array.isArray(l)){var g=function(t,e,o){var n=t-e;return Math.abs(n)>=o?e+parseInt(n/o,10)*o:e};c=function(t){return{left:g(t.left,t.prevLeft,l[0]),top:g(t.top,t.prevTop,l[1])}}}var v;c&&(v=c({prevLeft:this.state.offsetLeft,prevTop:this.state.offsetTop,left:p.offsetLeft,top:p.offsetTop}),v&&("left"in v&&!isNaN(v.left)&&(p.offsetLeft=v.left),"top"in v&&!isNaN(v.top)&&(p.offsetTop=v.top))),p.clientX=this.state.clientX+(p.offsetLeft-a),p.clientY=this.state.clientY+(p.offsetTop-u),this.setState(p),this.props.onDrag(t,s(this))},render:function(){var t={top:this.state.offsetTop,left:this.state.offsetLeft};this.state.dragging&&!isNaN(this.props.zIndex)&&(t.zIndex=this.props.zIndex);var e={style:t,className:"react-draggable",onMouseDown:this._dragHandlers.mouse.start,onTouchStart:this._dragHandlers.touch.start,onMouseUp:this._dragHandlers.mouse.end,onTouchEnd:this._dragHandlers.touch.end};return this.props.useChild?c.cloneElement(c.Children.only(this.props.children),e):c.DOM.div(e,this.props.children)},_toPixels:function(t){if("string"==typeof t&&"%"==t.slice(-1))return parseInt(+t.replace("%","")/100*this.getDOMNode().offsetParent.clientWidth,10)||0;var e=parseInt(t,10);return isNaN(e)||!isFinite(e)?0:e}})},function(e){e.exports=t}])}); 2 | //# sourceMappingURL=react-draggable.min.js.map -------------------------------------------------------------------------------- /dist/react-draggable.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///react-draggable.min.js","webpack:///webpack/bootstrap a38ef75fd10fe5ef1bcc","webpack:///./lib/draggable.js","webpack:///external \"React\""],"names":["root","factory","exports","module","require","define","amd","this","__WEBPACK_EXTERNAL_MODULE_1__","modules","__webpack_require__","moduleId","installedModules","id","loaded","call","m","c","p","updateBoundState","state","bound","String","boundTop","indexOf","boundRight","boundBottom","boundLeft","boundAll","boundBox","createUIEvent","draggable","position","top","offsetTop","left","offsetLeft","canDragY","props","axis","canDragX","isFunction","func","Object","prototype","toString","findInArray","array","callback","i","element","length","apply","matchesSelector","el","selector","method","getControlPosition","e","touches","clientX","clientY","addEvent","event","handler","attachEvent","addEventListener","removeEvent","detachEvent","removeEventListener","React","emptyFunction","dragEventsFor","touch","start","move","end","mouse","createClass","displayName","mixins","addons","PureRenderMixin","propTypes","PropTypes","oneOf","handle","string","cancel","grid","arrayOf","number","constrain","object","zIndex","useChild","bool","onStart","onDrag","onStop","onMouseDown","onTouchStart","onMouseUp","onTouchEnd","componentWillMount","_dragHandlers","handleMouseDown","handleMouseMove","handleMouseUp","handleTouchStart","handleTouchMove","handleTouchEnd","getDefaultProps","NaN","getInitialState","dragging","x","y","componentWillReceiveProps","nextProps","setState","componentWillUnmount","bodyElement","getDOMNode","ownerDocument","body","defaultPrevented","handleDragStart","handleDrag","handleDragEnd","device","target","preventDefault","dragPoint","className","replace","offset","boundingValue","_toPixels","node","offsetParent","clientWidth","offsetWidth","clientHeight","offsetHeight","Array","isArray","constrainOffset","prev","snap","delta","Math","abs","parseInt","pos","prevLeft","prevTop","positions","isNaN","render","style","cloneElement","Children","only","children","DOM","div","value","slice","isFinite"],"mappings":"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","file":"react-draggable.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"React\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"React\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ReactDraggable\"] = factory(require(\"React\"));\n\telse\n\t\troot[\"ReactDraggable\"] = factory(root[\"React\"]);\n})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {\nreturn \n\n\n/** WEBPACK FOOTER **\n ** webpack/universalModuleDefinition\n **/","(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"React\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"React\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ReactDraggable\"] = factory(require(\"React\"));\n\telse\n\t\troot[\"ReactDraggable\"] = factory(root[\"React\"]);\n})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\tvar React = __webpack_require__(1);\n\tvar emptyFunction = function () {};\n\t\n\tfunction updateBoundState (state, bound) {\n\t if (!bound) return state;\n\t bound = String(bound);\n\t var boundTop = !!~bound.indexOf('top');\n\t var boundRight = !!~bound.indexOf('right');\n\t var boundBottom = !!~bound.indexOf('bottom');\n\t var boundLeft = !!~bound.indexOf('left');\n\t var boundAll = !!~bound.indexOf('all') ||\n\t !(boundTop || boundRight || boundBottom || boundLeft);\n\t var boundBox = !~bound.indexOf('point');\n\t state.boundTop = boundAll || boundTop;\n\t state.boundRight = boundAll || boundRight;\n\t state.boundBottom = boundAll || boundBottom;\n\t state.boundLeft = boundAll || boundLeft;\n\t state.boundBox = boundBox;\n\t return state;\n\t};\n\t\n\tfunction createUIEvent(draggable) {\n\t return {\n\t position: {\n\t top: draggable.state.offsetTop,\n\t left: draggable.state.offsetLeft\n\t }\n\t };\n\t}\n\t\n\tfunction canDragY(draggable) {\n\t return draggable.props.axis === 'both' ||\n\t draggable.props.axis === 'y';\n\t}\n\t\n\tfunction canDragX(draggable) {\n\t return draggable.props.axis === 'both' ||\n\t draggable.props.axis === 'x';\n\t}\n\t\n\tfunction isFunction(func) {\n\t return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]'\n\t}\n\t\n\t// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc\n\tfunction findInArray(array, callback) {\n\t for (var i = 0, length = array.length, element = null; i < length, element = array[i]; i++) {\n\t if (callback.apply(callback, [element, i, array])) return element;\n\t }\n\t}\n\t\n\tfunction matchesSelector(el, selector) {\n\t var method = findInArray([\n\t 'matches',\n\t 'webkitMatchesSelector',\n\t 'mozMatchesSelector',\n\t 'msMatchesSelector',\n\t 'oMatchesSelector'\n\t ], function(method){\n\t return isFunction(el[method]);\n\t });\n\t\n\t return el[method].call(el, selector);\n\t}\n\t\n\t// look ::handleDragStart\n\t//function isMultiTouch(e) {\n\t// return e.touches && Array.isArray(e.touches) && e.touches.length > 1\n\t//}\n\t\n\t/**\n\t * simple abstraction for dragging events names\n\t * */\n\t var dragEventsFor = {\n\t touch: {\n\t start: 'touchstart',\n\t move: 'touchmove',\n\t end: 'touchend'\n\t },\n\t mouse: {\n\t start: 'mousedown',\n\t move: 'mousemove',\n\t end: 'mouseup'\n\t }\n\t};\n\t\n\t/**\n\t * get {clientX, clientY} positions of control\n\t * */\n\tfunction getControlPosition(e) {\n\t var position = (e.touches && e.touches[0]) || e;\n\t return {\n\t clientX: position.clientX,\n\t clientY: position.clientY\n\t }\n\t}\n\t\n\tfunction addEvent(el, event, handler) {\n\t if (!el) { return; }\n\t if (el.attachEvent) {\n\t el.attachEvent('on' + event, handler);\n\t } else if (el.addEventListener) {\n\t el.addEventListener(event, handler, true);\n\t } else {\n\t el['on' + event] = handler;\n\t }\n\t}\n\t\n\tfunction removeEvent(el, event, handler) {\n\t if (!el) { return; }\n\t if (el.detachEvent) {\n\t el.detachEvent('on' + event, handler);\n\t } else if (el.removeEventListener) {\n\t el.removeEventListener(event, handler, true);\n\t } else {\n\t el['on' + event] = null;\n\t }\n\t}\n\t\n\tmodule.exports = React.createClass({\n\t displayName: 'Draggable',\n\t mixins: [React.addons.PureRenderMixin],\n\t\n\t propTypes: {\n\t /**\n\t * `axis` determines which axis the draggable can move.\n\t *\n\t * 'both' allows movement horizontally and vertically.\n\t * 'x' limits movement to horizontal axis.\n\t * 'y' limits movement to vertical axis.\n\t *\n\t * Defaults to 'both'.\n\t */\n\t axis: React.PropTypes.oneOf(['both', 'x', 'y']),\n\t\n\t /**\n\t * `handle` specifies a selector to be used as the handle that initiates drag.\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return (\n\t * \n\t *
\n\t *
Click me to drag
\n\t *
This is some other content
\n\t *
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t handle: React.PropTypes.string,\n\t\n\t /**\n\t * `cancel` specifies a selector to be used to prevent drag initialization.\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return(\n\t * \n\t *
\n\t *
You can't drag from here
\n\t *
Dragging here works fine
\n\t *
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t cancel: React.PropTypes.string,\n\t\n\t /**\n\t * `bound` determines whether to bound the movement to the parent box.\n\t *\n\t * The property takes a list of space-separated strings. The Draggable\n\t * is bounded by the nearest DOMNode.offsetParent. To set the offset\n\t * parent, give it a position value other than 'static'.\n\t *\n\t * Optionally choose one or more bounds from:\n\t * 'top' bounds movement to the top edge of the parent box.\n\t * 'right' bounds movement to the right edge of the parent box.\n\t * 'bottom' bounds movement to the bottom edge of the parent box.\n\t * 'left' bounds movement to the left edge of the parent box.\n\t * 'all' bounds movement to all edges (default if not specified).\n\t *\n\t * Optionally choose one anchor from:\n\t * 'point' to constrain only the top-left corner.\n\t * 'box' to constrain the entire box (default if not specified).\n\t *\n\t * You may use more than one bound, e.g. 'top left point'. Set to a\n\t * falsy value to disable.\n\t *\n\t * Defaults to 'all box'.\n\t */\n\t bound: React.PropTypes.string,\n\t\n\t /**\n\t * `grid` specifies the x and y that dragging should snap to.\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return (\n\t * \n\t *
I snap to a 25 x 25 grid
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t grid: React.PropTypes.arrayOf(React.PropTypes.number),\n\t\n\t /**\n\t * `constrain` takes a function to constrain the dragging.\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * function constrain (snap) {\n\t * function constrainOffset (offset, prev) {\n\t * var delta = offset - prev;\n\t * if (Math.abs(delta) >= snap) {\n\t * return prev + (delta < 0 ? -snap : snap);\n\t * }\n\t * return prev;\n\t * }\n\t * return function (pos) {\n\t * return {\n\t * top: constrainOffset(pos.top, pos.prevTop),\n\t * left: constrainOffset(pos.left, pos.prevLeft)\n\t * };\n\t * };\n\t * }\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return (\n\t * \n\t *
I snap to a 25 x 25 grid
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t constrain: React.PropTypes.func,\n\t\n\t /**\n\t * `start` specifies the x and y that the dragged item should start at\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return (\n\t * \n\t *
I start with left: 25px; top: 25px;
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t start: React.PropTypes.object,\n\t\n\t /**\n\t * `zIndex` specifies the zIndex to use while dragging.\n\t *\n\t * Example:\n\t *\n\t * ```jsx\n\t * var App = React.createClass({\n\t * render: function () {\n\t * return (\n\t * \n\t *
I have a zIndex
\n\t *
\n\t * );\n\t * }\n\t * });\n\t * ```\n\t */\n\t zIndex: React.PropTypes.number,\n\t\n\t /**\n\t * `useChild` determines whether to use the first child as root.\n\t *\n\t * If false, a div is created. This option is required if any children\n\t * have a ref.\n\t *\n\t * Defaults to true.\n\t */\n\t useChild: React.PropTypes.bool,\n\t\n\t /**\n\t * Called when dragging starts.\n\t *\n\t * Example:\n\t *\n\t * ```js\n\t * function (event, ui) {}\n\t * ```\n\t *\n\t * `event` is the Event that was triggered.\n\t * `ui` is an object:\n\t *\n\t * ```js\n\t * {\n\t * position: {top: 0, left: 0}\n\t * }\n\t * ```\n\t */\n\t onStart: React.PropTypes.func,\n\t\n\t /**\n\t * Called while dragging.\n\t *\n\t * Example:\n\t *\n\t * ```js\n\t * function (event, ui) {}\n\t * ```\n\t *\n\t * `event` is the Event that was triggered.\n\t * `ui` is an object:\n\t *\n\t * ```js\n\t * {\n\t * position: {top: 0, left: 0}\n\t * }\n\t * ```\n\t */\n\t onDrag: React.PropTypes.func,\n\t\n\t /**\n\t * Called when dragging stops.\n\t *\n\t * Example:\n\t *\n\t * ```js\n\t * function (event, ui) {}\n\t * ```\n\t *\n\t * `event` is the Event that was triggered.\n\t * `ui` is an object:\n\t *\n\t * ```js\n\t * {\n\t * position: {top: 0, left: 0}\n\t * }\n\t * ```\n\t */\n\t onStop: React.PropTypes.func,\n\t\n\t /**\n\t * A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally.\n\t *\n\t */\n\t onMouseDown: React.PropTypes.func,\n\t onTouchStart: React.PropTypes.func,\n\t onMouseUp: React.PropTypes.func,\n\t onTouchEnd: React.PropTypes.func,\n\t },\n\t\n\t componentWillMount: function() {\n\t this._dragHandlers = {\n\t mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp},\n\t touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd}\n\t };\n\t },\n\t\n\t getDefaultProps: function () {\n\t return {\n\t axis: 'both',\n\t bound: null,\n\t handle: null,\n\t cancel: null,\n\t grid: null,\n\t start: {},\n\t zIndex: NaN,\n\t useChild: true,\n\t onStart: emptyFunction,\n\t onDrag: emptyFunction,\n\t onStop: emptyFunction,\n\t onMouseDown: emptyFunction,\n\t onMouseUp: emptyFunction,\n\t onTouchStart: emptyFunction,\n\t onTouchEnd: emptyFunction,\n\t };\n\t },\n\t\n\t getInitialState: function () {\n\t var state = {\n\t // Whether or not currently dragging\n\t dragging: false,\n\t\n\t // Pointer offset on screen\n\t clientX: 0, clientY: 0,\n\t\n\t // DOMNode offset relative to parent\n\t offsetLeft: this.props.start.x || 0, offsetTop: this.props.start.y || 0\n\t };\n\t\n\t updateBoundState(state, this.props.bound);\n\t\n\t return state;\n\t },\n\t\n\t componentWillReceiveProps: function (nextProps) {\n\t var state = updateBoundState({}, nextProps.bound);\n\t if (nextProps.start) {\n\t if (nextProps.start.x != null) {\n\t state.offsetLeft = nextProps.start.x || 0;\n\t }\n\t if (nextProps.start.y != null) {\n\t state.offsetTop = nextProps.start.y || 0;\n\t }\n\t }\n\t this.setState(state);\n\t },\n\t\n\t componentWillUnmount: function() {\n\t // Remove any leftover event handlers\n\t var bodyElement = this.getDOMNode().ownerDocument.body;\n\t removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move);\n\t removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end);\n\t removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move);\n\t removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end);\n\t },\n\t\n\t handleMouseDown: function(e) {\n\t if (typeof this.props.onMouseDown == 'function') {\n\t this.props.onMouseDown(e);\n\t }\n\t if (!e.defaultPrevented) {\n\t this.handleDragStart(e, 'mouse');\n\t }\n\t },\n\t\n\t handleMouseMove: function(e) {\n\t this.handleDrag(e, 'mouse');\n\t },\n\t\n\t handleMouseUp: function(e) {\n\t if (typeof this.props.onMouseUp == 'function') {\n\t this.props.onMouseUp(e);\n\t }\n\t if (!e.defaultPrevented) {\n\t this.handleDragEnd(e, 'mouse');\n\t }\n\t },\n\t\n\t handleTouchStart: function(e) {\n\t if (typeof this.props.onTouchStart == 'function') {\n\t this.props.onTouchStart(e);\n\t }\n\t if (!e.defaultPrevented) {\n\t this.handleDragStart(e, 'touch');\n\t }\n\t },\n\t\n\t handleTouchMove: function(e) {\n\t this.handleDrag(e, 'touch');\n\t },\n\t\n\t handleTouchEnd: function(e) {\n\t if (typeof this.props.onTouchEnd == 'function') {\n\t this.props.onTouchEnd(e);\n\t }\n\t if (!e.defaultPrevented) {\n\t this.handleDragEnd(e, 'touch');\n\t }\n\t },\n\t\n\t handleDragStart: function (e, device) {\n\t if (this.state.dragging) {\n\t return;\n\t }\n\t\n\t // todo: write right implementation to prevent multitouch drag\n\t // prevent multi-touch events\n\t // if (isMultiTouch(e)) {\n\t // this.handleDragEnd.apply(e, arguments);\n\t // return\n\t // }\n\t\n\t // Short circuit if handle or cancel prop was provided and selector doesn't match\n\t if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||\n\t (this.props.cancel && matchesSelector(e.target, this.props.cancel))) {\n\t return;\n\t }\n\t\n\t e.preventDefault();\n\t\n\t var dragPoint = getControlPosition(e);\n\t\n\t // Initiate dragging\n\t this.setState({\n\t dragging: device,\n\t clientX: dragPoint.clientX,\n\t clientY: dragPoint.clientY\n\t });\n\t\n\t // Call event handler\n\t this.props.onStart(e, createUIEvent(this));\n\t\n\t var bodyElement = this.getDOMNode().ownerDocument.body;\n\t\n\t // Add event handlers\n\t addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n\t addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\t\n\t // Add dragging class to body element\n\t if (bodyElement) bodyElement.className += ' react-draggable-dragging';\n\t },\n\t\n\t handleDragEnd: function (e, device) {\n\t // Short circuit if not currently dragging\n\t if (!this.state.dragging || (this.state.dragging !== device)) {\n\t return;\n\t }\n\t\n\t e.preventDefault();\n\t\n\t // Turn off dragging\n\t this.setState({\n\t dragging: false\n\t });\n\t\n\t // Call event handler\n\t this.props.onStop(e, createUIEvent(this));\n\t\n\t\n\t var bodyElement = this.getDOMNode().ownerDocument.body;\n\t\n\t // Remove event handlers\n\t removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n\t removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\t\n\t\n\t // Remove dragging class from body element\n\t if (bodyElement) {\n\t var className = bodyElement.className;\n\t bodyElement.className =\n\t className.replace(/(?:^|\\s+)react-draggable-dragging\\b/, ' ');\n\t }\n\t },\n\t\n\t handleDrag: function (e, device) {\n\t var dragPoint = getControlPosition(e);\n\t var offsetLeft = this._toPixels(this.state.offsetLeft);\n\t var offsetTop = this._toPixels(this.state.offsetTop);\n\t\n\t var state = {\n\t offsetLeft: offsetLeft,\n\t offsetTop: offsetTop\n\t };\n\t\n\t // Get parent DOM node\n\t var node = this.getDOMNode();\n\t var offsetParent = node.offsetParent;\n\t var offset, boundingValue;\n\t\n\t if (canDragX(this)) {\n\t // Calculate updated position\n\t offset = offsetLeft + dragPoint.clientX - this.state.clientX;\n\t\n\t // Bound movement to parent box\n\t if (this.state.boundLeft) {\n\t boundingValue = state.offsetLeft - node.offsetLeft;\n\t if (offset < boundingValue) {\n\t offset = boundingValue;\n\t }\n\t }\n\t if (this.state.boundRight) {\n\t boundingValue += offsetParent.clientWidth;\n\t if (this.state.boundBox) {\n\t boundingValue -= node.offsetWidth;\n\t }\n\t if (offset > boundingValue) {\n\t offset = boundingValue;\n\t }\n\t }\n\t // Update left\n\t state.offsetLeft = offset;\n\t }\n\t\n\t if (canDragY(this)) {\n\t // Calculate updated position\n\t offset = offsetTop + dragPoint.clientY - this.state.clientY;\n\t // Bound movement to parent box\n\t if (this.state.boundTop) {\n\t boundingValue = state.offsetTop - node.offsetTop;\n\t if (offset < boundingValue) {\n\t offset = boundingValue;\n\t }\n\t }\n\t if (this.state.boundBottom) {\n\t boundingValue += offsetParent.clientHeight;\n\t if (this.state.boundBox) {\n\t boundingValue -= node.offsetHeight;\n\t }\n\t if (offset > boundingValue) {\n\t offset = boundingValue;\n\t }\n\t }\n\t // Update top\n\t state.offsetTop = offset;\n\t }\n\t\n\t var constrain = this.props.constrain;\n\t var grid = this.props.grid;\n\t\n\t // Backwards-compatibility for snap to grid\n\t if (!constrain && Array.isArray(grid)) {\n\t var constrainOffset = function (offset, prev, snap) {\n\t var delta = offset - prev;\n\t if (Math.abs(delta) >= snap) {\n\t return prev + parseInt(delta / snap, 10) * snap;\n\t }\n\t return prev;\n\t };\n\t constrain = function (pos) {\n\t return {\n\t left: constrainOffset(pos.left, pos.prevLeft, grid[0]),\n\t top: constrainOffset(pos.top, pos.prevTop, grid[1])\n\t };\n\t };\n\t }\n\t\n\t // Constrain if function has been provided\n\t var positions;\n\t if (constrain) {\n\t // Constrain positions\n\t positions = constrain({\n\t prevLeft: this.state.offsetLeft,\n\t prevTop: this.state.offsetTop,\n\t left: state.offsetLeft,\n\t top: state.offsetTop\n\t });\n\t if (positions) {\n\t // Update left\n\t if ('left' in positions && !isNaN(positions.left)) {\n\t state.offsetLeft = positions.left;\n\t }\n\t // Update top\n\t if ('top' in positions && !isNaN(positions.top)) {\n\t state.offsetTop = positions.top;\n\t }\n\t }\n\t }\n\t\n\t // Save new state\n\t state.clientX = this.state.clientX + (state.offsetLeft - offsetLeft);\n\t state.clientY = this.state.clientY + (state.offsetTop - offsetTop);\n\t this.setState(state);\n\t\n\t // Call event handler\n\t this.props.onDrag(e, createUIEvent(this));\n\t },\n\t\n\t render: function () {\n\t var style = {\n\t top: this.state.offsetTop,\n\t left: this.state.offsetLeft\n\t };\n\t\n\t // Set zIndex if currently dragging and prop has been provided\n\t if (this.state.dragging && !isNaN(this.props.zIndex)) {\n\t style.zIndex = this.props.zIndex;\n\t }\n\t\n\t var props = {\n\t style: style,\n\t className: 'react-draggable',\n\t\n\t onMouseDown: this._dragHandlers.mouse.start,\n\t onTouchStart: this._dragHandlers.touch.start,\n\t onMouseUp: this._dragHandlers.mouse.end,\n\t onTouchEnd: this._dragHandlers.touch.end,\n\t };\n\t\n\t // Reuse the child provided\n\t // This makes it flexible to use whatever element is wanted (div, ul, etc)\n\t if (this.props.useChild) {\n\t return React.cloneElement(React.Children.only(this.props.children), props);\n\t }\n\t\n\t return React.DOM.div(props, this.props.children);\n\t },\n\t\n\t _toPixels: function (value) {\n\t\n\t // Support percentages\n\t if (typeof value == 'string' && value.slice(-1) == '%') {\n\t return parseInt((+value.replace('%', '') / 100) *\n\t this.getDOMNode().offsetParent.clientWidth, 10) || 0;\n\t }\n\t\n\t // Invalid values become zero\n\t var i = parseInt(value, 10);\n\t if (isNaN(i) || !isFinite(i)) return 0;\n\t\n\t return i;\n\t }\n\t\n\t});\n\n\n/***/ },\n/* 1 */\n/***/ function(module, exports) {\n\n\tmodule.exports = __WEBPACK_EXTERNAL_MODULE_1__;\n\n/***/ }\n/******/ ])\n});\n;\n\n\n/** WEBPACK FOOTER **\n ** react-draggable.min.js\n **/"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap a38ef75fd10fe5ef1bcc\n **/","'use strict';\n\nvar React = require('react/addons');\nvar emptyFunction = function () {};\n\nfunction updateBoundState (state, bound) {\n if (!bound) return state;\n bound = String(bound);\n var boundTop = !!~bound.indexOf('top');\n var boundRight = !!~bound.indexOf('right');\n var boundBottom = !!~bound.indexOf('bottom');\n var boundLeft = !!~bound.indexOf('left');\n var boundAll = !!~bound.indexOf('all') ||\n !(boundTop || boundRight || boundBottom || boundLeft);\n var boundBox = !~bound.indexOf('point');\n state.boundTop = boundAll || boundTop;\n state.boundRight = boundAll || boundRight;\n state.boundBottom = boundAll || boundBottom;\n state.boundLeft = boundAll || boundLeft;\n state.boundBox = boundBox;\n return state;\n};\n\nfunction createUIEvent(draggable) {\n return {\n position: {\n top: draggable.state.offsetTop,\n left: draggable.state.offsetLeft\n }\n };\n}\n\nfunction canDragY(draggable) {\n return draggable.props.axis === 'both' ||\n draggable.props.axis === 'y';\n}\n\nfunction canDragX(draggable) {\n return draggable.props.axis === 'both' ||\n draggable.props.axis === 'x';\n}\n\nfunction isFunction(func) {\n return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]'\n}\n\n// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc\nfunction findInArray(array, callback) {\n for (var i = 0, length = array.length, element = null; i < length, element = array[i]; i++) {\n if (callback.apply(callback, [element, i, array])) return element;\n }\n}\n\nfunction matchesSelector(el, selector) {\n var method = findInArray([\n 'matches',\n 'webkitMatchesSelector',\n 'mozMatchesSelector',\n 'msMatchesSelector',\n 'oMatchesSelector'\n ], function(method){\n return isFunction(el[method]);\n });\n\n return el[method].call(el, selector);\n}\n\n// look ::handleDragStart\n//function isMultiTouch(e) {\n// return e.touches && Array.isArray(e.touches) && e.touches.length > 1\n//}\n\n/**\n * simple abstraction for dragging events names\n * */\n var dragEventsFor = {\n touch: {\n start: 'touchstart',\n move: 'touchmove',\n end: 'touchend'\n },\n mouse: {\n start: 'mousedown',\n move: 'mousemove',\n end: 'mouseup'\n }\n};\n\n/**\n * get {clientX, clientY} positions of control\n * */\nfunction getControlPosition(e) {\n var position = (e.touches && e.touches[0]) || e;\n return {\n clientX: position.clientX,\n clientY: position.clientY\n }\n}\n\nfunction addEvent(el, event, handler) {\n if (!el) { return; }\n if (el.attachEvent) {\n el.attachEvent('on' + event, handler);\n } else if (el.addEventListener) {\n el.addEventListener(event, handler, true);\n } else {\n el['on' + event] = handler;\n }\n}\n\nfunction removeEvent(el, event, handler) {\n if (!el) { return; }\n if (el.detachEvent) {\n el.detachEvent('on' + event, handler);\n } else if (el.removeEventListener) {\n el.removeEventListener(event, handler, true);\n } else {\n el['on' + event] = null;\n }\n}\n\nmodule.exports = React.createClass({\n displayName: 'Draggable',\n mixins: [React.addons.PureRenderMixin],\n\n propTypes: {\n /**\n * `axis` determines which axis the draggable can move.\n *\n * 'both' allows movement horizontally and vertically.\n * 'x' limits movement to horizontal axis.\n * 'y' limits movement to vertical axis.\n *\n * Defaults to 'both'.\n */\n axis: React.PropTypes.oneOf(['both', 'x', 'y']),\n\n /**\n * `handle` specifies a selector to be used as the handle that initiates drag.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
\n *
Click me to drag
\n *
This is some other content
\n *
\n *
\n * );\n * }\n * });\n * ```\n */\n handle: React.PropTypes.string,\n\n /**\n * `cancel` specifies a selector to be used to prevent drag initialization.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return(\n * \n *
\n *
You can't drag from here
\n *
Dragging here works fine
\n *
\n *
\n * );\n * }\n * });\n * ```\n */\n cancel: React.PropTypes.string,\n\n /**\n * `bound` determines whether to bound the movement to the parent box.\n *\n * The property takes a list of space-separated strings. The Draggable\n * is bounded by the nearest DOMNode.offsetParent. To set the offset\n * parent, give it a position value other than 'static'.\n *\n * Optionally choose one or more bounds from:\n * 'top' bounds movement to the top edge of the parent box.\n * 'right' bounds movement to the right edge of the parent box.\n * 'bottom' bounds movement to the bottom edge of the parent box.\n * 'left' bounds movement to the left edge of the parent box.\n * 'all' bounds movement to all edges (default if not specified).\n *\n * Optionally choose one anchor from:\n * 'point' to constrain only the top-left corner.\n * 'box' to constrain the entire box (default if not specified).\n *\n * You may use more than one bound, e.g. 'top left point'. Set to a\n * falsy value to disable.\n *\n * Defaults to 'all box'.\n */\n bound: React.PropTypes.string,\n\n /**\n * `grid` specifies the x and y that dragging should snap to.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I snap to a 25 x 25 grid
\n *
\n * );\n * }\n * });\n * ```\n */\n grid: React.PropTypes.arrayOf(React.PropTypes.number),\n\n /**\n * `constrain` takes a function to constrain the dragging.\n *\n * Example:\n *\n * ```jsx\n * function constrain (snap) {\n * function constrainOffset (offset, prev) {\n * var delta = offset - prev;\n * if (Math.abs(delta) >= snap) {\n * return prev + (delta < 0 ? -snap : snap);\n * }\n * return prev;\n * }\n * return function (pos) {\n * return {\n * top: constrainOffset(pos.top, pos.prevTop),\n * left: constrainOffset(pos.left, pos.prevLeft)\n * };\n * };\n * }\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I snap to a 25 x 25 grid
\n *
\n * );\n * }\n * });\n * ```\n */\n constrain: React.PropTypes.func,\n\n /**\n * `start` specifies the x and y that the dragged item should start at\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I start with left: 25px; top: 25px;
\n *
\n * );\n * }\n * });\n * ```\n */\n start: React.PropTypes.object,\n\n /**\n * `zIndex` specifies the zIndex to use while dragging.\n *\n * Example:\n *\n * ```jsx\n * var App = React.createClass({\n * render: function () {\n * return (\n * \n *
I have a zIndex
\n *
\n * );\n * }\n * });\n * ```\n */\n zIndex: React.PropTypes.number,\n\n /**\n * `useChild` determines whether to use the first child as root.\n *\n * If false, a div is created. This option is required if any children\n * have a ref.\n *\n * Defaults to true.\n */\n useChild: React.PropTypes.bool,\n\n /**\n * Called when dragging starts.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onStart: React.PropTypes.func,\n\n /**\n * Called while dragging.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onDrag: React.PropTypes.func,\n\n /**\n * Called when dragging stops.\n *\n * Example:\n *\n * ```js\n * function (event, ui) {}\n * ```\n *\n * `event` is the Event that was triggered.\n * `ui` is an object:\n *\n * ```js\n * {\n * position: {top: 0, left: 0}\n * }\n * ```\n */\n onStop: React.PropTypes.func,\n\n /**\n * A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally.\n *\n */\n onMouseDown: React.PropTypes.func,\n onTouchStart: React.PropTypes.func,\n onMouseUp: React.PropTypes.func,\n onTouchEnd: React.PropTypes.func,\n },\n\n componentWillMount: function() {\n this._dragHandlers = {\n mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp},\n touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd}\n };\n },\n\n getDefaultProps: function () {\n return {\n axis: 'both',\n bound: null,\n handle: null,\n cancel: null,\n grid: null,\n start: {},\n zIndex: NaN,\n useChild: true,\n onStart: emptyFunction,\n onDrag: emptyFunction,\n onStop: emptyFunction,\n onMouseDown: emptyFunction,\n onMouseUp: emptyFunction,\n onTouchStart: emptyFunction,\n onTouchEnd: emptyFunction,\n };\n },\n\n getInitialState: function () {\n var state = {\n // Whether or not currently dragging\n dragging: false,\n\n // Pointer offset on screen\n clientX: 0, clientY: 0,\n\n // DOMNode offset relative to parent\n offsetLeft: this.props.start.x || 0, offsetTop: this.props.start.y || 0\n };\n\n updateBoundState(state, this.props.bound);\n\n return state;\n },\n\n componentWillReceiveProps: function (nextProps) {\n var state = updateBoundState({}, nextProps.bound);\n if (nextProps.start) {\n if (nextProps.start.x != null) {\n state.offsetLeft = nextProps.start.x || 0;\n }\n if (nextProps.start.y != null) {\n state.offsetTop = nextProps.start.y || 0;\n }\n }\n this.setState(state);\n },\n\n componentWillUnmount: function() {\n // Remove any leftover event handlers\n var bodyElement = this.getDOMNode().ownerDocument.body;\n removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move);\n removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end);\n removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move);\n removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end);\n },\n\n handleMouseDown: function(e) {\n if (typeof this.props.onMouseDown == 'function') {\n this.props.onMouseDown(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragStart(e, 'mouse');\n }\n },\n\n handleMouseMove: function(e) {\n this.handleDrag(e, 'mouse');\n },\n\n handleMouseUp: function(e) {\n if (typeof this.props.onMouseUp == 'function') {\n this.props.onMouseUp(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragEnd(e, 'mouse');\n }\n },\n\n handleTouchStart: function(e) {\n if (typeof this.props.onTouchStart == 'function') {\n this.props.onTouchStart(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragStart(e, 'touch');\n }\n },\n\n handleTouchMove: function(e) {\n this.handleDrag(e, 'touch');\n },\n\n handleTouchEnd: function(e) {\n if (typeof this.props.onTouchEnd == 'function') {\n this.props.onTouchEnd(e);\n }\n if (!e.defaultPrevented) {\n this.handleDragEnd(e, 'touch');\n }\n },\n\n handleDragStart: function (e, device) {\n if (this.state.dragging) {\n return;\n }\n\n // todo: write right implementation to prevent multitouch drag\n // prevent multi-touch events\n // if (isMultiTouch(e)) {\n // this.handleDragEnd.apply(e, arguments);\n // return\n // }\n\n // Short circuit if handle or cancel prop was provided and selector doesn't match\n if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) ||\n (this.props.cancel && matchesSelector(e.target, this.props.cancel))) {\n return;\n }\n\n e.preventDefault();\n\n var dragPoint = getControlPosition(e);\n\n // Initiate dragging\n this.setState({\n dragging: device,\n clientX: dragPoint.clientX,\n clientY: dragPoint.clientY\n });\n\n // Call event handler\n this.props.onStart(e, createUIEvent(this));\n\n var bodyElement = this.getDOMNode().ownerDocument.body;\n\n // Add event handlers\n addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\n // Add dragging class to body element\n if (bodyElement) bodyElement.className += ' react-draggable-dragging';\n },\n\n handleDragEnd: function (e, device) {\n // Short circuit if not currently dragging\n if (!this.state.dragging || (this.state.dragging !== device)) {\n return;\n }\n\n e.preventDefault();\n\n // Turn off dragging\n this.setState({\n dragging: false\n });\n\n // Call event handler\n this.props.onStop(e, createUIEvent(this));\n\n\n var bodyElement = this.getDOMNode().ownerDocument.body;\n\n // Remove event handlers\n removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move);\n removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end);\n\n\n // Remove dragging class from body element\n if (bodyElement) {\n var className = bodyElement.className;\n bodyElement.className =\n className.replace(/(?:^|\\s+)react-draggable-dragging\\b/, ' ');\n }\n },\n\n handleDrag: function (e, device) {\n var dragPoint = getControlPosition(e);\n var offsetLeft = this._toPixels(this.state.offsetLeft);\n var offsetTop = this._toPixels(this.state.offsetTop);\n\n var state = {\n offsetLeft: offsetLeft,\n offsetTop: offsetTop\n };\n\n // Get parent DOM node\n var node = this.getDOMNode();\n var offsetParent = node.offsetParent;\n var offset, boundingValue;\n\n if (canDragX(this)) {\n // Calculate updated position\n offset = offsetLeft + dragPoint.clientX - this.state.clientX;\n\n // Bound movement to parent box\n if (this.state.boundLeft) {\n boundingValue = state.offsetLeft - node.offsetLeft;\n if (offset < boundingValue) {\n offset = boundingValue;\n }\n }\n if (this.state.boundRight) {\n boundingValue += offsetParent.clientWidth;\n if (this.state.boundBox) {\n boundingValue -= node.offsetWidth;\n }\n if (offset > boundingValue) {\n offset = boundingValue;\n }\n }\n // Update left\n state.offsetLeft = offset;\n }\n\n if (canDragY(this)) {\n // Calculate updated position\n offset = offsetTop + dragPoint.clientY - this.state.clientY;\n // Bound movement to parent box\n if (this.state.boundTop) {\n boundingValue = state.offsetTop - node.offsetTop;\n if (offset < boundingValue) {\n offset = boundingValue;\n }\n }\n if (this.state.boundBottom) {\n boundingValue += offsetParent.clientHeight;\n if (this.state.boundBox) {\n boundingValue -= node.offsetHeight;\n }\n if (offset > boundingValue) {\n offset = boundingValue;\n }\n }\n // Update top\n state.offsetTop = offset;\n }\n\n var constrain = this.props.constrain;\n var grid = this.props.grid;\n\n // Backwards-compatibility for snap to grid\n if (!constrain && Array.isArray(grid)) {\n var constrainOffset = function (offset, prev, snap) {\n var delta = offset - prev;\n if (Math.abs(delta) >= snap) {\n return prev + parseInt(delta / snap, 10) * snap;\n }\n return prev;\n };\n constrain = function (pos) {\n return {\n left: constrainOffset(pos.left, pos.prevLeft, grid[0]),\n top: constrainOffset(pos.top, pos.prevTop, grid[1])\n };\n };\n }\n\n // Constrain if function has been provided\n var positions;\n if (constrain) {\n // Constrain positions\n positions = constrain({\n prevLeft: this.state.offsetLeft,\n prevTop: this.state.offsetTop,\n left: state.offsetLeft,\n top: state.offsetTop\n });\n if (positions) {\n // Update left\n if ('left' in positions && !isNaN(positions.left)) {\n state.offsetLeft = positions.left;\n }\n // Update top\n if ('top' in positions && !isNaN(positions.top)) {\n state.offsetTop = positions.top;\n }\n }\n }\n\n // Save new state\n state.clientX = this.state.clientX + (state.offsetLeft - offsetLeft);\n state.clientY = this.state.clientY + (state.offsetTop - offsetTop);\n this.setState(state);\n\n // Call event handler\n this.props.onDrag(e, createUIEvent(this));\n },\n\n render: function () {\n var style = {\n top: this.state.offsetTop,\n left: this.state.offsetLeft\n };\n\n // Set zIndex if currently dragging and prop has been provided\n if (this.state.dragging && !isNaN(this.props.zIndex)) {\n style.zIndex = this.props.zIndex;\n }\n\n var props = {\n style: style,\n className: 'react-draggable',\n\n onMouseDown: this._dragHandlers.mouse.start,\n onTouchStart: this._dragHandlers.touch.start,\n onMouseUp: this._dragHandlers.mouse.end,\n onTouchEnd: this._dragHandlers.touch.end,\n };\n\n // Reuse the child provided\n // This makes it flexible to use whatever element is wanted (div, ul, etc)\n if (this.props.useChild) {\n return React.cloneElement(React.Children.only(this.props.children), props);\n }\n\n return React.DOM.div(props, this.props.children);\n },\n\n _toPixels: function (value) {\n\n // Support percentages\n if (typeof value == 'string' && value.slice(-1) == '%') {\n return parseInt((+value.replace('%', '') / 100) *\n this.getDOMNode().offsetParent.clientWidth, 10) || 0;\n }\n\n // Invalid values become zero\n var i = parseInt(value, 10);\n if (isNaN(i) || !isFinite(i)) return 0;\n\n return i;\n }\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./lib/draggable.js\n ** module id = 0\n ** module chunks = 0\n **/","module.exports = __WEBPACK_EXTERNAL_MODULE_1__;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** external \"React\"\n ** module id = 1\n ** module chunks = 0\n **/"],"sourceRoot":""} -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Draggable 6 | 7 | 55 | 56 | 57 |
58 | 59 | 60 | 61 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Jasmine configuration. 5 | */ 6 | 7 | module.exports = function (config) { 8 | config.set({ 9 | 10 | basePath: "", 11 | 12 | frameworks: ["jasmine"], 13 | 14 | files: [ 15 | "test/draggable_test.jsx" 16 | ], 17 | 18 | exclude: [], 19 | 20 | preprocessors: { 21 | "test/draggable_test.jsx": ["webpack"] 22 | }, 23 | 24 | webpack: { 25 | cache: true, 26 | module: { 27 | loaders: [{ 28 | test: /\.jsx$/, 29 | loader: "jsx-loader" 30 | }] 31 | } 32 | }, 33 | 34 | webpackServer: { 35 | stats: { 36 | colors: true 37 | } 38 | }, 39 | 40 | reporters: ["progress"], 41 | 42 | port: 9876, 43 | 44 | colors: true, 45 | 46 | logLevel: config.LOG_INFO, 47 | 48 | autoWatch: false, 49 | 50 | browsers: ["Chrome"], 51 | 52 | singleRun: false, 53 | 54 | plugins: [ 55 | require("karma-jasmine"), 56 | require("karma-chrome-launcher"), 57 | require("karma-webpack") 58 | ] 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /lib/draggable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactDOM = require('react-dom'); 5 | var PureRenderMixin = require('react-addons-pure-render-mixin'); 6 | 7 | var assign = require('object-assign'); 8 | var cx = require('classnames') 9 | 10 | var emptyFunction = function () {}; 11 | 12 | function updateBoundState (state, bound) { 13 | if (!bound) return state; 14 | bound = String(bound); 15 | var boundTop = !!~bound.indexOf('top'); 16 | var boundRight = !!~bound.indexOf('right'); 17 | var boundBottom = !!~bound.indexOf('bottom'); 18 | var boundLeft = !!~bound.indexOf('left'); 19 | var boundAll = !!~bound.indexOf('all') || 20 | !(boundTop || boundRight || boundBottom || boundLeft); 21 | var boundBox = !~bound.indexOf('point'); 22 | state.boundTop = boundAll || boundTop; 23 | state.boundRight = boundAll || boundRight; 24 | state.boundBottom = boundAll || boundBottom; 25 | state.boundLeft = boundAll || boundLeft; 26 | state.boundBox = boundBox; 27 | return state; 28 | }; 29 | 30 | function createUIEvent(draggable) { 31 | return { 32 | position: { 33 | top: draggable.state.offsetTop, 34 | left: draggable.state.offsetLeft 35 | } 36 | }; 37 | } 38 | 39 | function canDragY(draggable) { 40 | return draggable.props.axis === 'both' || 41 | draggable.props.axis === 'y'; 42 | } 43 | 44 | function canDragX(draggable) { 45 | return draggable.props.axis === 'both' || 46 | draggable.props.axis === 'x'; 47 | } 48 | 49 | function isFunction(func) { 50 | return typeof func === 'function' || Object.prototype.toString.call(func) === '[object Function]' 51 | } 52 | 53 | // @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc 54 | function findInArray(array, callback) { 55 | for (var i = 0, length = array.length, element = null; i < length, element = array[i]; i++) { 56 | if (callback.apply(callback, [element, i, array])) return element; 57 | } 58 | } 59 | 60 | function matchesSelector(el, selector) { 61 | var method = findInArray([ 62 | 'matches', 63 | 'webkitMatchesSelector', 64 | 'mozMatchesSelector', 65 | 'msMatchesSelector', 66 | 'oMatchesSelector' 67 | ], function(method){ 68 | return isFunction(el[method]); 69 | }); 70 | 71 | return el[method].call(el, selector); 72 | } 73 | 74 | // look ::handleDragStart 75 | //function isMultiTouch(e) { 76 | // return e.touches && Array.isArray(e.touches) && e.touches.length > 1 77 | //} 78 | 79 | /** 80 | * simple abstraction for dragging events names 81 | * */ 82 | var dragEventsFor = { 83 | touch: { 84 | start: 'touchstart', 85 | move: 'touchmove', 86 | end: 'touchend' 87 | }, 88 | mouse: { 89 | start: 'mousedown', 90 | move: 'mousemove', 91 | end: 'mouseup' 92 | } 93 | }; 94 | 95 | /** 96 | * get {clientX, clientY} positions of control 97 | * */ 98 | function getControlPosition(e) { 99 | var position = (e.touches && e.touches[0]) || e; 100 | return { 101 | clientX: position.clientX, 102 | clientY: position.clientY 103 | } 104 | } 105 | 106 | function addEvent(el, event, handler) { 107 | if (!el) { return; } 108 | if (el.attachEvent) { 109 | el.attachEvent('on' + event, handler); 110 | } else if (el.addEventListener) { 111 | el.addEventListener(event, handler, true); 112 | } else { 113 | el['on' + event] = handler; 114 | } 115 | } 116 | 117 | function removeEvent(el, event, handler) { 118 | if (!el) { return; } 119 | if (el.detachEvent) { 120 | el.detachEvent('on' + event, handler); 121 | } else if (el.removeEventListener) { 122 | el.removeEventListener(event, handler, true); 123 | } else { 124 | el['on' + event] = null; 125 | } 126 | } 127 | 128 | module.exports = React.createClass({ 129 | displayName: 'Draggable', 130 | mixins: [PureRenderMixin], 131 | 132 | propTypes: { 133 | /** 134 | * `axis` determines which axis the draggable can move. 135 | * 136 | * 'both' allows movement horizontally and vertically. 137 | * 'x' limits movement to horizontal axis. 138 | * 'y' limits movement to vertical axis. 139 | * 140 | * Defaults to 'both'. 141 | */ 142 | axis: React.PropTypes.oneOf(['both', 'x', 'y']), 143 | 144 | /** 145 | * `handle` specifies a selector to be used as the handle that initiates drag. 146 | * 147 | * Example: 148 | * 149 | * ```jsx 150 | * var App = React.createClass({ 151 | * render: function () { 152 | * return ( 153 | * 154 | *
155 | *
Click me to drag
156 | *
This is some other content
157 | *
158 | *
159 | * ); 160 | * } 161 | * }); 162 | * ``` 163 | */ 164 | handle: React.PropTypes.string, 165 | 166 | /** 167 | * `cancel` specifies a selector to be used to prevent drag initialization. 168 | * 169 | * Example: 170 | * 171 | * ```jsx 172 | * var App = React.createClass({ 173 | * render: function () { 174 | * return( 175 | * 176 | *
177 | *
You can't drag from here
178 | *
Dragging here works fine
179 | *
180 | *
181 | * ); 182 | * } 183 | * }); 184 | * ``` 185 | */ 186 | cancel: React.PropTypes.string, 187 | 188 | /** 189 | * `bound` determines whether to bound the movement to the parent box. 190 | * 191 | * The property takes a list of space-separated strings. The Draggable 192 | * is bounded by the nearest DOMNode.offsetParent. To set the offset 193 | * parent, give it a position value other than 'static'. 194 | * 195 | * Optionally choose one or more bounds from: 196 | * 'top' bounds movement to the top edge of the parent box. 197 | * 'right' bounds movement to the right edge of the parent box. 198 | * 'bottom' bounds movement to the bottom edge of the parent box. 199 | * 'left' bounds movement to the left edge of the parent box. 200 | * 'all' bounds movement to all edges (default if not specified). 201 | * 202 | * Optionally choose one anchor from: 203 | * 'point' to constrain only the top-left corner. 204 | * 'box' to constrain the entire box (default if not specified). 205 | * 206 | * You may use more than one bound, e.g. 'top left point'. Set to a 207 | * falsy value to disable. 208 | * 209 | * Defaults to 'all box'. 210 | */ 211 | bound: React.PropTypes.string, 212 | 213 | /** 214 | * `grid` specifies the x and y that dragging should snap to. 215 | * 216 | * Example: 217 | * 218 | * ```jsx 219 | * var App = React.createClass({ 220 | * render: function () { 221 | * return ( 222 | * 223 | *
I snap to a 25 x 25 grid
224 | *
225 | * ); 226 | * } 227 | * }); 228 | * ``` 229 | */ 230 | grid: React.PropTypes.arrayOf(React.PropTypes.number), 231 | 232 | /** 233 | * `constrain` takes a function to constrain the dragging. 234 | * 235 | * Example: 236 | * 237 | * ```jsx 238 | * function constrain (snap) { 239 | * function constrainOffset (offset, prev) { 240 | * var delta = offset - prev; 241 | * if (Math.abs(delta) >= snap) { 242 | * return prev + (delta < 0 ? -snap : snap); 243 | * } 244 | * return prev; 245 | * } 246 | * return function (pos) { 247 | * return { 248 | * top: constrainOffset(pos.top, pos.prevTop), 249 | * left: constrainOffset(pos.left, pos.prevLeft) 250 | * }; 251 | * }; 252 | * } 253 | * var App = React.createClass({ 254 | * render: function () { 255 | * return ( 256 | * 257 | *
I snap to a 25 x 25 grid
258 | *
259 | * ); 260 | * } 261 | * }); 262 | * ``` 263 | */ 264 | constrain: React.PropTypes.func, 265 | 266 | /** 267 | * `start` specifies the x and y that the dragged item should start at 268 | * 269 | * Example: 270 | * 271 | * ```jsx 272 | * var App = React.createClass({ 273 | * render: function () { 274 | * return ( 275 | * 276 | *
I start with left: 25px; top: 25px;
277 | *
278 | * ); 279 | * } 280 | * }); 281 | * ``` 282 | */ 283 | start: React.PropTypes.object, 284 | 285 | /** 286 | * `zIndex` specifies the zIndex to use while dragging. 287 | * 288 | * Example: 289 | * 290 | * ```jsx 291 | * var App = React.createClass({ 292 | * render: function () { 293 | * return ( 294 | * 295 | *
I have a zIndex
296 | *
297 | * ); 298 | * } 299 | * }); 300 | * ``` 301 | */ 302 | zIndex: React.PropTypes.number, 303 | 304 | /** 305 | * `useChild` determines whether to use the first child as root. 306 | * 307 | * If false, a div is created. This option is required if any children 308 | * have a ref. 309 | * 310 | * Defaults to true. 311 | */ 312 | useChild: React.PropTypes.bool, 313 | 314 | /** 315 | * Called when dragging starts. 316 | * 317 | * Example: 318 | * 319 | * ```js 320 | * function (event, ui) {} 321 | * ``` 322 | * 323 | * `event` is the Event that was triggered. 324 | * `ui` is an object: 325 | * 326 | * ```js 327 | * { 328 | * position: {top: 0, left: 0} 329 | * } 330 | * ``` 331 | */ 332 | onStart: React.PropTypes.func, 333 | 334 | /** 335 | * Called while dragging. 336 | * 337 | * Example: 338 | * 339 | * ```js 340 | * function (event, ui) {} 341 | * ``` 342 | * 343 | * `event` is the Event that was triggered. 344 | * `ui` is an object: 345 | * 346 | * ```js 347 | * { 348 | * position: {top: 0, left: 0} 349 | * } 350 | * ``` 351 | */ 352 | onDrag: React.PropTypes.func, 353 | 354 | /** 355 | * Called when dragging stops. 356 | * 357 | * Example: 358 | * 359 | * ```js 360 | * function (event, ui) {} 361 | * ``` 362 | * 363 | * `event` is the Event that was triggered. 364 | * `ui` is an object: 365 | * 366 | * ```js 367 | * { 368 | * position: {top: 0, left: 0} 369 | * } 370 | * ``` 371 | */ 372 | onStop: React.PropTypes.func, 373 | 374 | /** 375 | * A workaround option which can be passed if these event handlers need to be accessed, since they're always handled internally. 376 | * 377 | */ 378 | onMouseDown: React.PropTypes.func, 379 | onTouchStart: React.PropTypes.func, 380 | onMouseUp: React.PropTypes.func, 381 | onTouchEnd: React.PropTypes.func, 382 | }, 383 | 384 | componentWillMount: function() { 385 | this._dragHandlers = { 386 | mouse: {start: this.handleMouseDown, move: this.handleMouseMove, end: this.handleMouseUp}, 387 | touch: {start: this.handleTouchStart, move: this.handleTouchMove, end: this.handleTouchEnd} 388 | }; 389 | }, 390 | 391 | getDefaultProps: function () { 392 | return { 393 | axis: 'both', 394 | bound: null, 395 | handle: null, 396 | cancel: null, 397 | grid: null, 398 | start: {}, 399 | zIndex: NaN, 400 | useChild: true, 401 | onStart: emptyFunction, 402 | onDrag: emptyFunction, 403 | onStop: emptyFunction, 404 | onMouseDown: emptyFunction, 405 | onMouseUp: emptyFunction, 406 | onTouchStart: emptyFunction, 407 | onTouchEnd: emptyFunction, 408 | }; 409 | }, 410 | 411 | getInitialState: function () { 412 | var state = { 413 | // Whether or not currently dragging 414 | dragging: false, 415 | 416 | // Pointer offset on screen 417 | clientX: 0, clientY: 0, 418 | 419 | // DOMNode offset relative to parent 420 | offsetLeft: this.props.start.x || 0, offsetTop: this.props.start.y || 0 421 | }; 422 | 423 | updateBoundState(state, this.props.bound); 424 | 425 | return state; 426 | }, 427 | 428 | componentWillReceiveProps: function (nextProps) { 429 | var state = updateBoundState({}, nextProps.bound); 430 | if (nextProps.start) { 431 | if (nextProps.start.x != null) { 432 | state.offsetLeft = nextProps.start.x || 0; 433 | } 434 | if (nextProps.start.y != null) { 435 | state.offsetTop = nextProps.start.y || 0; 436 | } 437 | } 438 | this.setState(state); 439 | }, 440 | 441 | componentWillUnmount: function() { 442 | // Remove any leftover event handlers 443 | var bodyElement = ReactDOM.findDOMNode(this).ownerDocument.body; 444 | removeEvent(bodyElement, dragEventsFor.mouse.move, this._dragHandlers.mouse.move); 445 | removeEvent(bodyElement, dragEventsFor.mouse.end, this._dragHandlers.mouse.end); 446 | removeEvent(bodyElement, dragEventsFor.touch.move, this._dragHandlers.touch.move); 447 | removeEvent(bodyElement, dragEventsFor.touch.end, this._dragHandlers.touch.end); 448 | }, 449 | 450 | handleMouseDown: function(e) { 451 | if (typeof this.props.onMouseDown == 'function') { 452 | this.props.onMouseDown(e); 453 | } 454 | if (!e.defaultPrevented) { 455 | this.handleDragStart(e, 'mouse'); 456 | } 457 | }, 458 | 459 | handleMouseMove: function(e) { 460 | this.handleDrag(e, 'mouse'); 461 | }, 462 | 463 | handleMouseUp: function(e) { 464 | if (typeof this.props.onMouseUp == 'function') { 465 | this.props.onMouseUp(e); 466 | } 467 | if (!e.defaultPrevented) { 468 | this.handleDragEnd(e, 'mouse'); 469 | } 470 | }, 471 | 472 | handleTouchStart: function(e) { 473 | if (typeof this.props.onTouchStart == 'function') { 474 | this.props.onTouchStart(e); 475 | } 476 | if (!e.defaultPrevented) { 477 | this.handleDragStart(e, 'touch'); 478 | } 479 | }, 480 | 481 | handleTouchMove: function(e) { 482 | this.handleDrag(e, 'touch'); 483 | }, 484 | 485 | handleTouchEnd: function(e) { 486 | if (typeof this.props.onTouchEnd == 'function') { 487 | this.props.onTouchEnd(e); 488 | } 489 | if (!e.defaultPrevented) { 490 | this.handleDragEnd(e, 'touch'); 491 | } 492 | }, 493 | 494 | handleDragStart: function (e, device) { 495 | if (this.state.dragging) { 496 | return; 497 | } 498 | 499 | // todo: write right implementation to prevent multitouch drag 500 | // prevent multi-touch events 501 | // if (isMultiTouch(e)) { 502 | // this.handleDragEnd.apply(e, arguments); 503 | // return 504 | // } 505 | 506 | // Short circuit if handle or cancel prop was provided and selector doesn't match 507 | if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) || 508 | (this.props.cancel && matchesSelector(e.target, this.props.cancel))) { 509 | return; 510 | } 511 | 512 | e.preventDefault(); 513 | 514 | var dragPoint = getControlPosition(e); 515 | 516 | // Initiate dragging 517 | this.setState({ 518 | dragging: device, 519 | clientX: dragPoint.clientX, 520 | clientY: dragPoint.clientY 521 | }); 522 | 523 | // Call event handler 524 | this.props.onStart(e, createUIEvent(this)); 525 | 526 | var bodyElement = ReactDOM.findDOMNode(this).ownerDocument.body; 527 | 528 | // Add event handlers 529 | addEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move); 530 | addEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end); 531 | 532 | // Add dragging class to body element 533 | if (bodyElement) { 534 | bodyElement.className = 535 | cx(bodyElement.className, 'react-draggable-dragging'); 536 | } 537 | }, 538 | 539 | handleDragEnd: function (e, device) { 540 | // Short circuit if not currently dragging 541 | if (!this.state.dragging || (this.state.dragging !== device)) { 542 | return; 543 | } 544 | 545 | e.preventDefault(); 546 | 547 | // Turn off dragging 548 | this.setState({ 549 | dragging: false 550 | }); 551 | 552 | // Call event handler 553 | this.props.onStop(e, createUIEvent(this)); 554 | 555 | 556 | var bodyElement = ReactDOM.findDOMNode(this).ownerDocument.body; 557 | 558 | // Remove event handlers 559 | removeEvent(bodyElement, dragEventsFor[device].move, this._dragHandlers[device].move); 560 | removeEvent(bodyElement, dragEventsFor[device].end, this._dragHandlers[device].end); 561 | 562 | 563 | // Remove dragging class from body element 564 | if (bodyElement) { 565 | bodyElement.className = cx(bodyElement.className, { 566 | 'react-draggable-dragging': false 567 | }); 568 | } 569 | }, 570 | 571 | handleDrag: function (e, device) { 572 | var dragPoint = getControlPosition(e); 573 | var offsetLeft = this._toPixels(this.state.offsetLeft); 574 | var offsetTop = this._toPixels(this.state.offsetTop); 575 | 576 | var state = { 577 | offsetLeft: offsetLeft, 578 | offsetTop: offsetTop 579 | }; 580 | 581 | // Get parent DOM node 582 | var node = ReactDOM.findDOMNode(this); 583 | var offsetParent = node.offsetParent; 584 | var offset, boundingValue; 585 | 586 | if (canDragX(this)) { 587 | // Calculate updated position 588 | offset = offsetLeft + dragPoint.clientX - this.state.clientX; 589 | 590 | // Bound movement to parent box 591 | if (this.state.boundLeft) { 592 | boundingValue = state.offsetLeft - node.offsetLeft; 593 | if (offset < boundingValue) { 594 | offset = boundingValue; 595 | } 596 | } 597 | if (this.state.boundRight) { 598 | boundingValue += offsetParent.clientWidth; 599 | if (this.state.boundBox) { 600 | boundingValue -= node.offsetWidth; 601 | } 602 | if (offset > boundingValue) { 603 | offset = boundingValue; 604 | } 605 | } 606 | // Update left 607 | state.offsetLeft = offset; 608 | } 609 | 610 | if (canDragY(this)) { 611 | // Calculate updated position 612 | offset = offsetTop + dragPoint.clientY - this.state.clientY; 613 | // Bound movement to parent box 614 | if (this.state.boundTop) { 615 | boundingValue = state.offsetTop - node.offsetTop; 616 | if (offset < boundingValue) { 617 | offset = boundingValue; 618 | } 619 | } 620 | if (this.state.boundBottom) { 621 | boundingValue += offsetParent.clientHeight; 622 | if (this.state.boundBox) { 623 | boundingValue -= node.offsetHeight; 624 | } 625 | if (offset > boundingValue) { 626 | offset = boundingValue; 627 | } 628 | } 629 | // Update top 630 | state.offsetTop = offset; 631 | } 632 | 633 | var constrain = this.props.constrain; 634 | var grid = this.props.grid; 635 | 636 | // Backwards-compatibility for snap to grid 637 | if (!constrain && Array.isArray(grid)) { 638 | var constrainOffset = function (offset, prev, snap) { 639 | var delta = offset - prev; 640 | if (Math.abs(delta) >= snap) { 641 | return prev + parseInt(delta / snap, 10) * snap; 642 | } 643 | return prev; 644 | }; 645 | constrain = function (pos) { 646 | return { 647 | left: constrainOffset(pos.left, pos.prevLeft, grid[0]), 648 | top: constrainOffset(pos.top, pos.prevTop, grid[1]) 649 | }; 650 | }; 651 | } 652 | 653 | // Constrain if function has been provided 654 | var positions; 655 | if (constrain) { 656 | // Constrain positions 657 | positions = constrain({ 658 | prevLeft: this.state.offsetLeft, 659 | prevTop: this.state.offsetTop, 660 | left: state.offsetLeft, 661 | top: state.offsetTop 662 | }); 663 | if (positions) { 664 | // Update left 665 | if ('left' in positions && !isNaN(positions.left)) { 666 | state.offsetLeft = positions.left; 667 | } 668 | // Update top 669 | if ('top' in positions && !isNaN(positions.top)) { 670 | state.offsetTop = positions.top; 671 | } 672 | } 673 | } 674 | 675 | // Save new state 676 | state.clientX = this.state.clientX + (state.offsetLeft - offsetLeft); 677 | state.clientY = this.state.clientY + (state.offsetTop - offsetTop); 678 | this.setState(state); 679 | 680 | // Call event handler 681 | this.props.onDrag(e, createUIEvent(this)); 682 | }, 683 | 684 | render: function () { 685 | var style = { 686 | top: this.state.offsetTop, 687 | left: this.state.offsetLeft 688 | }; 689 | 690 | // Set zIndex if currently dragging and prop has been provided 691 | if (this.state.dragging && !isNaN(this.props.zIndex)) { 692 | style.zIndex = this.props.zIndex; 693 | } 694 | 695 | var props = { 696 | style: style, 697 | className: 'react-draggable', 698 | 699 | onMouseDown: this._dragHandlers.mouse.start, 700 | onTouchStart: this._dragHandlers.touch.start, 701 | onMouseUp: this._dragHandlers.mouse.end, 702 | onTouchEnd: this._dragHandlers.touch.end, 703 | }; 704 | 705 | // Reuse the child provided 706 | // This makes it flexible to use whatever element is wanted (div, ul, etc) 707 | var child; 708 | if (this.props.useChild) { 709 | child = React.Children.only(this.props.children); 710 | 711 | // Manually merge class names and styles for React 0.13+ 712 | props.className = cx(child.props.className, props.className); 713 | assign(props.style, child.props.style); 714 | 715 | return React.cloneElement(child, props); 716 | } 717 | 718 | return React.DOM.div(props, this.props.children); 719 | }, 720 | 721 | _toPixels: function (value) { 722 | 723 | // Support percentages 724 | if (typeof value == 'string' && value.slice(-1) == '%') { 725 | return parseInt((+value.replace('%', '') / 100) * 726 | ReactDOM.findDOMNode(this).offsetParent.clientWidth, 10) || 0; 727 | } 728 | 729 | // Invalid values become zero 730 | var i = parseInt(value, 10); 731 | if (isNaN(i) || !isFinite(i)) return 0; 732 | 733 | return i; 734 | } 735 | 736 | }); 737 | -------------------------------------------------------------------------------- /lib/styles.css: -------------------------------------------------------------------------------- 1 | .react-draggable strong, 2 | .react-draggable-dragging, 3 | .react-draggable-dragging strong { 4 | -webkit-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | -o-user-select: none; 8 | user-select: none; 9 | } 10 | 11 | .react-draggable { 12 | position: relative; 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-draggable2", 3 | "version": "0.7.0-alpha1", 4 | "description": "React draggable component", 5 | "main": "lib/draggable.js", 6 | "scripts": { 7 | "test": "karma start --single-run", 8 | "prepublish": "webpack lib/draggable.js dist/react-draggable.js && webpack --optimize-minimize lib/draggable.js dist/react-draggable.min.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mikepb/react-draggable.git" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "draggable", 17 | "react-component" 18 | ], 19 | "author": "Matt Zabriskie", 20 | "contributors": [ 21 | { 22 | "name": "Michael Phan-Ba", 23 | "email": "michael@mikepb.com" 24 | } 25 | ], 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/mikepb/react-draggable/issues" 29 | }, 30 | "homepage": "https://github.com/mikepb/react-draggable", 31 | "devDependencies": { 32 | "jasmine": "^2.3.2", 33 | "jasmine-core": "^2.3.4", 34 | "jsx-loader": "*", 35 | "karma": "^0.13.3", 36 | "karma-chrome-launcher": "^0.2.0", 37 | "karma-cli": "^0.1.1", 38 | "karma-jasmine": "^0.3.6", 39 | "karma-webpack": "^1.7.0", 40 | "node-libs-browser": "^0.5.2", 41 | "react": "0.14.0-rc1", 42 | "react-dom": "^0.14.0-rc1", 43 | "react-tools": "^0.13.3", 44 | "webpack": "^1.12.2" 45 | }, 46 | "dependencies": { 47 | "classnames": "^2.1.5", 48 | "object-assign": "^4.0.1", 49 | "react-addons-pure-render-mixin": "^0.14.0-rc1", 50 | "react-addons-test-utils": "^0.14.0-rc1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/draggable_test.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOM = require('react-dom'); 3 | var TestUtils = require('react-addons-test-utils'); 4 | var Draggable = require('../lib/draggable'); 5 | 6 | describe('react-draggable', function () { 7 | describe('props', function () { 8 | it('should have default properties', function () { 9 | var drag = TestUtils.renderIntoDocument(
); 10 | 11 | expect(drag.props.axis).toEqual('both'); 12 | expect(drag.props.bound).toEqual(null); 13 | expect(drag.props.handle).toEqual(null); 14 | expect(drag.props.cancel).toEqual(null); 15 | expect(isNaN(drag.props.zIndex)).toEqual(true); 16 | expect(drag.props.useChild).toEqual(true); 17 | expect(typeof drag.props.onStart).toEqual('function'); 18 | expect(typeof drag.props.onDrag).toEqual('function'); 19 | expect(typeof drag.props.onStop).toEqual('function'); 20 | }); 21 | 22 | it('should honor props', function () { 23 | function handleStart() {} 24 | function handleDrag() {} 25 | function handleStop() {} 26 | 27 | var drag = TestUtils.renderIntoDocument( 28 | 38 |
39 |
40 |
41 |
42 | 43 | ); 44 | 45 | expect(drag.props.axis).toEqual('y'); 46 | expect(drag.props.bound).toEqual("true"); 47 | expect(drag.props.handle).toEqual('.handle'); 48 | expect(drag.props.cancel).toEqual('.cancel'); 49 | expect(drag.props.grid).toEqual([10, 10]); 50 | expect(drag.props.zIndex).toEqual(1000); 51 | expect(drag.props.onStart).toEqual(handleStart); 52 | expect(drag.props.onDrag).toEqual(handleDrag); 53 | expect(drag.props.onStop).toEqual(handleStop); 54 | }); 55 | 56 | it('should honor useChild prop', function () { 57 | var drag = TestUtils.renderIntoDocument( 58 | 59 | ); 60 | 61 | expect(ReactDOM.findDOMNode(drag).nodeName).toEqual('DIV'); 62 | }); 63 | 64 | it('should call onStart when dragging begins', function () { 65 | var called = false; 66 | var drag = TestUtils.renderIntoDocument( 67 | 68 |
69 | 70 | ); 71 | 72 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 73 | expect(called).toEqual(true); 74 | }); 75 | 76 | it('should call onStop when dragging ends', function () { 77 | var called = false; 78 | var drag = TestUtils.renderIntoDocument( 79 | 80 |
81 | 82 | ); 83 | 84 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 85 | TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); 86 | expect(called).toEqual(true); 87 | }); 88 | 89 | it('should call onStart when touch dragging begins', function () { 90 | var called = false; 91 | var drag = TestUtils.renderIntoDocument( 92 | 93 |
94 | 95 | ); 96 | 97 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 98 | expect(called).toEqual(true); 99 | }); 100 | 101 | it('should call onStop when touch dragging ends', function () { 102 | var called = false; 103 | var drag = TestUtils.renderIntoDocument( 104 | 105 |
106 | 107 | ); 108 | 109 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 110 | TestUtils.Simulate.touchEnd(ReactDOM.findDOMNode(drag)); 111 | expect(called).toEqual(true); 112 | }); 113 | 114 | 115 | it('should add react-draggable-dragging CSS class to body element when dragging', function () { 116 | var drag = TestUtils.renderIntoDocument( 117 | 118 |
119 | 120 | ); 121 | 122 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 123 | expect(document.body.className).toMatch(/\breact-draggable-dragging\b/); 124 | TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); 125 | }); 126 | }); 127 | 128 | describe('mouse interaction', function () { 129 | it('should initialize dragging onmousedown', function () { 130 | var drag = TestUtils.renderIntoDocument(
); 131 | 132 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 133 | expect(drag.state.dragging).toEqual('mouse'); 134 | }); 135 | 136 | it('should only initialize dragging onmousedown of handle', function () { 137 | var drag = TestUtils.renderIntoDocument( 138 | 139 |
140 |
Handle
141 |
Lorem ipsum...
142 |
143 |
144 | ); 145 | 146 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content')); 147 | expect(drag.state.dragging).toEqual(false); 148 | 149 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.handle')); 150 | expect(drag.state.dragging).toEqual('mouse'); 151 | }); 152 | 153 | it('should not initialize dragging onmousedown of cancel', function () { 154 | var drag = TestUtils.renderIntoDocument( 155 | 156 |
157 |
Cancel
158 |
Lorem ipsum...
159 |
160 |
161 | ); 162 | 163 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.cancel')); 164 | expect(drag.state.dragging).toEqual(false); 165 | 166 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content')); 167 | expect(drag.state.dragging).toEqual('mouse'); 168 | }); 169 | 170 | it('should discontinue dragging onmouseup', function () { 171 | var drag = TestUtils.renderIntoDocument(
); 172 | 173 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 174 | expect(drag.state.dragging).toEqual('mouse'); 175 | 176 | TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); 177 | expect(drag.state.dragging).toEqual(false); 178 | }); 179 | }); 180 | 181 | describe('touch interaction', function () { 182 | it('should initialize dragging ontouchstart', function () { 183 | var drag = TestUtils.renderIntoDocument(
); 184 | 185 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 186 | expect(drag.state.dragging).toEqual('touch'); 187 | }); 188 | 189 | it('should only initialize dragging ontouchstart of handle', function () { 190 | var drag = TestUtils.renderIntoDocument( 191 | 192 |
193 |
Handle
194 |
Lorem ipsum...
195 |
196 |
197 | ); 198 | 199 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag).querySelector('.content')); 200 | expect(drag.state.dragging).toEqual(false); 201 | 202 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag).querySelector('.handle')); 203 | expect(drag.state.dragging).toEqual('touch'); 204 | }); 205 | 206 | it('should not initialize dragging ontouchstart of cancel', function () { 207 | var drag = TestUtils.renderIntoDocument( 208 | 209 |
210 |
Cancel
211 |
Lorem ipsum...
212 |
213 |
214 | ); 215 | 216 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag).querySelector('.cancel')); 217 | expect(drag.state.dragging).toEqual(false); 218 | 219 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag).querySelector('.content')); 220 | expect(drag.state.dragging).toEqual('touch'); 221 | }); 222 | 223 | it('should discontinue dragging ontouchend', function () { 224 | var drag = TestUtils.renderIntoDocument(
); 225 | 226 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 227 | expect(drag.state.dragging).toEqual('touch'); 228 | 229 | TestUtils.Simulate.touchEnd(ReactDOM.findDOMNode(drag)); 230 | expect(drag.state.dragging).toEqual(false); 231 | }); 232 | }); 233 | 234 | describe('validation', function () { 235 | it('should result with invariant when there isn\'t any children', function () { 236 | var drag = (); 237 | 238 | var error = false; 239 | try { 240 | TestUtils.renderIntoDocument(drag); 241 | } catch (e) { 242 | error = true; 243 | } 244 | 245 | expect(error).toEqual(true); 246 | }); 247 | 248 | it('should result with invariant if there\'s more than a single child', function () { 249 | var drag = (
); 250 | 251 | var error = false; 252 | try { 253 | TestUtils.renderIntoDocument(drag); 254 | } catch (e) { 255 | error = true; 256 | } 257 | 258 | expect(error).toEqual(true); 259 | }); 260 | }); 261 | 262 | describe('mouse events', function () { 263 | it('should pass through onMouseDown', function () { 264 | var called = false; 265 | 266 | var drag = TestUtils.renderIntoDocument(
); 267 | 268 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 269 | expect(called).toEqual(true); 270 | }); 271 | 272 | it('should not drag if onMouseDown calls preventDefault', function () { 273 | var drag = TestUtils.renderIntoDocument(
); 274 | 275 | TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); 276 | expect(drag.state.dragging).toEqual(false); 277 | }); 278 | 279 | it('should pass through onMouseUp', function () { 280 | var called = false; 281 | 282 | var drag = TestUtils.renderIntoDocument(
); 283 | 284 | TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); 285 | expect(called).toEqual(true); 286 | }); 287 | }); 288 | 289 | describe('touch events', function() { 290 | it('should pass through onTouchStart', function () { 291 | var called = false; 292 | 293 | var drag = TestUtils.renderIntoDocument(
); 294 | 295 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 296 | expect(called).toEqual(true); 297 | }); 298 | 299 | it('should not drag if onTouchStart calls preventDefault', function () { 300 | var drag = TestUtils.renderIntoDocument(
); 301 | 302 | TestUtils.Simulate.touchStart(ReactDOM.findDOMNode(drag)); 303 | expect(drag.state.dragging).toEqual(false); 304 | }); 305 | 306 | 307 | it('should pass through onTouchEnd', function () { 308 | var called = false; 309 | 310 | var drag = TestUtils.renderIntoDocument(
); 311 | 312 | TestUtils.Simulate.touchEnd(ReactDOM.findDOMNode(drag)); 313 | expect(called).toEqual(true); 314 | }); 315 | }); 316 | 317 | }); 318 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Webpack configuration. 5 | */ 6 | 7 | module.exports = { 8 | output: { 9 | library: "ReactDraggable", 10 | libraryTarget: "umd" 11 | }, 12 | externals: { 13 | "react": "React", 14 | "react-dom": "ReactDOM" 15 | }, 16 | devtool: "source-map" 17 | }; 18 | --------------------------------------------------------------------------------