@samitouri / QOS-React-2 / commits / 99d271228d

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,