diff --git a/example/index.html b/example/index.html index 69b224cf..5b2e4529 100644 --- a/example/index.html +++ b/example/index.html @@ -146,7 +146,7 @@

React Draggable

- Drag here +
Drag here
You must click my handle to drag me
diff --git a/lib/DraggableCore.es6 b/lib/DraggableCore.es6 index 27e81a02..d1a20b56 100644 --- a/lib/DraggableCore.es6 +++ b/lib/DraggableCore.es6 @@ -1,6 +1,7 @@ // @flow import React, {PropTypes} from 'react'; -import {matchesSelector, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier, +import ReactDOM from 'react-dom'; +import {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier, removeUserSelectStyles, styleHacks} from './utils/domFns'; import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns'; import {dontSetMe} from './utils/shims'; @@ -187,8 +188,8 @@ export default class DraggableCore extends React.Component { // Short circuit if handle or cancel prop was provided and selector doesn't match. if (this.props.disabled || (!(e.target instanceof Node)) || - (this.props.handle && !matchesSelector(e.target, this.props.handle)) || - (this.props.cancel && matchesSelector(e.target, this.props.cancel))) { + (this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, ReactDOM.findDOMNode(this))) || + (this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, ReactDOM.findDOMNode(this)))) { return; } diff --git a/lib/utils/domFns.es6 b/lib/utils/domFns.es6 index f996429e..9afa67a5 100644 --- a/lib/utils/domFns.es6 +++ b/lib/utils/domFns.es6 @@ -23,6 +23,18 @@ export function matchesSelector(el: Node, selector: string): boolean { return el[matchesSelectorFunc].call(el, selector); } +// Works up the tree to the draggable itself attempting to match selector. +export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean { + let node = el; + do { + if (matchesSelector(node, selector)) return true; + if (node === baseNode) return false; + node = node.parentNode; + } while (node); + + return false; +} + export function addEvent(el: ?Node, event: string, handler: Function): void { if (!el) { return; } if (el.attachEvent) { diff --git a/specs/draggable.spec.jsx b/specs/draggable.spec.jsx index c0c741f8..e470837d 100644 --- a/specs/draggable.spec.jsx +++ b/specs/draggable.spec.jsx @@ -322,6 +322,20 @@ describe('react-draggable', function () { }); describe('interaction', function () { + + function mouseDownOn(drag, selector, shouldDrag) { + resetDragging(drag); + const node = ReactDOM.findDOMNode(drag).querySelector(selector); + if (!node) throw new Error(`Selector not found: ${selector}`); + TestUtils.Simulate.mouseDown(node); + expect(drag.state.dragging).toEqual(shouldDrag); + } + + function resetDragging(drag) { + TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); + expect(drag.state.dragging).toEqual(false); + } + it('should initialize dragging onmousedown', function () { drag = TestUtils.renderIntoDocument(
); @@ -339,11 +353,27 @@ describe('react-draggable', function () { ); - TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content')); - expect(drag.state.dragging).toEqual(false); + mouseDownOn(drag, '.content', false); + mouseDownOn(drag, '.handle', true); + }); - TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.handle')); - expect(drag.state.dragging).toEqual(true); + it('should only initialize dragging onmousedown of handle, even if children fire event', function () { + drag = TestUtils.renderIntoDocument( + +
+
+
Handle
+
+
Lorem ipsum...
+
+
+ ); + + mouseDownOn(drag, '.content', false); + mouseDownOn(drag, '.deep', true); + mouseDownOn(drag, '.handle > div', true); + mouseDownOn(drag, '.handle span', true); + mouseDownOn(drag, '.handle', true); }); it('should not initialize dragging onmousedown of cancel', function () { @@ -356,11 +386,27 @@ describe('react-draggable', function () { ); - TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.cancel')); - expect(drag.state.dragging).toEqual(false); + mouseDownOn(drag, '.cancel', false); + mouseDownOn(drag, '.content', true); + }); - TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content')); - expect(drag.state.dragging).toEqual(true); + it('should not initialize dragging onmousedown of handle, even if children fire event', function () { + drag = TestUtils.renderIntoDocument( + +
+
+
Cancel
+
+
Lorem ipsum...
+
+
+ ); + + mouseDownOn(drag, '.content', true); + mouseDownOn(drag, '.deep', false); + mouseDownOn(drag, '.cancel > div', false); + mouseDownOn(drag, '.cancel span', false); + mouseDownOn(drag, '.cancel', false); }); it('should discontinue dragging onmouseup', function () { @@ -369,8 +415,7 @@ describe('react-draggable', function () { TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag)); expect(drag.state.dragging).toEqual(true); - TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag)); - expect(drag.state.dragging).toEqual(false); + resetDragging(drag); }); it('should modulate position on scroll', function (done) {