diff --git a/src/decorators/trackRemoval.js b/src/decorators/trackRemoval.js new file mode 100644 index 000000000..d072ea551 --- /dev/null +++ b/src/decorators/trackRemoval.js @@ -0,0 +1,44 @@ +/** + * Tracking target removing from DOM. + * It's nessesary to hide tooltip when it's target disappears. + * Otherwise, the tooltip would be shown forever until another target + * is triggered. + * + * If MutationObserver is not available, this feature just doesn't work. + */ + +// https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/ +const getMutationObserverClass = () => { + return window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver +} + +export default function (target) { + target.prototype.bindRemovalTracker = function () { + const MutationObserver = getMutationObserverClass() + if (MutationObserver == null) return + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const element of mutation.removedNodes) { + if (element === this.state.currentTarget) { + this.hideTooltip() + return + } + } + } + }) + + observer.observe(window.document, { childList: true, subtree: true }) + + this.removalTracker = observer + } + + target.prototype.unbindRemovalTracker = function () { + if (this.removalTracker) { + this.removalTracker.disconnect() + this.removalTracker = null + } + } +} diff --git a/src/index.js b/src/index.js index 69e76c9cb..24780c721 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ import windowListener from './decorators/windowListener' import customEvent from './decorators/customEvent' import isCapture from './decorators/isCapture' import getEffect from './decorators/getEffect' +import trackRemoval from './decorators/trackRemoval' /* Utils */ import getPosition from './utils/getPosition' @@ -20,7 +21,12 @@ import nodeListToArray from './utils/nodeListToArray' /* CSS */ import cssStyle from './style' -@staticMethods @windowListener @customEvent @isCapture @getEffect +@staticMethods +@windowListener +@customEvent +@isCapture +@getEffect +@trackRemoval class ReactTooltip extends Component { static propTypes = { @@ -86,7 +92,6 @@ class ReactTooltip extends Component { this.bind([ 'showTooltip', 'updateTooltip', - 'checkSameTarget', 'hideTooltip', 'globalRebuild', 'globalShow', @@ -181,7 +186,6 @@ class ReactTooltip extends Component { target.addEventListener('mousemove', this.updateTooltip, isCaptureMode) } target.addEventListener('mouseleave', this.hideTooltip, isCaptureMode) - target.addEventListener('DOMNodeRemovedFromDocument', this.checkSameTarget, isCaptureMode) }) // Global event to hide tooltip @@ -189,6 +193,9 @@ class ReactTooltip extends Component { window.removeEventListener(globalEventOff, this.hideTooltip) window.addEventListener(globalEventOff, this.hideTooltip, false) } + + // Track removal of targetArray elements from DOM + this.bindRemovalTracker() } /** @@ -203,6 +210,7 @@ class ReactTooltip extends Component { }) if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip) + this.unbindRemovalTracker() } /** @@ -215,7 +223,6 @@ class ReactTooltip extends Component { target.removeEventListener('mouseenter', this.showTooltip, isCaptureMode) target.removeEventListener('mousemove', this.updateTooltip, isCaptureMode) target.removeEventListener('mouseleave', this.hideTooltip, isCaptureMode) - target.removeEventListener('DOMNodeRemovedFromDocument', this.checkSameTarget, isCaptureMode) } /** @@ -332,12 +339,6 @@ class ReactTooltip extends Component { } } - checkSameTarget (e) { - if (this.state.currentTarget === e.currentTarget) { - this.hideTooltip(e) - } - } - /** * When mouse leave, hide tooltip */