Skip to content

Commit f0d23eb

Browse files
committed
Add removal tracker prototype
1 parent 9399d75 commit f0d23eb

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

src/decorators/trackRemoval.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// http://stackoverflow.com/a/32726412/7571078
2+
const isDetached = (element) => {
3+
if (element.parentNode === window.document) {
4+
return false
5+
}
6+
if (element.parentNode == null) return true
7+
return isDetached(element.parentNode)
8+
}
9+
10+
// https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
11+
const getMutationObserverClass = () => {
12+
return window.MutationObserver ||
13+
window.WebKitMutationObserver ||
14+
window.MozMutationObserver
15+
}
16+
17+
export default function (target) {
18+
target.prototype.trackRemoval = function () {
19+
if (this.removalObserver) {
20+
this.releaseRemovalTracker()
21+
}
22+
23+
this.tracked = []
24+
25+
const MutationObserver = getMutationObserverClass()
26+
if (MutationObserver) {
27+
const observer = this.removalObserver = new MutationObserver(() => {
28+
for (const element of this.tracked) {
29+
if (isDetached(element) && element === this.state.currentTarget) {
30+
this.hideTooltip()
31+
}
32+
}
33+
})
34+
observer.observe(window.document, { childList: true, subtree: true })
35+
}
36+
}
37+
38+
target.prototype.attachRemovalTracker = function (element) {
39+
this.tracked.push(element)
40+
41+
const isMutationObserverAvailable = getMutationObserverClass()
42+
if (!isMutationObserverAvailable) {
43+
this.listeners = this.listeners || []
44+
45+
let listener = function (e) {
46+
if (e.currentTarget === this.state.currentTarget) {
47+
this.hideTooltip()
48+
const idx = this.listeners.indexOf(listener)
49+
this.listeners.splice(idx, 1)
50+
}
51+
}
52+
listener = listener.bind(this)
53+
54+
this.listeners.push({
55+
element,
56+
listener
57+
})
58+
59+
element.addEventListener('DOMNodeRemovedFromDocument', listener)
60+
}
61+
}
62+
63+
target.prototype.releaseRemovalTracker = function () {
64+
if (this.removalObserver) {
65+
this.removalObserver.disconnect()
66+
this.removalObserver = null
67+
this.tracked = []
68+
}
69+
if (this.listeners) {
70+
for (const {listener, element} of this.listeners) {
71+
element.removeEventListener('DOMNodeRemovedFromDocument', listener)
72+
}
73+
this.listeners = []
74+
}
75+
}
76+
}

src/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import windowListener from './decorators/windowListener'
1010
import customEvent from './decorators/customEvent'
1111
import isCapture from './decorators/isCapture'
1212
import getEffect from './decorators/getEffect'
13+
import trackRemoval from './decorators/trackRemoval'
1314

1415
/* Utils */
1516
import getPosition from './utils/getPosition'
@@ -20,7 +21,12 @@ import nodeListToArray from './utils/nodeListToArray'
2021
/* CSS */
2122
import cssStyle from './style'
2223

23-
@staticMethods @windowListener @customEvent @isCapture @getEffect
24+
@staticMethods
25+
@windowListener
26+
@customEvent
27+
@isCapture
28+
@getEffect
29+
@trackRemoval
2430
class ReactTooltip extends Component {
2531

2632
static propTypes = {
@@ -163,6 +169,8 @@ class ReactTooltip extends Component {
163169
const {id, globalEventOff} = this.props
164170
let targetArray = this.getTargetArray(id)
165171

172+
this.trackRemoval()
173+
166174
targetArray.forEach(target => {
167175
const isCaptureMode = this.isCapture(target)
168176
const effect = this.getEffect(target)
@@ -181,7 +189,7 @@ class ReactTooltip extends Component {
181189
target.addEventListener('mousemove', this.updateTooltip, isCaptureMode)
182190
}
183191
target.addEventListener('mouseleave', this.hideTooltip, isCaptureMode)
184-
target.addEventListener('DOMNodeRemovedFromDocument', this.checkSameTarget, isCaptureMode)
192+
this.attachRemovalTracker(target)
185193
})
186194

187195
// Global event to hide tooltip
@@ -203,6 +211,7 @@ class ReactTooltip extends Component {
203211
})
204212

205213
if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip)
214+
this.releaseRemovalTracker()
206215
}
207216

208217
/**
@@ -215,7 +224,6 @@ class ReactTooltip extends Component {
215224
target.removeEventListener('mouseenter', this.showTooltip, isCaptureMode)
216225
target.removeEventListener('mousemove', this.updateTooltip, isCaptureMode)
217226
target.removeEventListener('mouseleave', this.hideTooltip, isCaptureMode)
218-
target.removeEventListener('DOMNodeRemovedFromDocument', this.checkSameTarget, isCaptureMode)
219227
}
220228

221229
/**

0 commit comments

Comments
 (0)