ReactDOM.useEvent: more scaffolding changes (#18282)
Dominic Gannaway committed
Mar 12, 2020 at 09:12 UTC
99d271228dc0831c9d613bab20032081a286e1c0
17 files changed
+209
-47
packages/legacy-events/PluginModuleType.js
+1
-1
@@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
29
nativeTarget: NativeEvent,
30
nativeEventTarget: null | EventTarget,
31
eventSystemFlags: EventSystemFlags,
32
- container?: Document | Element,
32
+ container?: EventTarget,
33
) => ?ReactSyntheticEvent,
34
tapMoveThreshold?: number,
35
};
packages/legacy-events/ReactSyntheticEventType.js
+1
@@ -34,4 +34,5 @@ export type ReactSyntheticEvent = {|
34
_dispatchInstances: null | Array<Fiber>,
35
_dispatchListeners: null | Array<Function>,
36
_targetInst: null | Fiber,
37
+ type: string,
38
|};
packages/react-art/src/ReactARTHostConfig.js
+12
@@ -469,3 +469,15 @@ export function getInstanceFromNode(node) {
469
export function beforeRemoveInstance(instance) {
470
// noop
471
}
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
+1
-1
@@ -44,7 +44,7 @@ type HookLogEntry = {
44
45
type ReactDebugListenerMap = {|
46
clear: () => void,
47
- setListener: (instance: EventTarget, callback: ?(Event) => void) => void,
47
+ setListener: (target: EventTarget, callback: ?(Event) => void) => void,
48
|};
49
50
let hookLog: Array<HookLogEntry> = [];
packages/react-dom/src/client/ReactDOMHostConfig.js
+65
@@ -66,12 +66,17 @@ import {
66
enableSuspenseServerRenderer,
67
enableDeprecatedFlareAPI,
68
enableFundamentalAPI,
69
+ enableUseEventAPI,
70
} from 'shared/ReactFeatureFlags';
71
import {HostComponent} from 'shared/ReactWorkTags';
72
import {
73
RESPONDER_EVENT_SYSTEM,
74
IS_PASSIVE,
75
} from 'legacy-events/EventSystemFlags';
76
+import {
77
+ attachElementListener,
78
+ detachElementListener,
79
+} from '../events/DOMModernPluginEventSystem';
80
81
export type ReactListenerEvent = ReactDOMListenerEvent;
82
export type ReactListenerMap = ReactDOMListenerMap;
@@ -1075,3 +1080,63 @@ export function unmountFundamentalComponent(
1080
export function getInstanceFromNode(node: HTMLElement): null | Object {
1081
return getClosestInstanceFromNode(node) || null;
1082
}
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
+4
-7
@@ -16,18 +16,15 @@ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
16
const elementListenerMap:
17
// $FlowFixMe Work around Flow bug
18
| 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();
20
21
export function getListenerMapForElement(
25
- element: Document | Element | Node,
22
+ target: EventTarget,
23
): Map<DOMTopLevelEventType | string, null | (any => void)> {
27
- let listenerMap = elementListenerMap.get(element);
24
+ let listenerMap = elementListenerMap.get(target);
25
if (listenerMap === undefined) {
26
listenerMap = new Map();
30
- elementListenerMap.set(element, listenerMap);
27
+ elementListenerMap.set(target, listenerMap);
28
}
29
return listenerMap;
30
}
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+13
-4
@@ -108,7 +108,7 @@ function dispatchEventsForPlugins(
108
eventSystemFlags: EventSystemFlags,
109
nativeEvent: AnyNativeEvent,
110
targetInst: null | Fiber,
111
- rootContainer: Element | Document,
111
+ rootContainer: EventTarget,
112
): void {
113
const nativeEventTarget = getEventTarget(nativeEvent);
114
const syntheticEvents: Array<ReactSyntheticEvent> = [];
@@ -209,7 +209,7 @@ function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
209
210
function isMatchingRootContainer(
211
grandContainer: Element,
212
- rootContainer: Document | Element,
212
+ rootContainer: EventTarget,
213
): boolean {
214
return (
215
grandContainer === rootContainer ||
@@ -223,10 +223,19 @@ export function dispatchEventForPluginEventSystem(
223
eventSystemFlags: EventSystemFlags,
224
nativeEvent: AnyNativeEvent,
225
targetInst: null | Fiber,
226
- rootContainer: Document | Element,
226
+ rootContainer: EventTarget,
227
): void {
228
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
+ ) {
239
// If we detect the FB legacy primer system, we
240
// defer the event to the "document" with a one
241
// time event listener so we can defer the event.
packages/react-dom/src/events/EventListener.js
+17
-6
@@ -8,29 +8,40 @@
8
*/
9
10
export function addEventBubbleListener(
11
- element: Document | Element | Node,
11
+ target: EventTarget,
12
eventType: string,
13
listener: Function,
14
): void {
15
- element.addEventListener(eventType, listener, false);
15
+ target.addEventListener(eventType, listener, false);
16
}
17
18
export function addEventCaptureListener(
19
- element: Document | Element | Node,
19
+ target: EventTarget,
20
eventType: string,
21
listener: Function,
22
): void {
23
- element.addEventListener(eventType, listener, true);
23
+ target.addEventListener(eventType, listener, true);
24
}
25
26
export function addEventCaptureListenerWithPassiveFlag(
27
- element: Document | Element | Node,
27
+ target: EventTarget,
28
eventType: string,
29
listener: Function,
30
passive: boolean,
31
): void {
32
- element.addEventListener(eventType, listener, {
32
+ target.addEventListener(eventType, listener, {
33
capture: true,
34
passive,
35
});
36
}
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
+18
-10
@@ -130,7 +130,7 @@ export function addResponderEventSystemEvent(
130
}
131
132
export function addTrappedEventListener(
133
- container: Document | Element,
133
+ targetContainer: EventTarget,
134
topLevelType: DOMTopLevelEventType,
135
capture: boolean,
136
legacyFBSupport?: boolean,
@@ -153,7 +153,7 @@ export function addTrappedEventListener(
153
null,
154
topLevelType,
155
PLUGIN_EVENT_SYSTEM,
156
- container,
156
+ targetContainer,
157
);
158
159
const rawEventName = getRawEventName(topLevelType);
@@ -179,7 +179,7 @@ export function addTrappedEventListener(
179
if (fbListener) {
180
fbListener.remove();
181
} else {
182
- container.removeEventListener(
182
+ targetContainer.removeEventListener(
183
((rawEventName: any): string),
184
(listener: any),
185
);
@@ -188,9 +188,17 @@ export function addTrappedEventListener(
188
};
189
}
190
if (capture) {
191
- fbListener = addEventCaptureListener(container, rawEventName, listener);
191
+ fbListener = addEventCaptureListener(
192
+ targetContainer,
193
+ rawEventName,
194
+ listener,
195
+ );
196
} else {
193
- fbListener = addEventBubbleListener(container, rawEventName, listener);
197
+ fbListener = addEventBubbleListener(
198
+ targetContainer,
199
+ rawEventName,
200
+ listener,
201
+ );
202
}
203
// If we have an fbListener, then use that.
204
// We'll only have one if we use the forked
@@ -199,7 +207,7 @@ export function addTrappedEventListener(
207
}
208
209
export function removeTrappedPassiveEventListener(
202
- document: Document,
210
+ targetContainer: EventTarget,
211
topLevelType: string,
212
listener: any => void,
213
) {
@@ -207,12 +215,12 @@ export function removeTrappedPassiveEventListener(
215
listener.remove();
216
} else {
217
if (passiveBrowserEventsSupported) {
210
- document.removeEventListener(topLevelType, listener, {
218
+ targetContainer.removeEventListener(topLevelType, listener, {
219
capture: true,
220
passive: true,
221
});
222
} else {
215
- document.removeEventListener(topLevelType, listener, true);
223
+ targetContainer.removeEventListener(topLevelType, listener, true);
224
}
225
}
226
}
@@ -254,7 +262,7 @@ function dispatchUserBlockingUpdate(
262
export function dispatchEvent(
263
topLevelType: DOMTopLevelEventType,
264
eventSystemFlags: EventSystemFlags,
257
- container: Document | Element,
265
+ container: EventTarget,
266
nativeEvent: AnyNativeEvent,
267
): void {
268
if (!_enabled) {
@@ -370,7 +378,7 @@ export function dispatchEvent(
378
export function attemptToDispatchEvent(
379
topLevelType: DOMTopLevelEventType,
380
eventSystemFlags: EventSystemFlags,
373
- container: Document | Element,
381
+ container: EventTarget,
382
nativeEvent: AnyNativeEvent,
383
): null | Container | SuspenseInstance {
384
// TODO: Warn if _enabled is false.
packages/react-dom/src/events/ReactDOMEventReplaying.js
+5
-5
@@ -126,7 +126,7 @@ type QueuedReplayableEvent = {|
126
topLevelType: DOMTopLevelEventType,
127
eventSystemFlags: EventSystemFlags,
128
nativeEvent: AnyNativeEvent,
129
- container: Document | Element,
129
+ container: EventTarget,
130
|};
131
132
let hasScheduledReplayAttempt = false;
@@ -285,7 +285,7 @@ function createQueuedReplayableEvent(
285
blockedOn: null | Container | SuspenseInstance,
286
topLevelType: DOMTopLevelEventType,
287
eventSystemFlags: EventSystemFlags,
288
- container: Document | Element,
288
+ container: EventTarget,
289
nativeEvent: AnyNativeEvent,
290
): QueuedReplayableEvent {
291
return {
@@ -301,7 +301,7 @@ export function queueDiscreteEvent(
301
blockedOn: null | Container | SuspenseInstance,
302
topLevelType: DOMTopLevelEventType,
303
eventSystemFlags: EventSystemFlags,
304
- container: Document | Element,
304
+ container: EventTarget,
305
nativeEvent: AnyNativeEvent,
306
): void {
307
const queuedEvent = createQueuedReplayableEvent(
@@ -376,7 +376,7 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
376
blockedOn: null | Container | SuspenseInstance,
377
topLevelType: DOMTopLevelEventType,
378
eventSystemFlags: EventSystemFlags,
379
- container: Document | Element,
379
+ container: EventTarget,
380
nativeEvent: AnyNativeEvent,
381
): QueuedReplayableEvent {
382
if (
@@ -411,7 +411,7 @@ export function queueIfContinuousEvent(
411
blockedOn: null | Container | SuspenseInstance,
412
topLevelType: DOMTopLevelEventType,
413
eventSystemFlags: EventSystemFlags,
414
- container: Document | Element,
414
+ container: EventTarget,
415
nativeEvent: AnyNativeEvent,
416
): boolean {
417
// These set relatedTarget to null because the replayed event will be treated as if we
packages/react-dom/src/events/forks/EventListener-www.js
+20
-6
@@ -13,29 +13,43 @@ import typeof * as EventListenerType from '../EventListener';
13
import typeof * as EventListenerShimType from './EventListener-www';
14
15
export function addEventBubbleListener(
16
- element: Element,
16
+ target: EventTarget,
17
eventType: string,
18
listener: Function,
19
) {
20
- return EventListenerWWW.listen(element, eventType, listener);
20
+ return EventListenerWWW.listen(target, eventType, listener);
21
}
22
23
export function addEventCaptureListener(
24
- element: Element,
24
+ target: EventTarget,
25
eventType: string,
26
listener: Function,
27
) {
28
- return EventListenerWWW.capture(element, eventType, listener);
28
+ return EventListenerWWW.capture(target, eventType, listener);
29
}
30
31
export function addEventCaptureListenerWithPassiveFlag(
32
- element: Element,
32
+ target: EventTarget,
33
eventType: string,
34
listener: Function,
35
passive: boolean,
36
) {
37
return EventListenerWWW.captureWithPassiveFlag(
38
- element,
38
+ target,
39
+ eventType,
40
+ listener,
41
+ passive,
42
+ );
43
+}
44
+
45
+export function addEventBubbleListenerWithPassiveFlag(
46
+ target: EventTarget,
47
+ eventType: string,
48
+ listener: Function,
49
+ passive: boolean,
50
+) {
51
+ return EventListenerWWW.bubbleWithPassiveFlag(
52
+ target,
53
eventType,
54
listener,
55
passive,
packages/react-native-renderer/src/ReactFabricHostConfig.js
+12
@@ -473,3 +473,15 @@ export function getInstanceFromNode(node: any) {
473
export function beforeRemoveInstance(instance: any) {
474
// noop
475
}
476
+
477
+export function mountEventListener(listener: any) {
478
+ throw new Error('Not yet implemented.');
479
+}
480
+
481
+export function unmountEventListener(listener: any) {
482
+ throw new Error('Not yet implemented.');
483
+}
484
+
485
+export function validateEventListenerTarget(target: any, listener: any) {
486
+ throw new Error('Not yet implemented.');
487
+}
packages/react-native-renderer/src/ReactNativeHostConfig.js
+12
@@ -517,3 +517,15 @@ export function getInstanceFromNode(node: any) {
517
export function beforeRemoveInstance(instance: any) {
518
// noop
519
}
520
+
521
+export function mountEventListener(listener: any) {
522
+ throw new Error('Not yet implemented.');
523
+}
524
+
525
+export function unmountEventListener(listener: any) {
526
+ throw new Error('Not yet implemented.');
527
+}
528
+
529
+export function validateEventListenerTarget(target: any, listener: any) {
530
+ throw new Error('Not yet implemented.');
531
+}
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+4
@@ -76,6 +76,10 @@ export const shouldUpdateFundamentalComponent =
76
$$$hostConfig.shouldUpdateFundamentalComponent;
77
export const getInstanceFromNode = $$$hostConfig.getInstanceFromNode;
78
export const beforeRemoveInstance = $$$hostConfig.beforeRemoveInstance;
79
+export const mountEventListener = $$$hostConfig.mountEventListener;
80
+export const unmountEventListener = $$$hostConfig.unmountEventListener;
81
+export const validateEventListenerTarget =
82
+ $$$hostConfig.validateEventListenerTarget;
83
84
// -------------------
85
// Mutation
packages/react-test-renderer/src/ReactTestHostConfig.js
+12
@@ -379,3 +379,15 @@ export function getInstanceFromNode(mockNode: Object) {
379
export function beforeRemoveInstance(instance: any) {
380
// noop
381
}
382
+
383
+export function mountEventListener(listener: any) {
384
+ throw new Error('Not yet implemented.');
385
+}
386
+
387
+export function unmountEventListener(listener: any) {
388
+ throw new Error('Not yet implemented.');
389
+}
390
+
391
+export function validateEventListenerTarget(target: any, listener: any) {
392
+ throw new Error('Not yet implemented.');
393
+}
packages/shared/ReactDOMTypes.js
+3
-4
@@ -85,13 +85,12 @@ export type ReactDOMListenerEvent = {|
85
86
export type ReactDOMListenerMap = {|
87
clear: () => void,
88
- setListener: (instance: EventTarget, callback: ?(Event) => void) => void,
88
+ setListener: (target: EventTarget, callback: ?(Event) => void) => void,
89
|};
90
91
export type ReactDOMListener = {|
92
callback: Event => void,
93
- depth: number,
94
- destroy: Document | (Element => void),
93
+ destroy: Node => void,
94
event: ReactDOMListenerEvent,
96
- instance: EventTarget,
95
+ target: EventTarget,
96
|};
scripts/flow/environment.js
+9
-3
@@ -44,15 +44,21 @@ declare module 'ReactFiberErrorDialog' {
44
declare module 'EventListener' {
45
declare module.exports: {
46
listen: (
47
- target: Element,
47
+ target: EventTarget,
48
type: string,
49
callback: Function,
50
priority?: number,
51
options?: {passive: boolean, ...},
52
) => mixed,
53
- capture: (target: Element, type: string, callback: Function) => mixed,
53
+ capture: (target: EventTarget, type: string, callback: Function) => mixed,
54
captureWithPassiveFlag: (
55
- target: Element,
55
+ target: EventTarget,
56
+ type: string,
57
+ callback: Function,
58
+ passive: boolean,
59
+ ) => mixed,
60
+ bubbleWithPassiveFlag: (
61
+ target: EventTarget,
62
type: string,
63
callback: Function,
64
passive: boolean,