Skip to content

Commit 720f2b8

Browse files
committed
Refactor removal tracker
1 parent f0d23eb commit 720f2b8

File tree

2 files changed

+92
-52
lines changed

2 files changed

+92
-52
lines changed

src/decorators/trackRemoval.js

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,116 @@
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-
101
// https://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
112
const getMutationObserverClass = () => {
123
return window.MutationObserver ||
134
window.WebKitMutationObserver ||
145
window.MozMutationObserver
156
}
167

17-
export default function (target) {
18-
target.prototype.trackRemoval = function () {
19-
if (this.removalObserver) {
20-
this.releaseRemovalTracker()
8+
const isMutationObserverAvailable = () => {
9+
return getMutationObserverClass() != null
10+
}
11+
12+
class EventBasedRemovalTracker {
13+
constructor (tooltip) {
14+
this.tooltip = tooltip
15+
this.listeners = []
16+
}
17+
18+
attach (element) {
19+
const {tooltip} = this
20+
21+
let listener = function (e) {
22+
if (e.currentTarget === tooltip.state.currentTarget) {
23+
tooltip.hideTooltip()
24+
this.listeners.splice(this.listeners.indexOf(listener), 1)
25+
}
2126
}
27+
listener = listener.bind(this)
28+
29+
this.listeners.push({
30+
element,
31+
listener
32+
})
2233

23-
this.tracked = []
34+
element.addEventListener('DOMNodeRemovedFromDocument', listener)
35+
}
36+
37+
unbind () {
38+
for (const {listener, element} of this.listeners) {
39+
element.removeEventListener('DOMNodeRemovedFromDocument', listener)
40+
}
41+
this.listeners = []
42+
}
43+
}
44+
45+
class MutationBasedRemovalTracker {
46+
constructor (tooltip) {
47+
this.tooltip = tooltip
48+
49+
this.observer = null
50+
this.inited = false
51+
this.trackedElements = null
52+
}
53+
54+
init () {
55+
if (this.inited) {
56+
this.unbind()
57+
}
58+
59+
this.trackedElements = []
2460

2561
const MutationObserver = getMutationObserverClass()
2662
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()
63+
this.observer = new MutationObserver(() => {
64+
for (const element of this.trackedElements) {
65+
if (this.isDetached(element) && element === this.tooltip.state.currentTarget) {
66+
this.tooltip.hideTooltip()
3167
}
3268
}
3369
})
34-
observer.observe(window.document, { childList: true, subtree: true })
70+
this.observer.observe(window.document, { childList: true, subtree: true })
3571
}
36-
}
3772

38-
target.prototype.attachRemovalTracker = function (element) {
39-
this.tracked.push(element)
40-
41-
const isMutationObserverAvailable = getMutationObserverClass()
42-
if (!isMutationObserverAvailable) {
43-
this.listeners = this.listeners || []
73+
this.inited = true
74+
}
4475

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)
76+
unbind () {
77+
if (this.observer) {
78+
this.observer.disconnect()
79+
this.observer = null
80+
this.trackedElements = null
81+
}
82+
this.inited = false
83+
}
5384

54-
this.listeners.push({
55-
element,
56-
listener
57-
})
85+
attach (element) {
86+
this.trackedElements.push(element)
87+
}
5888

59-
element.addEventListener('DOMNodeRemovedFromDocument', listener)
89+
// http://stackoverflow.com/a/32726412/7571078
90+
isDetached (element) {
91+
if (element.parentNode === window.document) {
92+
return false
6093
}
94+
if (element.parentNode == null) return true
95+
return this.isDetached(element.parentNode)
6196
}
97+
}
6298

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 = []
99+
export default function (target) {
100+
target.prototype.bindRemovalTracker = function () {
101+
if (isMutationObserverAvailable()) {
102+
this.removalTracker = new MutationBasedRemovalTracker(this)
103+
this.removalTracker.init()
104+
} else {
105+
this.removalTracker = new EventBasedRemovalTracker(this)
74106
}
75107
}
108+
109+
target.prototype.attachRemovalTracker = function (element) {
110+
this.removalTracker.attach(element)
111+
}
112+
113+
target.prototype.unbindRemovalTracker = function () {
114+
this.removalTracker.unbind()
115+
}
76116
}

src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ class ReactTooltip extends Component {
169169
const {id, globalEventOff} = this.props
170170
let targetArray = this.getTargetArray(id)
171171

172-
this.trackRemoval()
172+
this.bindRemovalTracker()
173173

174174
targetArray.forEach(target => {
175175
const isCaptureMode = this.isCapture(target)
@@ -211,7 +211,7 @@ class ReactTooltip extends Component {
211211
})
212212

213213
if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip)
214-
this.releaseRemovalTracker()
214+
this.unbindRemovalTracker()
215215
}
216216

217217
/**

0 commit comments

Comments
 (0)