Skip to content

Commit a6b4789

Browse files
committed
Properly handle events fired by children of handle or cancel selectors. Fixes #88
1 parent 028a391 commit a6b4789

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

example/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ <h1>React Draggable</h1>
146146
</Draggable>
147147
<Draggable handle="strong" {...dragHandlers}>
148148
<div className="box no-cursor">
149-
<strong className="cursor">Drag here</strong>
149+
<strong className="cursor"><div>Drag here</div></strong>
150150
<div>You must click my handle to drag me</div>
151151
</div>
152152
</Draggable>

lib/DraggableCore.es6

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import React, {PropTypes} from 'react';
3-
import {matchesSelector, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
3+
import ReactDOM from 'react-dom';
4+
import {matchesSelectorAndParentsTo, addEvent, removeEvent, addUserSelectStyles, getTouchIdentifier,
45
removeUserSelectStyles, styleHacks} from './utils/domFns';
56
import {createCoreData, getControlPosition, snapToGrid} from './utils/positionFns';
67
import {dontSetMe} from './utils/shims';
@@ -187,8 +188,8 @@ export default class DraggableCore extends React.Component {
187188
// Short circuit if handle or cancel prop was provided and selector doesn't match.
188189
if (this.props.disabled ||
189190
(!(e.target instanceof Node)) ||
190-
(this.props.handle && !matchesSelector(e.target, this.props.handle)) ||
191-
(this.props.cancel && matchesSelector(e.target, this.props.cancel))) {
191+
(this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, ReactDOM.findDOMNode(this))) ||
192+
(this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, ReactDOM.findDOMNode(this)))) {
192193
return;
193194
}
194195

lib/utils/domFns.es6

+12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ export function matchesSelector(el: Node, selector: string): boolean {
2323
return el[matchesSelectorFunc].call(el, selector);
2424
}
2525

26+
// Works up the tree to the draggable itself attempting to match selector.
27+
export function matchesSelectorAndParentsTo(el: Node, selector: string, baseNode: Node): boolean {
28+
let node = el;
29+
do {
30+
if (matchesSelector(node, selector)) return true;
31+
if (node === baseNode) return false;
32+
node = node.parentNode;
33+
} while (node);
34+
35+
return false;
36+
}
37+
2638
export function addEvent(el: ?Node, event: string, handler: Function): void {
2739
if (!el) { return; }
2840
if (el.attachEvent) {

specs/draggable.spec.jsx

+55-10
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,20 @@ describe('react-draggable', function () {
322322
});
323323

324324
describe('interaction', function () {
325+
326+
function mouseDownOn(drag, selector, shouldDrag) {
327+
resetDragging(drag);
328+
const node = ReactDOM.findDOMNode(drag).querySelector(selector);
329+
if (!node) throw new Error(`Selector not found: ${selector}`);
330+
TestUtils.Simulate.mouseDown(node);
331+
expect(drag.state.dragging).toEqual(shouldDrag);
332+
}
333+
334+
function resetDragging(drag) {
335+
TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag));
336+
expect(drag.state.dragging).toEqual(false);
337+
}
338+
325339
it('should initialize dragging onmousedown', function () {
326340
drag = TestUtils.renderIntoDocument(<Draggable><div/></Draggable>);
327341

@@ -339,11 +353,27 @@ describe('react-draggable', function () {
339353
</Draggable>
340354
);
341355

342-
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content'));
343-
expect(drag.state.dragging).toEqual(false);
356+
mouseDownOn(drag, '.content', false);
357+
mouseDownOn(drag, '.handle', true);
358+
});
344359

345-
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.handle'));
346-
expect(drag.state.dragging).toEqual(true);
360+
it('should only initialize dragging onmousedown of handle, even if children fire event', function () {
361+
drag = TestUtils.renderIntoDocument(
362+
<Draggable handle=".handle">
363+
<div>
364+
<div className="handle">
365+
<div><span><div className="deep">Handle</div></span></div>
366+
</div>
367+
<div className="content">Lorem ipsum...</div>
368+
</div>
369+
</Draggable>
370+
);
371+
372+
mouseDownOn(drag, '.content', false);
373+
mouseDownOn(drag, '.deep', true);
374+
mouseDownOn(drag, '.handle > div', true);
375+
mouseDownOn(drag, '.handle span', true);
376+
mouseDownOn(drag, '.handle', true);
347377
});
348378

349379
it('should not initialize dragging onmousedown of cancel', function () {
@@ -356,11 +386,27 @@ describe('react-draggable', function () {
356386
</Draggable>
357387
);
358388

359-
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.cancel'));
360-
expect(drag.state.dragging).toEqual(false);
389+
mouseDownOn(drag, '.cancel', false);
390+
mouseDownOn(drag, '.content', true);
391+
});
361392

362-
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag).querySelector('.content'));
363-
expect(drag.state.dragging).toEqual(true);
393+
it('should not initialize dragging onmousedown of handle, even if children fire event', function () {
394+
drag = TestUtils.renderIntoDocument(
395+
<Draggable cancel=".cancel">
396+
<div>
397+
<div className="cancel">
398+
<div><span><div className="deep">Cancel</div></span></div>
399+
</div>
400+
<div className="content">Lorem ipsum...</div>
401+
</div>
402+
</Draggable>
403+
);
404+
405+
mouseDownOn(drag, '.content', true);
406+
mouseDownOn(drag, '.deep', false);
407+
mouseDownOn(drag, '.cancel > div', false);
408+
mouseDownOn(drag, '.cancel span', false);
409+
mouseDownOn(drag, '.cancel', false);
364410
});
365411

366412
it('should discontinue dragging onmouseup', function () {
@@ -369,8 +415,7 @@ describe('react-draggable', function () {
369415
TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(drag));
370416
expect(drag.state.dragging).toEqual(true);
371417

372-
TestUtils.Simulate.mouseUp(ReactDOM.findDOMNode(drag));
373-
expect(drag.state.dragging).toEqual(false);
418+
resetDragging(drag);
374419
});
375420

376421
it('should modulate position on scroll', function (done) {

0 commit comments

Comments
 (0)