From ef126bcaaf7f40ac5e7f19914f2b9661279c2d27 Mon Sep 17 00:00:00 2001 From: Andrew Patton Date: Mon, 18 Jul 2016 13:36:31 -0700 Subject: [PATCH] Use window/document relative to DOM element Use the ownerDocument and window of the DOM element being manipulated rather than the global window and document objects. This makes react-draggable work with a tool like https://github.com/ryanseddon/react-frame-component --- lib/DraggableCore.es6 | 34 ++++++++++++++++++++-------------- lib/utils/domFns.es6 | 24 ++++++++++++------------ lib/utils/positionFns.es6 | 13 +++++++------ 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/lib/DraggableCore.es6 b/lib/DraggableCore.es6 index 64e412fa..1e630dd3 100644 --- a/lib/DraggableCore.es6 +++ b/lib/DraggableCore.es6 @@ -182,11 +182,12 @@ export default class DraggableCore extends React.Component { componentWillUnmount() { // Remove any leftover event handlers. Remove both touch and mouse handlers in case // some browser quirk caused a touch event to fire during a mouse move, or vice versa. - removeEvent(document, eventsFor.mouse.move, this.handleDrag); - removeEvent(document, eventsFor.touch.move, this.handleDrag); - removeEvent(document, eventsFor.mouse.stop, this.handleDragStop); - removeEvent(document, eventsFor.touch.stop, this.handleDragStop); - if (this.props.enableUserSelectHack) removeUserSelectStyles(); + const {ownerDocument} = ReactDOM.findDOMNode(this); + removeEvent(ownerDocument, eventsFor.mouse.move, this.handleDrag); + removeEvent(ownerDocument, eventsFor.touch.move, this.handleDrag); + removeEvent(ownerDocument, eventsFor.mouse.stop, this.handleDragStop); + removeEvent(ownerDocument, eventsFor.touch.stop, this.handleDragStop); + if (this.props.enableUserSelectHack) removeUserSelectStyles(ownerDocument.body); } handleDragStart: EventHandler = (e) => { @@ -196,11 +197,15 @@ export default class DraggableCore extends React.Component { // Only accept left-clicks. if (!this.props.allowAnyClick && typeof e.button === 'number' && e.button !== 0) return false; + // Get nodes. Be sure to grab relative document (could be iframed) + const domNode = ReactDOM.findDOMNode(this); + const {ownerDocument} = domNode; + // 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 && !matchesSelectorAndParentsTo(e.target, this.props.handle, ReactDOM.findDOMNode(this))) || - (this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, ReactDOM.findDOMNode(this)))) { + (!(e.target instanceof ownerDocument.defaultView.Node)) || + (this.props.handle && !matchesSelectorAndParentsTo(e.target, this.props.handle, domNode)) || + (this.props.cancel && matchesSelectorAndParentsTo(e.target, this.props.cancel, domNode))) { return; } @@ -227,7 +232,7 @@ export default class DraggableCore extends React.Component { // Add a style to the body to disable user-select. This prevents text from // being selected all over the page. - if (this.props.enableUserSelectHack) addUserSelectStyles(); + if (this.props.enableUserSelectHack) addUserSelectStyles(ownerDocument.body); // Initiate dragging. Set the current x and y as offsets // so we know how much we've moved during the drag. This allows us @@ -242,8 +247,8 @@ export default class DraggableCore extends React.Component { // Add events to the document directly so we catch when the user's mouse/touch moves outside of // this element. We use different events depending on whether or not we have detected that this // is a touch-capable device. - addEvent(document, dragEventFor.move, this.handleDrag); - addEvent(document, dragEventFor.stop, this.handleDragStop); + addEvent(ownerDocument, dragEventFor.move, this.handleDrag); + addEvent(ownerDocument, dragEventFor.stop, this.handleDragStop); }; handleDrag: EventHandler = (e) => { @@ -298,7 +303,7 @@ export default class DraggableCore extends React.Component { const coreEvent = createCoreData(this, x, y); // Remove user-select hack - if (this.props.enableUserSelectHack) removeUserSelectStyles(); + if (this.props.enableUserSelectHack) removeUserSelectStyles(ReactDOM.findDOMNode(this).ownerDocument.body); log('DraggableCore: handleDragStop: %j', coreEvent); @@ -313,9 +318,10 @@ export default class DraggableCore extends React.Component { this.props.onStop(e, coreEvent); // Remove event handlers + const {ownerDocument} = ReactDOM.findDOMNode(this); log('DraggableCore: Removing handlers'); - removeEvent(document, dragEventFor.move, this.handleDrag); - removeEvent(document, dragEventFor.stop, this.handleDragStop); + removeEvent(ownerDocument, dragEventFor.move, this.handleDrag); + removeEvent(ownerDocument, dragEventFor.stop, this.handleDragStop); }; onMouseDown: EventHandler = (e) => { diff --git a/lib/utils/domFns.es6 b/lib/utils/domFns.es6 index 7ae4ad5a..0e1a6b79 100644 --- a/lib/utils/domFns.es6 +++ b/lib/utils/domFns.es6 @@ -63,7 +63,7 @@ export function outerHeight(node: HTMLElement): number { // This is deliberately excluding margin for our calculations, since we are using // offsetTop which is including margin. See getBoundPosition let height = node.clientHeight; - const computedStyle = window.getComputedStyle(node); + const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); height += int(computedStyle.borderTopWidth); height += int(computedStyle.borderBottomWidth); return height; @@ -73,14 +73,14 @@ export function outerWidth(node: HTMLElement): number { // This is deliberately excluding margin for our calculations, since we are using // offsetLeft which is including margin. See getBoundPosition let width = node.clientWidth; - const computedStyle = window.getComputedStyle(node); + const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); width += int(computedStyle.borderLeftWidth); width += int(computedStyle.borderRightWidth); return width; } export function innerHeight(node: HTMLElement): number { let height = node.clientHeight; - const computedStyle = window.getComputedStyle(node); + const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); height -= int(computedStyle.paddingTop); height -= int(computedStyle.paddingBottom); return height; @@ -88,15 +88,14 @@ export function innerHeight(node: HTMLElement): number { export function innerWidth(node: HTMLElement): number { let width = node.clientWidth; - const computedStyle = window.getComputedStyle(node); + const computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); width -= int(computedStyle.paddingLeft); width -= int(computedStyle.paddingRight); return width; } // Get from offsetParent -export function offsetXYFromParent(evt: {clientX: number, clientY: number}, offsetParent: ?HTMLElement): ControlPosition { - if (!offsetParent) offsetParent = document.body; +export function offsetXYFromParent(evt: {clientX: number, clientY: number}, offsetParent: HTMLElement): ControlPosition { const isBody = offsetParent === offsetParent.ownerDocument.body; const offsetParentRect = isBody ? {left: 0, top: 0} : offsetParent.getBoundingClientRect(); @@ -132,14 +131,15 @@ const userSelectPrefix = getPrefix('user-select'); const userSelect = browserPrefixToStyle('user-select', userSelectPrefix); const userSelectStyle = `;${userSelect}: none;`; -export function addUserSelectStyles() { - const style = document.body.getAttribute('style') || ''; - document.body.setAttribute('style', style + userSelectStyle); +// Note we're passing `document` b/c we could be iframed +export function addUserSelectStyles(body: HTMLElement) { + const style = body.getAttribute('style') || ''; + body.setAttribute('style', style + userSelectStyle); } -export function removeUserSelectStyles() { - const style = document.body.getAttribute('style') || ''; - document.body.setAttribute('style', style.replace(userSelectStyle, '')); +export function removeUserSelectStyles(body: HTMLElement) { + const style = body.getAttribute('style') || ''; + body.setAttribute('style', style.replace(userSelectStyle, '')); } export function styleHacks(childStyle: Object = {}): Object { diff --git a/lib/utils/positionFns.es6 b/lib/utils/positionFns.es6 index e1591bdc..64b6d8f1 100644 --- a/lib/utils/positionFns.es6 +++ b/lib/utils/positionFns.es6 @@ -17,15 +17,17 @@ export function getBoundPosition(draggable: Draggable, x: number, y: number): [n const node = ReactDOM.findDOMNode(draggable); if (typeof bounds === 'string') { + const {currentDocument} = node; + const currentWindow = node.defaultView; let boundNode; if (bounds === 'parent') { boundNode = node.parentNode; } else { - boundNode = document.querySelector(bounds); + boundNode = currentDocument.querySelector(bounds); if (!boundNode) throw new Error('Bounds selector "' + bounds + '" could not find an element.'); } - const nodeStyle = window.getComputedStyle(node); - const boundNodeStyle = window.getComputedStyle(boundNode); + const nodeStyle = currentWindow.getComputedStyle(node); + const boundNodeStyle = currentWindow.getComputedStyle(boundNode); // Compute bounds. This is a pain with padding and offsets but this gets it exactly right. bounds = { left: -node.offsetLeft + int(boundNodeStyle.paddingLeft) + @@ -66,10 +68,9 @@ export function canDragY(draggable: Draggable): boolean { export function getControlPosition(e: MouseEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition { const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null; if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch + const node = ReactDOM.findDOMNode(draggableCore); // User can provide an offsetParent if desired. - const offsetParent = draggableCore.props.offsetParent || - ReactDOM.findDOMNode(draggableCore).offsetParent || - document.body; + const offsetParent = draggableCore.props.offsetParent || node.offsetParent || node.ownerDocument.body; return offsetXYFromParent(touchObj || e, offsetParent); }