Refactor Enter/Leave listener accumulation (#18405)
* Refactor Enter/Leave listener accumulation
Dominic Gannaway committed
Apr 2, 2020 at 11:07 UTC
663c13d71d902aa38bb93b14ef93ffa803d1913c
9 files changed
+376
-239
packages/legacy-events/EventPropagators.js
deleted
-76
@@ -1,76 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
-import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
12
-
13
-import getListener from 'legacy-events/getListener';
14
-
15
-import {traverseEnterLeave} from 'react-reconciler/src/ReactTreeTraversal';
16
-import accumulateInto from './accumulateInto';
17
-import forEachAccumulated from './forEachAccumulated';
18
-
19
-/**
20
- * A small set of propagation patterns, each of which will accept a small amount
21
- * of information, and generate a set of "dispatch ready event objects" - which
22
- * are sets of events that have already been annotated with a set of dispatched
23
- * listener functions/ids. The API is designed this way to discourage these
24
- * propagation strategies from actually executing the dispatches, since we
25
- * always want to collect the entire set of dispatches before executing even a
26
- * single one.
27
- */
28
-
29
-/**
30
- * Accumulates without regard to direction, does not look for phased
31
- * registration names. Same as `accumulateDirectDispatchesSingle` but without
32
- * requiring that the `dispatchMarker` be the same as the dispatched ID.
33
- */
34
-function accumulateDispatches(
35
- inst: Fiber,
36
- ignoredDirection: ?boolean,
37
- event: ReactSyntheticEvent,
38
-): void {
39
- if (inst && event && event.dispatchConfig.registrationName) {
40
- const registrationName = event.dispatchConfig.registrationName;
41
- const listener = getListener(inst, registrationName);
42
- if (listener) {
43
- event._dispatchListeners = accumulateInto(
44
- event._dispatchListeners,
45
- listener,
46
- );
47
- event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
48
- }
49
- }
50
-}
51
-
52
-/**
53
- * Accumulates dispatches on an `SyntheticEvent`, but only for the
54
- * `dispatchMarker`.
55
- * @param {SyntheticEvent} event
56
- */
57
-function accumulateDirectDispatchesSingle(event: ReactSyntheticEvent) {
58
- if (event && event.dispatchConfig.registrationName) {
59
- accumulateDispatches(event._targetInst, null, event);
60
- }
61
-}
62
-
63
-export function accumulateEnterLeaveDispatches(
64
- leave: ReactSyntheticEvent,
65
- enter: ReactSyntheticEvent,
66
- from: Fiber,
67
- to: Fiber,
68
-) {
69
- traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
70
-}
71
-
72
-export function accumulateDirectDispatches(
73
- events: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent),
74
-) {
75
- forEachAccumulated(events, accumulateDirectDispatchesSingle);
76
-}
packages/legacy-events/ReactSyntheticEventType.js
+1
@@ -27,6 +27,7 @@ export type CustomDispatchConfig = {|
27
bubbled: null,
28
captured: null,
29
|},
30
+ registrationName?: string,
31
customEvent: true,
32
|};
33
packages/legacy-events/ResponderEventPlugin.js
+125
-9
@@ -5,20 +5,12 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import {
9
- getLowestCommonAncestor,
10
- isAncestor,
11
- getParentInstance,
12
- traverseTwoPhase,
13
-} from 'react-reconciler/src/ReactTreeTraversal';
14
-
8
import {
9
executeDirectDispatch,
10
hasDispatches,
11
executeDispatchesInOrderStopAtTrue,
12
getInstanceFromNode,
13
} from './EventPluginUtils';
21
-import {accumulateDirectDispatches} from './EventPropagators';
14
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
15
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
16
import accumulate from './accumulate';
@@ -36,6 +28,7 @@ import {
28
import getListener from './getListener';
29
import accumulateInto from './accumulateInto';
30
import forEachAccumulated from './forEachAccumulated';
31
+import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
32
33
/**
34
* Instance of element that should respond to touch/move types of interactions,
@@ -158,6 +151,91 @@ const eventTypes = {
151
// Start of inline: the below functions were inlined from
152
// EventPropagator.js, as they deviated from ReactDOM's newer
153
// implementations.
154
+
155
+function getParent(inst) {
156
+ do {
157
+ inst = inst.return;
158
+ // TODO: If this is a HostRoot we might want to bail out.
159
+ // That is depending on if we want nested subtrees (layers) to bubble
160
+ // events to their parent. We could also go through parentNode on the
161
+ // host node but that wouldn't work for React Native and doesn't let us
162
+ // do the portal feature.
163
+ } while (inst && inst.tag !== HostComponent);
164
+ if (inst) {
165
+ return inst;
166
+ }
167
+ return null;
168
+}
169
+
170
+/**
171
+ * Return the lowest common ancestor of A and B, or null if they are in
172
+ * different trees.
173
+ */
174
+export function getLowestCommonAncestor(instA, instB) {
175
+ let depthA = 0;
176
+ for (let tempA = instA; tempA; tempA = getParent(tempA)) {
177
+ depthA++;
178
+ }
179
+ let depthB = 0;
180
+ for (let tempB = instB; tempB; tempB = getParent(tempB)) {
181
+ depthB++;
182
+ }
183
+
184
+ // If A is deeper, crawl up.
185
+ while (depthA - depthB > 0) {
186
+ instA = getParent(instA);
187
+ depthA--;
188
+ }
189
+
190
+ // If B is deeper, crawl up.
191
+ while (depthB - depthA > 0) {
192
+ instB = getParent(instB);
193
+ depthB--;
194
+ }
195
+
196
+ // Walk in lockstep until we find a match.
197
+ let depth = depthA;
198
+ while (depth--) {
199
+ if (instA === instB || instA === instB.alternate) {
200
+ return instA;
201
+ }
202
+ instA = getParent(instA);
203
+ instB = getParent(instB);
204
+ }
205
+ return null;
206
+}
207
+
208
+/**
209
+ * Return if A is an ancestor of B.
210
+ */
211
+export function isAncestor(instA, instB) {
212
+ while (instB) {
213
+ if (instA === instB || instA === instB.alternate) {
214
+ return true;
215
+ }
216
+ instB = getParent(instB);
217
+ }
218
+ return false;
219
+}
220
+
221
+/**
222
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
223
+ */
224
+export function traverseTwoPhase(inst, fn, arg) {
225
+ const path = [];
226
+ while (inst) {
227
+ path.push(inst);
228
+ inst = getParent(inst);
229
+ }
230
+ let i;
231
+ for (i = path.length; i-- > 0; ) {
232
+ fn(path[i], 'captured', arg);
233
+ }
234
+ for (i = 0; i < path.length; i++) {
235
+ fn(path[i], 'bubbled', arg);
236
+ }
237
+}
238
+
239
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
240
const registrationName =
241
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
@@ -180,10 +258,48 @@ function accumulateDirectionalDispatches(inst, phase, event) {
258
}
259
}
260
261
+/**
262
+ * Accumulates without regard to direction, does not look for phased
263
+ * registration names. Same as `accumulateDirectDispatchesSingle` but without
264
+ * requiring that the `dispatchMarker` be the same as the dispatched ID.
265
+ */
266
+function accumulateDispatches(
267
+ inst: Object,
268
+ ignoredDirection: ?boolean,
269
+ event: Object,
270
+): void {
271
+ if (inst && event && event.dispatchConfig.registrationName) {
272
+ const registrationName = event.dispatchConfig.registrationName;
273
+ const listener = getListener(inst, registrationName);
274
+ if (listener) {
275
+ event._dispatchListeners = accumulateInto(
276
+ event._dispatchListeners,
277
+ listener,
278
+ );
279
+ event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
280
+ }
281
+ }
282
+}
283
+
284
+/**
285
+ * Accumulates dispatches on an `SyntheticEvent`, but only for the
286
+ * `dispatchMarker`.
287
+ * @param {SyntheticEvent} event
288
+ */
289
+function accumulateDirectDispatchesSingle(event: Object) {
290
+ if (event && event.dispatchConfig.registrationName) {
291
+ accumulateDispatches(event._targetInst, null, event);
292
+ }
293
+}
294
+
295
+function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
296
+ forEachAccumulated(events, accumulateDirectDispatchesSingle);
297
+}
298
+
299
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
300
if (event && event.dispatchConfig.phasedRegistrationNames) {
301
const targetInst = event._targetInst;
186
- const parentInst = targetInst ? getParentInstance(targetInst) : null;
302
+ const parentInst = targetInst ? getParent(targetInst) : null;
303
traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
304
}
305
}
packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js
+3
-2
@@ -1378,7 +1378,8 @@ describe('ResponderEventPlugin', () => {
1378
// ResponderEventPlugin uses `getLowestCommonAncestor`
1379
const React = require('react');
1380
const ReactTestUtils = require('react-dom/test-utils');
1381
- const ReactTreeTraversal = require('react-reconciler/src/ReactTreeTraversal');
1381
+ const getLowestCommonAncestor = require('legacy-events/ResponderEventPlugin')
1382
+ .getLowestCommonAncestor;
1383
const ReactDOMComponentTree = require('../../react-dom/src/client/ReactDOMComponentTree');
1384
1385
class ChildComponent extends React.Component {
@@ -1451,7 +1452,7 @@ describe('ResponderEventPlugin', () => {
1452
let i;
1453
for (i = 0; i < ancestors.length; i++) {
1454
const plan = ancestors[i];
1454
- const firstCommon = ReactTreeTraversal.getLowestCommonAncestor(
1455
+ const firstCommon = getLowestCommonAncestor(
1456
ReactDOMComponentTree.getInstanceFromNode(plan.one),
1457
ReactDOMComponentTree.getInstanceFromNode(plan.two),
1458
);
packages/react-dom/src/events/EnterLeaveEventPlugin.js
+2
-3
@@ -5,8 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import {accumulateEnterLeaveDispatches} from 'legacy-events/EventPropagators';
9
-
8
import {
9
TOP_MOUSE_OUT,
10
TOP_MOUSE_OVER,
@@ -23,6 +21,7 @@ import {
21
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
22
import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
23
import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
24
+import accumulateEnterLeaveListeners from './accumulateEnterLeaveListeners';
25
26
const eventTypes = {
27
mouseEnter: {
@@ -172,7 +171,7 @@ const EnterLeaveEventPlugin = {
171
enter.target = toNode;
172
enter.relatedTarget = fromNode;
173
175
- accumulateEnterLeaveDispatches(leave, enter, from, to);
174
+ accumulateEnterLeaveListeners(leave, enter, from, to);
175
176
if (!enableModernEventSystem) {
177
// If we are not processing the first ancestor, then we
packages/react-dom/src/events/accumulateEnterLeaveListeners.js
new
+139
@@ -0,0 +1,139 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
+import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
12
+
13
+import getListener from 'legacy-events/getListener';
14
+import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
15
+
16
+function getParent(inst: Fiber | null): Fiber | null {
17
+ if (inst === null) {
18
+ return null;
19
+ }
20
+ do {
21
+ inst = inst.return;
22
+ // TODO: If this is a HostRoot we might want to bail out.
23
+ // That is depending on if we want nested subtrees (layers) to bubble
24
+ // events to their parent. We could also go through parentNode on the
25
+ // host node but that wouldn't work for React Native and doesn't let us
26
+ // do the portal feature.
27
+ } while (inst && inst.tag !== HostComponent);
28
+ if (inst) {
29
+ return inst;
30
+ }
31
+ return null;
32
+}
33
+
34
+/**
35
+ * Return the lowest common ancestor of A and B, or null if they are in
36
+ * different trees.
37
+ */
38
+function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
39
+ let nodeA = instA;
40
+ let nodeB = instB;
41
+ let depthA = 0;
42
+ for (let tempA = nodeA; tempA; tempA = getParent(tempA)) {
43
+ depthA++;
44
+ }
45
+ let depthB = 0;
46
+ for (let tempB = nodeB; tempB; tempB = getParent(tempB)) {
47
+ depthB++;
48
+ }
49
+
50
+ // If A is deeper, crawl up.
51
+ while (depthA - depthB > 0) {
52
+ nodeA = getParent(nodeA);
53
+ depthA--;
54
+ }
55
+
56
+ // If B is deeper, crawl up.
57
+ while (depthB - depthA > 0) {
58
+ nodeB = getParent(nodeB);
59
+ depthB--;
60
+ }
61
+
62
+ // Walk in lockstep until we find a match.
63
+ let depth = depthA;
64
+ while (depth--) {
65
+ if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
66
+ return nodeA;
67
+ }
68
+ nodeA = getParent(nodeA);
69
+ nodeB = getParent(nodeB);
70
+ }
71
+ return null;
72
+}
73
+
74
+function accumulateEnterLeaveListenersForEvent(
75
+ event: ReactSyntheticEvent,
76
+ target: Fiber,
77
+ common: Fiber | null,
78
+ capture: boolean,
79
+): void {
80
+ const registrationName = event.dispatchConfig.registrationName;
81
+ if (registrationName === undefined) {
82
+ return;
83
+ }
84
+ const dispatchListeners = [];
85
+ const dispatchInstances = [];
86
+
87
+ let node = target;
88
+ while (node !== null) {
89
+ if (node === common) {
90
+ break;
91
+ }
92
+ const alternate = node.alternate;
93
+ if (alternate !== null && alternate === common) {
94
+ break;
95
+ }
96
+ if (node.tag === HostComponent) {
97
+ if (capture) {
98
+ const captureListener = getListener(node, registrationName);
99
+ if (captureListener != null) {
100
+ // Capture listeners/instances should go at the start, so we
101
+ // unshift them to the start of the array.
102
+ dispatchListeners.unshift(captureListener);
103
+ dispatchInstances.unshift(node);
104
+ }
105
+ } else {
106
+ const bubbleListener = getListener(node, registrationName);
107
+ if (bubbleListener != null) {
108
+ // Bubble listeners/instances should go at the end, so we
109
+ // push them to the end of the array.
110
+ dispatchListeners.push(bubbleListener);
111
+ dispatchInstances.push(node);
112
+ }
113
+ }
114
+ }
115
+ node = node.return;
116
+ }
117
+ // To prevent allocation to the event unless we actually
118
+ // have listeners we check the length of one of the arrays.
119
+ if (dispatchListeners.length > 0) {
120
+ event._dispatchListeners = dispatchListeners;
121
+ event._dispatchInstances = dispatchInstances;
122
+ }
123
+}
124
+
125
+export default function accumulateEnterLeaveListeners(
126
+ leaveEvent: ReactSyntheticEvent,
127
+ enterEvent: ReactSyntheticEvent,
128
+ from: Fiber | null,
129
+ to: Fiber | null,
130
+): void {
131
+ const common = from && to ? getLowestCommonAncestor(from, to) : null;
132
+
133
+ if (from !== null) {
134
+ accumulateEnterLeaveListenersForEvent(leaveEvent, from, common, false);
135
+ }
136
+ if (to !== null) {
137
+ accumulateEnterLeaveListenersForEvent(enterEvent, to, common, true);
138
+ }
139
+}
packages/react-dom/src/test-utils/ReactTestUtils.js
+33
-1
@@ -23,7 +23,6 @@ import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
23
import act from './ReactTestUtilsAct';
24
import forEachAccumulated from 'legacy-events/forEachAccumulated';
25
import accumulateInto from 'legacy-events/accumulateInto';
26
-import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
26
27
const {findDOMNode} = ReactDOM;
28
// Keep in sync with ReactDOMUnstableNativeDependencies.js
@@ -390,6 +389,39 @@ function isInteractive(tag) {
389
);
390
}
391
392
+function getParent(inst) {
393
+ do {
394
+ inst = inst.return;
395
+ // TODO: If this is a HostRoot we might want to bail out.
396
+ // That is depending on if we want nested subtrees (layers) to bubble
397
+ // events to their parent. We could also go through parentNode on the
398
+ // host node but that wouldn't work for React Native and doesn't let us
399
+ // do the portal feature.
400
+ } while (inst && inst.tag !== HostComponent);
401
+ if (inst) {
402
+ return inst;
403
+ }
404
+ return null;
405
+}
406
+
407
+/**
408
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
409
+ */
410
+export function traverseTwoPhase(inst, fn, arg) {
411
+ const path = [];
412
+ while (inst) {
413
+ path.push(inst);
414
+ inst = getParent(inst);
415
+ }
416
+ let i;
417
+ for (i = path.length; i-- > 0; ) {
418
+ fn(path[i], 'captured', arg);
419
+ }
420
+ for (i = 0; i < path.length; i++) {
421
+ fn(path[i], 'bubbled', arg);
422
+ }
423
+}
424
+
425
function shouldPreventMouseEvent(name, type, props) {
426
switch (name) {
427
case 'onClick':
packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js
+73
-2
@@ -9,7 +9,6 @@
9
10
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
12
-import {accumulateDirectDispatches} from 'legacy-events/EventPropagators';
12
import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
13
import SyntheticEvent from 'legacy-events/SyntheticEvent';
14
import invariant from 'shared/invariant';
@@ -18,8 +17,8 @@ import invariant from 'shared/invariant';
17
import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
18
import accumulateInto from 'legacy-events/accumulateInto';
19
import getListener from 'legacy-events/getListener';
21
-import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
20
import forEachAccumulated from 'legacy-events/forEachAccumulated';
21
+import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
22
23
const {
24
customBubblingEventTypes,
@@ -51,6 +50,39 @@ function accumulateDirectionalDispatches(inst, phase, event) {
50
}
51
}
52
53
+function getParent(inst) {
54
+ do {
55
+ inst = inst.return;
56
+ // TODO: If this is a HostRoot we might want to bail out.
57
+ // That is depending on if we want nested subtrees (layers) to bubble
58
+ // events to their parent. We could also go through parentNode on the
59
+ // host node but that wouldn't work for React Native and doesn't let us
60
+ // do the portal feature.
61
+ } while (inst && inst.tag !== HostComponent);
62
+ if (inst) {
63
+ return inst;
64
+ }
65
+ return null;
66
+}
67
+
68
+/**
69
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
70
+ */
71
+export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
72
+ const path = [];
73
+ while (inst) {
74
+ path.push(inst);
75
+ inst = getParent(inst);
76
+ }
77
+ let i;
78
+ for (i = path.length; i-- > 0; ) {
79
+ fn(path[i], 'captured', arg);
80
+ }
81
+ for (i = 0; i < path.length; i++) {
82
+ fn(path[i], 'bubbled', arg);
83
+ }
84
+}
85
+
86
function accumulateTwoPhaseDispatchesSingle(event) {
87
if (event && event.dispatchConfig.phasedRegistrationNames) {
88
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
@@ -60,6 +92,45 @@ function accumulateTwoPhaseDispatchesSingle(event) {
92
function accumulateTwoPhaseDispatches(events) {
93
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
94
}
95
+
96
+/**
97
+ * Accumulates without regard to direction, does not look for phased
98
+ * registration names. Same as `accumulateDirectDispatchesSingle` but without
99
+ * requiring that the `dispatchMarker` be the same as the dispatched ID.
100
+ */
101
+function accumulateDispatches(
102
+ inst: Object,
103
+ ignoredDirection: ?boolean,
104
+ event: Object,
105
+): void {
106
+ if (inst && event && event.dispatchConfig.registrationName) {
107
+ const registrationName = event.dispatchConfig.registrationName;
108
+ const listener = getListener(inst, registrationName);
109
+ if (listener) {
110
+ event._dispatchListeners = accumulateInto(
111
+ event._dispatchListeners,
112
+ listener,
113
+ );
114
+ event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
115
+ }
116
+ }
117
+}
118
+
119
+/**
120
+ * Accumulates dispatches on an `SyntheticEvent`, but only for the
121
+ * `dispatchMarker`.
122
+ * @param {SyntheticEvent} event
123
+ */
124
+function accumulateDirectDispatchesSingle(event: Object) {
125
+ if (event && event.dispatchConfig.registrationName) {
126
+ accumulateDispatches(event._targetInst, null, event);
127
+ }
128
+}
129
+
130
+function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
131
+ forEachAccumulated(events, accumulateDirectDispatchesSingle);
132
+}
133
+
134
// End of inline
135
type PropagationPhases = 'bubbled' | 'captured';
136
packages/react-reconciler/src/ReactTreeTraversal.js
deleted
-146
@@ -1,146 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import {HostComponent} from './ReactWorkTags';
9
-
10
-function getParent(inst) {
11
- do {
12
- inst = inst.return;
13
- // TODO: If this is a HostRoot we might want to bail out.
14
- // That is depending on if we want nested subtrees (layers) to bubble
15
- // events to their parent. We could also go through parentNode on the
16
- // host node but that wouldn't work for React Native and doesn't let us
17
- // do the portal feature.
18
- } while (inst && inst.tag !== HostComponent);
19
- if (inst) {
20
- return inst;
21
- }
22
- return null;
23
-}
24
-
25
-/**
26
- * Return the lowest common ancestor of A and B, or null if they are in
27
- * different trees.
28
- */
29
-export function getLowestCommonAncestor(instA, instB) {
30
- let depthA = 0;
31
- for (let tempA = instA; tempA; tempA = getParent(tempA)) {
32
- depthA++;
33
- }
34
- let depthB = 0;
35
- for (let tempB = instB; tempB; tempB = getParent(tempB)) {
36
- depthB++;
37
- }
38
-
39
- // If A is deeper, crawl up.
40
- while (depthA - depthB > 0) {
41
- instA = getParent(instA);
42
- depthA--;
43
- }
44
-
45
- // If B is deeper, crawl up.
46
- while (depthB - depthA > 0) {
47
- instB = getParent(instB);
48
- depthB--;
49
- }
50
-
51
- // Walk in lockstep until we find a match.
52
- let depth = depthA;
53
- while (depth--) {
54
- if (instA === instB || instA === instB.alternate) {
55
- return instA;
56
- }
57
- instA = getParent(instA);
58
- instB = getParent(instB);
59
- }
60
- return null;
61
-}
62
-
63
-/**
64
- * Return if A is an ancestor of B.
65
- */
66
-export function isAncestor(instA, instB) {
67
- while (instB) {
68
- if (instA === instB || instA === instB.alternate) {
69
- return true;
70
- }
71
- instB = getParent(instB);
72
- }
73
- return false;
74
-}
75
-
76
-/**
77
- * Return the parent instance of the passed-in instance.
78
- */
79
-export function getParentInstance(inst) {
80
- return getParent(inst);
81
-}
82
-
83
-/**
84
- * Simulates the traversal of a two-phase, capture/bubble event dispatch.
85
- */
86
-export function traverseTwoPhase(inst, fn, arg) {
87
- const path = [];
88
- while (inst) {
89
- path.push(inst);
90
- inst = getParent(inst);
91
- }
92
- let i;
93
- for (i = path.length; i-- > 0; ) {
94
- fn(path[i], 'captured', arg);
95
- }
96
- for (i = 0; i < path.length; i++) {
97
- fn(path[i], 'bubbled', arg);
98
- }
99
-}
100
-
101
-/**
102
- * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
103
- * should would receive a `mouseEnter` or `mouseLeave` event.
104
- *
105
- * Does not invoke the callback on the nearest common ancestor because nothing
106
- * "entered" or "left" that element.
107
- */
108
-export function traverseEnterLeave(from, to, fn, argFrom, argTo) {
109
- const common = from && to ? getLowestCommonAncestor(from, to) : null;
110
- const pathFrom = [];
111
- while (true) {
112
- if (!from) {
113
- break;
114
- }
115
- if (from === common) {
116
- break;
117
- }
118
- const alternate = from.alternate;
119
- if (alternate !== null && alternate === common) {
120
- break;
121
- }
122
- pathFrom.push(from);
123
- from = getParent(from);
124
- }
125
- const pathTo = [];
126
- while (true) {
127
- if (!to) {
128
- break;
129
- }
130
- if (to === common) {
131
- break;
132
- }
133
- const alternate = to.alternate;
134
- if (alternate !== null && alternate === common) {
135
- break;
136
- }
137
- pathTo.push(to);
138
- to = getParent(to);
139
- }
140
- for (let i = 0; i < pathFrom.length; i++) {
141
- fn(pathFrom[i], 'bubbled', argFrom);
142
- }
143
- for (let i = pathTo.length; i-- > 0; ) {
144
- fn(pathTo[i], 'captured', argTo);
145
- }
146
-}