main
js 173 lines 5.62 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 {AnyNativeEvent} from '../PluginModuleType';
11 import type {DOMEventName} from '../DOMEventNames';
12 import type {DispatchQueue} from '../DOMPluginEventSystem';
13 import type {EventSystemFlags} from '../EventSystemFlags';
14 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
15 import type {KnownReactSyntheticEvent} from '../ReactSyntheticEventType';
16
17 import {registerDirectEvent} from '../EventRegistry';
18 import {isReplayingEvent} from '../CurrentReplayingEvent';
19 import {SyntheticMouseEvent, SyntheticPointerEvent} from '../SyntheticEvent';
20 import {
21 getClosestInstanceFromNode,
22 getNodeFromInstance,
23 isContainerMarkedAsRoot,
24 } from '../../client/ReactDOMComponentTree';
25 import {accumulateEnterLeaveTwoPhaseListeners} from '../DOMPluginEventSystem';
26
27 import {
28 HostComponent,
29 HostSingleton,
30 HostText,
31 } from 'react-reconciler/src/ReactWorkTags';
32 import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
33
34 function registerEvents() {
35 registerDirectEvent('onMouseEnter', ['mouseout', 'mouseover']);
36 registerDirectEvent('onMouseLeave', ['mouseout', 'mouseover']);
37 registerDirectEvent('onPointerEnter', ['pointerout', 'pointerover']);
38 registerDirectEvent('onPointerLeave', ['pointerout', 'pointerover']);
39 }
40
41 /**
42 * For almost every interaction we care about, there will be both a top-level
43 * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
44 * we do not extract duplicate events. However, moving the mouse into the
45 * browser from outside will not fire a `mouseout` event. In this case, we use
46 * the `mouseover` top-level event.
47 */
48 function extractEvents(
49 dispatchQueue: DispatchQueue,
50 domEventName: DOMEventName,
51 targetInst: null | Fiber,
52 nativeEvent: AnyNativeEvent,
53 nativeEventTarget: null | EventTarget,
54 eventSystemFlags: EventSystemFlags,
55 targetContainer: EventTarget,
56 ) {
57 const isOverEvent =
58 domEventName === 'mouseover' || domEventName === 'pointerover';
59 const isOutEvent =
60 domEventName === 'mouseout' || domEventName === 'pointerout';
61
62 if (isOverEvent && !isReplayingEvent(nativeEvent)) {
63 // If this is an over event with a target, we might have already dispatched
64 // the event in the out event of the other target. If this is replayed,
65 // then it's because we couldn't dispatch against this target previously
66 // so we have to do it now instead.
67 const related =
68 (nativeEvent as any).relatedTarget || (nativeEvent as any).fromElement;
69 if (related) {
70 // If the related node is managed by React, we can assume that we have
71 // already dispatched the corresponding events during its mouseout.
72 if (
73 getClosestInstanceFromNode(related) ||
74 isContainerMarkedAsRoot(related)
75 ) {
76 return;
77 }
78 }
79 }
80
81 if (!isOutEvent && !isOverEvent) {
82 // Must not be a mouse or pointer in or out - ignoring.
83 return;
84 }
85
86 let win;
87 // TODO: why is this nullable in the types but we read from it?
88 if ((nativeEventTarget as any).window === nativeEventTarget) {
89 // `nativeEventTarget` is probably a window object.
90 win = nativeEventTarget;
91 } else {
92 // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
93 const doc = (nativeEventTarget as any).ownerDocument;
94 if (doc) {
95 win = doc.defaultView || doc.parentWindow;
96 } else {
97 win = window;
98 }
99 }
100
101 let from;
102 let to;
103 if (isOutEvent) {
104 const related = nativeEvent.relatedTarget || (nativeEvent as any).toElement;
105 from = targetInst;
106 to = related ? getClosestInstanceFromNode(related as any) : null;
107 if (to !== null) {
108 const nearestMounted = getNearestMountedFiber(to);
109 const tag = to.tag;
110 if (
111 to !== nearestMounted ||
112 (tag !== HostComponent && tag !== HostSingleton && tag !== HostText)
113 ) {
114 to = null;
115 }
116 }
117 } else {
118 // Moving to a node from outside the window.
119 from = null;
120 to = targetInst;
121 }
122
123 if (from === to) {
124 // Nothing pertains to our managed components.
125 return;
126 }
127
128 let SyntheticEventCtor = SyntheticMouseEvent;
129 let leaveEventType = 'onMouseLeave';
130 let enterEventType = 'onMouseEnter';
131 let eventTypePrefix = 'mouse';
132 if (domEventName === 'pointerout' || domEventName === 'pointerover') {
133 SyntheticEventCtor = SyntheticPointerEvent;
134 leaveEventType = 'onPointerLeave';
135 enterEventType = 'onPointerEnter';
136 eventTypePrefix = 'pointer';
137 }
138
139 const fromNode = from == null ? win : getNodeFromInstance(from);
140 const toNode = to == null ? win : getNodeFromInstance(to);
141
142 const leave: KnownReactSyntheticEvent = new SyntheticEventCtor(
143 leaveEventType,
144 eventTypePrefix + 'leave',
145 from,
146 nativeEvent,
147 nativeEventTarget,
148 );
149 leave.target = fromNode;
150 leave.relatedTarget = toNode;
151
152 let enter: KnownReactSyntheticEvent | null = null;
153
154 // We should only process this nativeEvent if we are processing
155 // the first ancestor. Next time, we will ignore the event.
156 const nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget as any);
157 if (nativeTargetInst === targetInst) {
158 const enterEvent: KnownReactSyntheticEvent = new SyntheticEventCtor(
159 enterEventType,
160 eventTypePrefix + 'enter',
161 to,
162 nativeEvent,
163 nativeEventTarget,
164 );
165 enterEvent.target = toNode;
166 enterEvent.relatedTarget = fromNode;
167 enter = enterEvent;
168 }
169
170 accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
171 }
172
173 export {registerEvents, extractEvents};