From 5978eb4753896f364d87daa9ebcf4ff179d03113 Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Mon, 19 Nov 2018 08:40:56 +0200 Subject: [PATCH 1/2] Handle pointerDown / pointerUp event pairs Unfortunately right now the onOutsideClick will be triggered twice per click on browsers that support pointer events. --- src/OutsideClickHandler.jsx | 66 ++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/src/OutsideClickHandler.jsx b/src/OutsideClickHandler.jsx index f796a5a..6df359d 100644 --- a/src/OutsideClickHandler.jsx +++ b/src/OutsideClickHandler.jsx @@ -34,13 +34,17 @@ export default class OutsideClickHandler extends React.Component { this.onMouseDown = this.onMouseDown.bind(this); this.onMouseUp = this.onMouseUp.bind(this); + this.onPointerDown = this.onPointerDown.bind(this); + this.onPointerUp = this.onPointerUp.bind(this); + this.downHandler = this.downHandler.bind(this); + this.upHandler = this.upHandler.bind(this); this.setChildNodeRef = this.setChildNodeRef.bind(this); } componentDidMount() { const { disabled, useCapture } = this.props; - if (!disabled) this.addMouseDownEventListener(useCapture); + if (!disabled) this.addDownEventListeners(useCapture); } componentWillReceiveProps({ disabled, useCapture }) { @@ -49,7 +53,7 @@ export default class OutsideClickHandler extends React.Component { if (disabled) { this.removeEventListeners(); } else { - this.addMouseDownEventListener(useCapture); + this.addDownEventListeners(useCapture); } } } @@ -58,54 +62,78 @@ export default class OutsideClickHandler extends React.Component { this.removeEventListeners(); } - // Use mousedown/mouseup to enforce that clicks remain outside the root's - // descendant tree, even when dragged. This should also get triggered on - // touch devices. + // Use mousedown/mouseup or pointerdown/pointerup to enforce that clicks remain + // outside the root's descendant tree, even when dragged. This should also get + // triggered on touch devices. onMouseDown(e) { + this.downHandler(e, 'removeMouseUp', 'mouseup', this.onMouseUp); + } + + onPointerDown(e) { + this.downHandler(e, 'removePointerUp', 'pointerup', this.onPointerUp); + } + + // Use mousedown/mouseup or pointerdown/pointerup to enforce that clicks remain + // outside the root's descendant tree, even when dragged. This should also get + // triggered on touch devices. + onMouseUp(e) { + this.upHandler(e, 'removeMouseUp'); + } + + onPointerUp(e) { + this.upHandler(e, 'removePointerUp'); + } + + setChildNodeRef(ref) { + this.childNode = ref; + } + + downHandler(e, removeUpHandlerName, eventName, callback) { const { useCapture } = this.props; const isDescendantOfRoot = this.childNode && this.childNode.contains(e.target); if (!isDescendantOfRoot) { - this.removeMouseUp = addEventListener( + this[removeUpHandlerName] = addEventListener( document, - 'mouseup', - this.onMouseUp, + eventName, + callback, { capture: useCapture }, ); } } - // Use mousedown/mouseup to enforce that clicks remain outside the root's - // descendant tree, even when dragged. This should also get triggered on - // touch devices. - onMouseUp(e) { + upHandler(e, removeUpHandlerName) { const { onOutsideClick } = this.props; const isDescendantOfRoot = this.childNode && this.childNode.contains(e.target); - if (this.removeMouseUp) this.removeMouseUp(); - this.removeMouseUp = null; + if (this[removeUpHandlerName]) this[removeUpHandlerName](); + this[removeUpHandlerName] = null; if (!isDescendantOfRoot) { onOutsideClick(e); } } - setChildNodeRef(ref) { - this.childNode = ref; - } - - addMouseDownEventListener(useCapture) { + addDownEventListeners(useCapture) { this.removeMouseDown = addEventListener( document, 'mousedown', this.onMouseDown, { capture: useCapture }, ); + this.removePointerDown = addEventListener( + document, + 'pointerdown', + this.onPointerDown, + { capture: useCapture }, + ); } removeEventListeners() { if (this.removeMouseDown) this.removeMouseDown(); if (this.removeMouseUp) this.removeMouseUp(); + if (this.removePointerDown) this.removePointerDown(); + if (this.removePointerUp) this.removePointerUp(); } render() { From dd1cdf9630cd46a9b5e6dedf95351f9e2b7411f6 Mon Sep 17 00:00:00 2001 From: Antti Risteli Date: Thu, 22 Nov 2018 07:52:39 +0200 Subject: [PATCH 2/2] Remove duplicate triggering of onOutsideClick When both onPointerUp and onMouseUp fire, they will have the same timeStamp value. Furthermore onPointerUp will always fire before onMouseUp. Hence if we just handled pointer up and are handling mouse up caused by the same user event we can just skip it. --- src/OutsideClickHandler.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/OutsideClickHandler.jsx b/src/OutsideClickHandler.jsx index 6df359d..001aa17 100644 --- a/src/OutsideClickHandler.jsx +++ b/src/OutsideClickHandler.jsx @@ -110,7 +110,14 @@ export default class OutsideClickHandler extends React.Component { this[removeUpHandlerName] = null; if (!isDescendantOfRoot) { + if (this.lastUpTimestamp === e.timeStamp + && this.lastUpRemoveHandlerName === 'removePointerUp' + && removeUpHandlerName === 'removeMouseUp') { + return; + } onOutsideClick(e); + this.lastUpTimestamp = e.timeStamp; + this.lastUpRemoveHandlerName = removeUpHandlerName; } }