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(
+
+
+
+ );
+
+ 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(
+
+
+
+ );
+
+ 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) {