Skip to content

Commit 2e8b4fe

Browse files
Fix bounds="selector" functionality when in a Shadow DOM tree. (#763)
* Use querySelector on root node instead of just ownerDocument for Shadow DOM compatibility. * Add tests.
1 parent 9e6afaa commit 2e8b4fe

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

lib/utils/positionFns.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ export function getBoundPosition(draggable: Draggable, x: number, y: number): [n
2222
if (bounds === 'parent') {
2323
boundNode = node.parentNode;
2424
} else {
25-
boundNode = ownerDocument.querySelector(bounds);
25+
// Flow assigns the wrong return type (Node) for getRootNode(),
26+
// so we cast it to one of the correct types (Element).
27+
// The others are Document and ShadowRoot.
28+
// All three implement querySelector() so it's safe to call.
29+
const rootNode = (((node.getRootNode()): any): Element);
30+
boundNode = rootNode.querySelector(bounds);
2631
}
32+
2733
if (!(boundNode instanceof ownerWindow.HTMLElement)) {
2834
throw new Error('Bounds selector "' + bounds + '" could not find an element.');
2935
}

specs/draggable.spec.jsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,129 @@ describe('react-draggable', function () {
807807

808808
});
809809

810+
it('should clip dragging to parent, with bounds set to "parent"', function(done){
811+
function onDrag(event, data) {
812+
assert.equal(data.x, 100);
813+
assert.equal(data.y, 100);
814+
assert.equal(data.deltaX, 50);
815+
assert.equal(data.deltaY, 50);
816+
done();
817+
}
818+
drag = TestUtils.renderIntoDocument(
819+
<Draggable onDrag={onDrag} bounds="parent" defaultPosition={{x:50,y:50}}>
820+
<div style={{position: 'relative', width:'100px', height:'100px'}} />
821+
</Draggable>
822+
);
823+
const node = ReactDOM.findDOMNode(drag);
824+
825+
// Create a parent container.
826+
const fragment = fragmentFromString(`
827+
<div style="position: relative; width: 200px; height: 200px;">
828+
</div>
829+
`);
830+
transplantNodeInto(node, fragment, (f) => f);
831+
832+
833+
// (element, fromX, fromY, toX, toY)
834+
simulateMovementFromTo(drag, 50, 50, 350, 350);
835+
836+
});
837+
838+
it('should clip dragging to parent, with bounds set to "parent", in a shadow tree', function(done){
839+
function onDrag(event, data) {
840+
assert.equal(data.x, 100);
841+
assert.equal(data.y, 100);
842+
assert.equal(data.deltaX, 50);
843+
assert.equal(data.deltaY, 50);
844+
done();
845+
}
846+
drag = TestUtils.renderIntoDocument(
847+
<Draggable onDrag={onDrag} bounds="parent" defaultPosition={{x:50,y:50}}>
848+
<div style={{position: 'relative', width:'100px', height:'100px'}} />
849+
</Draggable>
850+
);
851+
const node = ReactDOM.findDOMNode(drag);
852+
853+
// Create a parent container.
854+
const fragment = fragmentFromString(`
855+
<div style="position: relative; width: 200px; height: 200px;">
856+
</div>
857+
`);
858+
859+
// Add the parent fragment to a shadow root
860+
const div = document.createElement('div');
861+
const shadowRoot = div.attachShadow({mode: 'open'});
862+
shadowRoot.appendChild(fragment);
863+
864+
transplantNodeInto(node, shadowRoot, (f) => f.children[0]);
865+
866+
867+
// (element, fromX, fromY, toX, toY)
868+
simulateMovementFromTo(drag, 50, 50, 350, 350);
869+
870+
});
871+
872+
it('should clip dragging to parent, with bounds set to selector', function(done){
873+
function onDrag(event, data) {
874+
assert.equal(data.x, 100);
875+
assert.equal(data.y, 100);
876+
assert.equal(data.deltaX, 50);
877+
assert.equal(data.deltaY, 50);
878+
done();
879+
}
880+
drag = TestUtils.renderIntoDocument(
881+
<Draggable onDrag={onDrag} bounds="#container" defaultPosition={{x:50,y:50}}>
882+
<div style={{position: 'relative', width:'100px', height:'100px'}} />
883+
</Draggable>
884+
);
885+
const node = ReactDOM.findDOMNode(drag);
886+
887+
// Create a parent container.
888+
const fragment = fragmentFromString(`
889+
<div id="container" style="position: relative; width: 200px; height: 200px;">
890+
</div>
891+
`);
892+
transplantNodeInto(node, fragment, (f) => f);
893+
894+
895+
// (element, fromX, fromY, toX, toY)
896+
simulateMovementFromTo(drag, 50, 50, 350, 350);
897+
898+
});
899+
900+
it('should clip dragging to parent, with bounds set to selector, in a shadow tree', function(done){
901+
function onDrag(event, data) {
902+
assert.equal(data.x, 100);
903+
assert.equal(data.y, 100);
904+
assert.equal(data.deltaX, 50);
905+
assert.equal(data.deltaY, 50);
906+
done();
907+
}
908+
drag = TestUtils.renderIntoDocument(
909+
<Draggable onDrag={onDrag} bounds="#container" defaultPosition={{x:50,y:50}}>
910+
<div style={{position: 'relative', width:'100px', height:'100px'}} />
911+
</Draggable>
912+
);
913+
const node = ReactDOM.findDOMNode(drag);
914+
915+
// Create a parent container.
916+
const fragment = fragmentFromString(`
917+
<div id="container" style="position: relative; width: 200px; height: 200px;">
918+
</div>
919+
`);
920+
921+
// Add the parent fragment to a shadow root
922+
const div = document.createElement('div');
923+
const shadowRoot = div.attachShadow({mode: 'open'});
924+
shadowRoot.appendChild(fragment);
925+
926+
transplantNodeInto(node, shadowRoot, (f) => f.children[0]);
927+
928+
// (element, fromX, fromY, toX, toY)
929+
simulateMovementFromTo(drag, 50, 50, 350, 350);
930+
931+
});
932+
810933
it('should call back with offset left/top, not client', function(done) {
811934
function onDrag(event, data) {
812935
assert.equal(data.x, 100);

0 commit comments

Comments
 (0)