Skip to content

Commit e8024e4

Browse files
authored
Merge pull request #179 from mzabriskie/pr/173
Refactor #173
2 parents 7b2f4d5 + 6855c38 commit e8024e4

File tree

5 files changed

+92
-16
lines changed

5 files changed

+92
-16
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ grid: [number, number],
182182
// Example: '.handle'
183183
handle: string,
184184

185+
// If desired, you can provide your own offsetParent for drag calculations.
186+
// By default, we use the Draggable's offsetParent. This can be useful for elements
187+
// with odd display types or floats.
188+
offsetParent: HTMLElement,
189+
185190
// Called whenever the user mouses down. Called regardless of handle or
186191
// disabled status.
187192
onMouseDown: (e: MouseEvent) => void,
@@ -249,6 +254,7 @@ on itself and thus must have callbacks attached to be useful.
249254
cancel: string,
250255
disabled: boolean,
251256
enableUserSelectHack: boolean,
257+
offsetParent: HTMLElement,
252258
grid: [number, number],
253259
handle: string,
254260
onStart: DraggableEventHandler,

lib/DraggableCore.es6

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ export default class DraggableCore extends React.Component {
6666
*/
6767
enableUserSelectHack: PropTypes.bool,
6868

69+
/**
70+
* `offsetParent`, if set, uses the passed DOM node to compute drag offsets
71+
* instead of using the parent node.
72+
*/
73+
offsetParent: function(props, propName) {
74+
if (process.browser && props[propName] && props[propName].nodeType !== 1) {
75+
throw new Error('Draggable\'s offsetParent must be a DOM Node.');
76+
}
77+
},
78+
6979
/**
7080
* `grid` specifies the x and y that dragging should snap to.
7181
*/
@@ -152,6 +162,7 @@ export default class DraggableCore extends React.Component {
152162
cancel: null,
153163
disabled: false,
154164
enableUserSelectHack: true,
165+
offsetParent: null,
155166
handle: null,
156167
grid: null,
157168
transform: null,

lib/utils/domFns.es6

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ export function innerWidth(node: HTMLElement): number {
9595
}
9696

9797
// Get from offsetParent
98-
export function offsetXYFromParentOf(evt: {clientX: number, clientY: number}, node: HTMLElement & {offsetParent: HTMLElement}): ControlPosition {
99-
const offsetParent = node.offsetParent || document.body;
100-
const offsetParentRect = node.offsetParent === document.body ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();
98+
export function offsetXYFromParent(evt: {clientX: number, clientY: number}, offsetParent: ?HTMLElement): ControlPosition {
99+
if (!offsetParent) offsetParent = document.body;
100+
const isBody = offsetParent === offsetParent.ownerDocument.body;
101+
const offsetParentRect = isBody ? {left: 0, top: 0} : offsetParent.getBoundingClientRect();
101102

102103
const x = evt.clientX + offsetParent.scrollLeft - offsetParentRect.left;
103104
const y = evt.clientY + offsetParent.scrollTop - offsetParentRect.top;

lib/utils/positionFns.es6

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import {isNum, int} from './shims';
33
import ReactDOM from 'react-dom';
4-
import {getTouch, innerWidth, innerHeight, offsetXYFromParentOf, outerWidth, outerHeight} from './domFns';
4+
import {getTouch, innerWidth, innerHeight, offsetXYFromParent, outerWidth, outerHeight} from './domFns';
55

66
import type Draggable from '../Draggable';
77
import type {Bounds, ControlPosition, DraggableData} from './types';
@@ -66,7 +66,11 @@ export function canDragY(draggable: Draggable): boolean {
6666
export function getControlPosition(e: MouseEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition {
6767
const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null;
6868
if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch
69-
return offsetXYFromParentOf(touchObj || e, ReactDOM.findDOMNode(draggableCore));
69+
// User can provide an offsetParent if desired.
70+
const offsetParent = draggableCore.props.offsetParent ||
71+
ReactDOM.findDOMNode(draggableCore).offsetParent ||
72+
document.body;
73+
return offsetXYFromParent(touchObj || e, offsetParent);
7074
}
7175

7276
// Create an data object exposed by <DraggableCore>'s events

specs/draggable.spec.jsx

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -456,32 +456,74 @@ describe('react-draggable', function () {
456456
});
457457

458458
it('should modulate position on scroll', function (done) {
459-
// This test fails in karma under Chrome & Firefox, positioning quirks
460-
// FIXME: Why? Chrome reports 2x scrollTo, Phantom reports 0x, Firefox reports 1x as it should
461-
var is_ff = navigator.userAgent.toLowerCase().indexOf('Firefox') > -1;
462-
if (!is_ff) return done();
463-
464-
var dragCalled = false;
459+
let dragCalled = false;
465460

466461
function onDrag(e, coreEvent) {
467462
assert(coreEvent.deltaY === 500);
468463
dragCalled = true;
469464
}
470465
drag = TestUtils.renderIntoDocument(<Draggable onDrag={onDrag}><div/></Draggable>);
471-
var node = ReactDOM.findDOMNode(drag);
466+
const node = ReactDOM.findDOMNode(drag);
467+
468+
// Create a container we can scroll. I'm doing it this way so we can still access <Draggable>.
469+
// Enzyme (airbnb project) would make this a lot easier.
470+
const fragment = fragmentFromString(`
471+
<div style="overflow: auto; height: 100px;">
472+
<div style="height: 10000px; position: relative;">
473+
</div>
474+
</div>
475+
`);
476+
transplantNodeInto(node, fragment, (f) => f.children[0]);
472477

473478
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
474479
assert(drag.state.dragging === true);
475480

476-
document.body.style.height = '10000px';
477-
window.scrollTo(0, 500);
478-
TestUtils.Simulate.mouseUp(node, {clientX: 0, clientY: 0});
481+
// Scroll the inner container & trigger a scroll
482+
fragment.scrollTop = 500;
483+
mouseMove(0, 0);
484+
TestUtils.Simulate.mouseUp(node);
479485
setTimeout(function() {
486+
assert(drag.state.dragging === false);
480487
assert(dragCalled === true);
481-
assert(drag.state.clientY === 500);
488+
assert(drag.state.y === 500);
489+
// Cleanup
490+
document.body.removeChild(fragment);
482491
done();
483492
}, 50);
493+
484494
});
495+
496+
it('should respect offsetParent on nested div scroll', function (done) {
497+
let dragCalled = false;
498+
function onDrag(e, coreEvent) {
499+
dragCalled = true;
500+
// Because the offsetParent is the body, we technically haven't moved at all relative to it
501+
assert(coreEvent.deltaY === 0);
502+
}
503+
drag = TestUtils.renderIntoDocument(<Draggable onDrag={onDrag} offsetParent={document.body}><div/></Draggable>);
504+
const node = ReactDOM.findDOMNode(drag);
505+
506+
// Create a container we can scroll. I'm doing it this way so we can still access <Draggable>.
507+
// Enzyme (airbnb project) would make this a lot easier.
508+
const fragment = fragmentFromString(`
509+
<div style="overflow: auto; height: 100px;">
510+
<div style="height: 10000px; position: relative;">
511+
</div>
512+
</div>
513+
`);
514+
transplantNodeInto(node, fragment, (f) => f.children[0]);
515+
516+
TestUtils.Simulate.mouseDown(node, {clientX: 0, clientY: 0});
517+
fragment.scrollTop = 500;
518+
519+
mouseMove(0, 0);
520+
TestUtils.Simulate.mouseUp(node);
521+
setTimeout(function() {
522+
assert(dragCalled);
523+
// Cleanup
524+
document.body.removeChild(fragment);
525+
done();
526+
}, 50);
485527
});
486528

487529
describe('draggable callbacks', function () {
@@ -597,3 +639,15 @@ function simulateMovementFromTo(drag, fromX, fromY, toX, toY) {
597639
mouseMove(toX, toY);
598640
TestUtils.Simulate.mouseUp(node);
599641
}
642+
643+
function fragmentFromString(strHTML) {
644+
var temp = document.createElement('div');
645+
temp.innerHTML = strHTML;
646+
return temp.children[0];
647+
}
648+
649+
function transplantNodeInto(node, into, selector) {
650+
node.parentNode.removeChild(node);
651+
selector(into).appendChild(node);
652+
document.body.appendChild(into);
653+
}

0 commit comments

Comments
 (0)