Skip to content

Commit 4a5ff56

Browse files
committed
ReactDOM.useEvent: more scaffolding changes (facebook#18282)
1 parent 4ca9ce9 commit 4a5ff56

File tree

17 files changed

+209
-47
lines changed

17 files changed

+209
-47
lines changed

packages/legacy-events/PluginModuleType.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
2929
nativeTarget: NativeEvent,
3030
nativeEventTarget: null | EventTarget,
3131
eventSystemFlags: EventSystemFlags,
32-
container?: Document | Element,
32+
container?: EventTarget,
3333
) => ?ReactSyntheticEvent,
3434
tapMoveThreshold?: number,
3535
};

packages/legacy-events/ReactSyntheticEventType.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ export type ReactSyntheticEvent = {|
3434
_dispatchInstances: null | Array<Fiber>,
3535
_dispatchListeners: null | Array<Function>,
3636
_targetInst: null | Fiber,
37+
type: string,
3738
|};

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,15 @@ export function getInstanceFromNode(node) {
469469
export function beforeRemoveInstance(instance) {
470470
// noop
471471
}
472+
473+
export function mountEventListener(listener: any) {
474+
throw new Error('Not yet implemented.');
475+
}
476+
477+
export function unmountEventListener(listener: any) {
478+
throw new Error('Not yet implemented.');
479+
}
480+
481+
export function validateEventListenerTarget(target: any, listener: any) {
482+
throw new Error('Not yet implemented.');
483+
}

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type HookLogEntry = {
4444

4545
type ReactDebugListenerMap = {|
4646
clear: () => void,
47-
setListener: (instance: EventTarget, callback: ?(Event) => void) => void,
47+
setListener: (target: EventTarget, callback: ?(Event) => void) => void,
4848
|};
4949

5050
let hookLog: Array<HookLogEntry> = [];

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,17 @@ import {
6666
enableSuspenseServerRenderer,
6767
enableDeprecatedFlareAPI,
6868
enableFundamentalAPI,
69+
enableUseEventAPI,
6970
} from 'shared/ReactFeatureFlags';
7071
import {HostComponent} from 'shared/ReactWorkTags';
7172
import {
7273
RESPONDER_EVENT_SYSTEM,
7374
IS_PASSIVE,
7475
} from 'legacy-events/EventSystemFlags';
76+
import {
77+
attachElementListener,
78+
detachElementListener,
79+
} from '../events/DOMModernPluginEventSystem';
7580

7681
export type ReactListenerEvent = ReactDOMListenerEvent;
7782
export type ReactListenerMap = ReactDOMListenerMap;
@@ -1075,3 +1080,63 @@ export function unmountFundamentalComponent(
10751080
export function getInstanceFromNode(node: HTMLElement): null | Object {
10761081
return getClosestInstanceFromNode(node) || null;
10771082
}
1083+
1084+
export function mountEventListener(listener: ReactDOMListener): void {
1085+
if (enableUseEventAPI) {
1086+
const {target} = listener;
1087+
if (target === window) {
1088+
// TODO (useEvent)
1089+
} else {
1090+
attachElementListener(listener);
1091+
}
1092+
}
1093+
}
1094+
1095+
export function unmountEventListener(listener: ReactDOMListener): void {
1096+
if (enableUseEventAPI) {
1097+
const {target} = listener;
1098+
if (target === window) {
1099+
// TODO (useEvent)
1100+
} else {
1101+
detachElementListener(listener);
1102+
}
1103+
}
1104+
}
1105+
1106+
export function validateEventListenerTarget(
1107+
target: EventTarget,
1108+
listener: ?(Event) => void,
1109+
): boolean {
1110+
if (enableUseEventAPI) {
1111+
if (
1112+
target &&
1113+
(target === window || getClosestInstanceFromNode(((target: any): Node)))
1114+
) {
1115+
if (listener == null || typeof listener === 'function') {
1116+
return true;
1117+
}
1118+
if (__DEV__) {
1119+
console.warn(
1120+
'Event listener method setListener() from useEvent() hook requires the second argument' +
1121+
' to be either a valid function callback or null/undefined.',
1122+
);
1123+
}
1124+
}
1125+
if (__DEV__) {
1126+
if (target && (target: any).nodeType === DOCUMENT_NODE) {
1127+
console.warn(
1128+
'Event listener method setListener() from useEvent() hook requires the first argument to be a valid' +
1129+
' DOM node that was rendered and managed by React or a "window" object. It looks like' +
1130+
' you supplied a "document" node, instead use the "window" object.',
1131+
);
1132+
} else {
1133+
console.warn(
1134+
'Event listener method setListener() from useEvent() hook requires the first argument to be a valid' +
1135+
' DOM node that was rendered and managed by React or a "window" object. If this is' +
1136+
' from a ref, ensure the ref value has been set before attaching.',
1137+
);
1138+
}
1139+
}
1140+
}
1141+
return false;
1142+
}

packages/react-dom/src/events/DOMEventListenerMap.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,15 @@ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
1616
const elementListenerMap:
1717
// $FlowFixMe Work around Flow bug
1818
| WeakMap
19-
| Map<
20-
Document | Element | Node,
21-
Map<DOMTopLevelEventType | string, null | (any => void)>,
22-
> = new PossiblyWeakMap();
19+
| Map<EventTarget, Map<DOMTopLevelEventType | string, null | (any => void)>> = new PossiblyWeakMap();
2320

2421
export function getListenerMapForElement(
25-
element: Document | Element | Node,
22+
target: EventTarget,
2623
): Map<DOMTopLevelEventType | string, null | (any => void)> {
27-
let listenerMap = elementListenerMap.get(element);
24+
let listenerMap = elementListenerMap.get(target);
2825
if (listenerMap === undefined) {
2926
listenerMap = new Map();
30-
elementListenerMap.set(element, listenerMap);
27+
elementListenerMap.set(target, listenerMap);
3128
}
3229
return listenerMap;
3330
}

packages/react-dom/src/events/DOMModernPluginEventSystem.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function dispatchEventsForPlugins(
108108
eventSystemFlags: EventSystemFlags,
109109
nativeEvent: AnyNativeEvent,
110110
targetInst: null | Fiber,
111-
rootContainer: Element | Document,
111+
rootContainer: EventTarget,
112112
): void {
113113
const nativeEventTarget = getEventTarget(nativeEvent);
114114
const syntheticEvents: Array<ReactSyntheticEvent> = [];
@@ -209,7 +209,7 @@ function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
209209

210210
function isMatchingRootContainer(
211211
grandContainer: Element,
212-
rootContainer: Document | Element,
212+
rootContainer: EventTarget,
213213
): boolean {
214214
return (
215215
grandContainer === rootContainer ||
@@ -223,10 +223,19 @@ export function dispatchEventForPluginEventSystem(
223223
eventSystemFlags: EventSystemFlags,
224224
nativeEvent: AnyNativeEvent,
225225
targetInst: null | Fiber,
226-
rootContainer: Document | Element,
226+
rootContainer: EventTarget,
227227
): void {
228228
let ancestorInst = targetInst;
229-
if (rootContainer.nodeType !== DOCUMENT_NODE) {
229+
// Given the rootContainer can be any EventTarget, if the
230+
// target is that of a DOM node (other than the document)
231+
// then we'll attempt to find the correct ancestor root.
232+
// Note: the rootContainer can be other things like
233+
// "window" or other valid EventTarget objects.
234+
const possibleContainerNodeType = ((rootContainer: any): Node).nodeType;
235+
if (
236+
possibleContainerNodeType !== undefined &&
237+
possibleContainerNodeType !== DOCUMENT_NODE
238+
) {
230239
// If we detect the FB legacy primer system, we
231240
// defer the event to the "document" with a one
232241
// time event listener so we can defer the event.

packages/react-dom/src/events/EventListener.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,40 @@
88
*/
99

1010
export function addEventBubbleListener(
11-
element: Document | Element | Node,
11+
target: EventTarget,
1212
eventType: string,
1313
listener: Function,
1414
): void {
15-
element.addEventListener(eventType, listener, false);
15+
target.addEventListener(eventType, listener, false);
1616
}
1717

1818
export function addEventCaptureListener(
19-
element: Document | Element | Node,
19+
target: EventTarget,
2020
eventType: string,
2121
listener: Function,
2222
): void {
23-
element.addEventListener(eventType, listener, true);
23+
target.addEventListener(eventType, listener, true);
2424
}
2525

2626
export function addEventCaptureListenerWithPassiveFlag(
27-
element: Document | Element | Node,
27+
target: EventTarget,
2828
eventType: string,
2929
listener: Function,
3030
passive: boolean,
3131
): void {
32-
element.addEventListener(eventType, listener, {
32+
target.addEventListener(eventType, listener, {
3333
capture: true,
3434
passive,
3535
});
3636
}
37+
38+
export function addEventBubbleListenerWithPassiveFlag(
39+
target: EventTarget,
40+
eventType: string,
41+
listener: Function,
42+
passive: boolean,
43+
): void {
44+
target.addEventListener(eventType, listener, {
45+
passive,
46+
});
47+
}

packages/react-dom/src/events/ReactDOMEventListener.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export function addResponderEventSystemEvent(
130130
}
131131

132132
export function addTrappedEventListener(
133-
container: Document | Element,
133+
targetContainer: EventTarget,
134134
topLevelType: DOMTopLevelEventType,
135135
capture: boolean,
136136
legacyFBSupport?: boolean,
@@ -153,7 +153,7 @@ export function addTrappedEventListener(
153153
null,
154154
topLevelType,
155155
PLUGIN_EVENT_SYSTEM,
156-
container,
156+
targetContainer,
157157
);
158158

159159
const rawEventName = getRawEventName(topLevelType);
@@ -179,7 +179,7 @@ export function addTrappedEventListener(
179179
if (fbListener) {
180180
fbListener.remove();
181181
} else {
182-
container.removeEventListener(
182+
targetContainer.removeEventListener(
183183
((rawEventName: any): string),
184184
(listener: any),
185185
);
@@ -188,9 +188,17 @@ export function addTrappedEventListener(
188188
};
189189
}
190190
if (capture) {
191-
fbListener = addEventCaptureListener(container, rawEventName, listener);
191+
fbListener = addEventCaptureListener(
192+
targetContainer,
193+
rawEventName,
194+
listener,
195+
);
192196
} else {
193-
fbListener = addEventBubbleListener(container, rawEventName, listener);
197+
fbListener = addEventBubbleListener(
198+
targetContainer,
199+
rawEventName,
200+
listener,
201+
);
194202
}
195203
// If we have an fbListener, then use that.
196204
// We'll only have one if we use the forked
@@ -199,20 +207,20 @@ export function addTrappedEventListener(
199207
}
200208

201209
export function removeTrappedPassiveEventListener(
202-
document: Document,
210+
targetContainer: EventTarget,
203211
topLevelType: string,
204212
listener: any => void,
205213
) {
206214
if (listener.remove != null) {
207215
listener.remove();
208216
} else {
209217
if (passiveBrowserEventsSupported) {
210-
document.removeEventListener(topLevelType, listener, {
218+
targetContainer.removeEventListener(topLevelType, listener, {
211219
capture: true,
212220
passive: true,
213221
});
214222
} else {
215-
document.removeEventListener(topLevelType, listener, true);
223+
targetContainer.removeEventListener(topLevelType, listener, true);
216224
}
217225
}
218226
}
@@ -254,7 +262,7 @@ function dispatchUserBlockingUpdate(
254262
export function dispatchEvent(
255263
topLevelType: DOMTopLevelEventType,
256264
eventSystemFlags: EventSystemFlags,
257-
container: Document | Element,
265+
container: EventTarget,
258266
nativeEvent: AnyNativeEvent,
259267
): void {
260268
if (!_enabled) {
@@ -370,7 +378,7 @@ export function dispatchEvent(
370378
export function attemptToDispatchEvent(
371379
topLevelType: DOMTopLevelEventType,
372380
eventSystemFlags: EventSystemFlags,
373-
container: Document | Element,
381+
container: EventTarget,
374382
nativeEvent: AnyNativeEvent,
375383
): null | Container | SuspenseInstance {
376384
// TODO: Warn if _enabled is false.

packages/react-dom/src/events/ReactDOMEventReplaying.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ type QueuedReplayableEvent = {|
126126
topLevelType: DOMTopLevelEventType,
127127
eventSystemFlags: EventSystemFlags,
128128
nativeEvent: AnyNativeEvent,
129-
container: Document | Element,
129+
container: EventTarget,
130130
|};
131131

132132
let hasScheduledReplayAttempt = false;
@@ -285,7 +285,7 @@ function createQueuedReplayableEvent(
285285
blockedOn: null | Container | SuspenseInstance,
286286
topLevelType: DOMTopLevelEventType,
287287
eventSystemFlags: EventSystemFlags,
288-
container: Document | Element,
288+
container: EventTarget,
289289
nativeEvent: AnyNativeEvent,
290290
): QueuedReplayableEvent {
291291
return {
@@ -301,7 +301,7 @@ export function queueDiscreteEvent(
301301
blockedOn: null | Container | SuspenseInstance,
302302
topLevelType: DOMTopLevelEventType,
303303
eventSystemFlags: EventSystemFlags,
304-
container: Document | Element,
304+
container: EventTarget,
305305
nativeEvent: AnyNativeEvent,
306306
): void {
307307
const queuedEvent = createQueuedReplayableEvent(
@@ -376,7 +376,7 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
376376
blockedOn: null | Container | SuspenseInstance,
377377
topLevelType: DOMTopLevelEventType,
378378
eventSystemFlags: EventSystemFlags,
379-
container: Document | Element,
379+
container: EventTarget,
380380
nativeEvent: AnyNativeEvent,
381381
): QueuedReplayableEvent {
382382
if (
@@ -411,7 +411,7 @@ export function queueIfContinuousEvent(
411411
blockedOn: null | Container | SuspenseInstance,
412412
topLevelType: DOMTopLevelEventType,
413413
eventSystemFlags: EventSystemFlags,
414-
container: Document | Element,
414+
container: EventTarget,
415415
nativeEvent: AnyNativeEvent,
416416
): boolean {
417417
// These set relatedTarget to null because the replayed event will be treated as if we

0 commit comments

Comments
 (0)