Remove ReactDOM.useEvent and associated types+tests (#18689)
Dominic Gannaway committed
Apr 21, 2020 at 16:40 UTC
ff431b7fc43ee9022db094a673afbac7088bcfa2
37 files changed
+31
-4152
packages/react-art/src/ReactARTHostConfig.js
-16
@@ -493,22 +493,6 @@ export function makeServerId(): OpaqueIDType {
493
throw new Error('Not yet implemented');
494
}
495
496
-export function registerEvent(event: any, rootContainerInstance: any) {
497
- throw new Error('Not yet implemented.');
498
-}
499
-
500
-export function mountEventListener(listener: any) {
501
- throw new Error('Not yet implemented.');
502
-}
503
-
504
-export function unmountEventListener(listener: any) {
505
- throw new Error('Not yet implemented.');
506
-}
507
-
508
-export function validateEventListenerTarget(target: any, listener: any) {
509
- throw new Error('Not yet implemented.');
510
-}
511
-
496
export function beforeActiveInstanceBlur() {
497
// noop
498
}
packages/react-debug-tools/src/ReactDebugHooks.js
-20
@@ -15,7 +15,6 @@ import type {
15
ReactProviderType,
16
ReactEventResponder,
17
ReactEventResponderListener,
18
- ReactScopeMethods,
18
} from 'shared/ReactTypes';
19
import type {
20
Fiber,
@@ -48,14 +47,6 @@ type HookLogEntry = {
47
...
48
};
49
51
-type ReactDebugListenerMap = {|
52
- clear: () => void,
53
- setListener: (
54
- target: EventTarget | ReactScopeMethods,
55
- callback: ?(SyntheticEvent<EventTarget>) => void,
56
- ) => void,
57
-|};
58
-
50
let hookLog: Array<HookLogEntry> = [];
51
52
// Primitives
@@ -311,16 +302,6 @@ function useTransition(
302
return [callback => {}, false];
303
}
304
314
-const noOp = () => {};
315
-
316
-function useEvent(event: any): ReactDebugListenerMap {
317
- hookLog.push({primitive: 'Event', stackError: new Error(), value: event});
318
- return {
319
- clear: noOp,
320
- setListener: noOp,
321
- };
322
-}
323
-
305
function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
306
// useDeferredValue() composes multiple hooks internally.
307
// Advance the current hook index the same number of times
@@ -368,7 +349,6 @@ const Dispatcher: DispatcherType = {
349
useTransition,
350
useMutableSource,
351
useDeferredValue,
371
- useEvent,
352
useOpaqueIdentifier,
353
};
354
packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js
-44
@@ -11,7 +11,6 @@
11
'use strict';
12
13
let React;
14
-let ReactDOM;
14
let ReactDebugTools;
15
16
describe('ReactHooksInspection', () => {
@@ -19,9 +18,7 @@ describe('ReactHooksInspection', () => {
18
jest.resetModules();
19
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
20
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
22
- ReactFeatureFlags.enableUseEventAPI = true;
21
React = require('react');
24
- ReactDOM = require('react-dom');
22
ReactDebugTools = require('react-debug-tools');
23
});
24
@@ -46,45 +43,4 @@ describe('ReactHooksInspection', () => {
43
},
44
]);
45
});
49
-
50
- // @gate experimental
51
- it('should inspect a simple ReactDOM.useEvent hook', () => {
52
- let clickHandle;
53
- let ref;
54
-
55
- const effect = () => {
56
- clickHandle.setListener(ref.current, () => {});
57
- };
58
-
59
- function Foo(props) {
60
- ref = React.useRef(null);
61
- clickHandle = ReactDOM.unstable_useEvent('click');
62
- React.useEffect(effect);
63
- return <div ref={ref}>Hello world</div>;
64
- }
65
- const tree = ReactDebugTools.inspectHooks(Foo, {});
66
- expect(tree).toEqual([
67
- {
68
- isStateEditable: false,
69
- id: 0,
70
- name: 'Ref',
71
- subHooks: [],
72
- value: null,
73
- },
74
- {
75
- isStateEditable: false,
76
- id: 1,
77
- name: 'Event',
78
- value: {capture: false, passive: undefined, priority: 0, type: 'click'},
79
- subHooks: [],
80
- },
81
- {
82
- isStateEditable: false,
83
- id: 2,
84
- name: 'Effect',
85
- value: effect,
86
- subHooks: [],
87
- },
88
- ]);
89
- });
46
});
packages/react-dom/index.classic.fb.js
-1
@@ -36,5 +36,4 @@ export {
36
unstable_scheduleHydration,
37
unstable_renderSubtreeIntoContainer,
38
unstable_createPortal,
39
- unstable_useEvent,
39
} from './src/client/ReactDOM';
packages/react-dom/index.modern.fb.js
-1
@@ -19,5 +19,4 @@ export {
19
unstable_flushDiscreteUpdates,
20
unstable_flushControlled,
21
unstable_scheduleHydration,
22
- unstable_useEvent,
22
} from './src/client/ReactDOM';
packages/react-dom/src/client/ReactDOM.js
-3
@@ -20,7 +20,6 @@ import {
20
unmountComponentAtNode,
21
} from './ReactDOMLegacy';
22
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
23
-import {useEvent} from './ReactDOMUseEvent';
23
24
import {
25
batchedEventUpdates,
@@ -211,8 +210,6 @@ export {
210
// Temporary alias since we already shipped React 16 RC with it.
211
// TODO: remove in React 17.
212
unstable_createPortal,
214
- // enableUseEventAPI
215
- useEvent as unstable_useEvent,
213
};
214
215
const foundDevTools = injectIntoDevTools({
packages/react-dom/src/client/ReactDOMComponentTree.js
-17
@@ -15,7 +15,6 @@ import type {
15
SuspenseInstance,
16
Props,
17
} from './ReactDOMHostConfig';
18
-import type {ReactDOMListener} from '../shared/ReactDOMTypes';
18
19
import {
20
HostComponent,
@@ -33,7 +32,6 @@ const randomKey = Math.random()
32
const internalInstanceKey = '__reactFiber$' + randomKey;
33
const internalEventHandlersKey = '__reactEvents$' + randomKey;
34
const internalContainerInstanceKey = '__reactContainer$' + randomKey;
36
-const internalEventListenersKey = '__reactListeners$' + randomKey;
35
36
export function precacheFiberNode(
37
hostInst: Fiber,
@@ -187,18 +185,3 @@ export function updateFiberProps(
185
): void {
186
(node: any)[internalEventHandlersKey] = props;
187
}
190
-
191
-// This is used for useEvent listeners
192
-export function getListenersFromTarget(
193
- target: EventTarget,
194
-): null | Set<ReactDOMListener> {
195
- return (target: any)[internalEventListenersKey] || null;
196
-}
197
-
198
-// This is used for useEvent listeners
199
-export function initListenersSet(
200
- target: EventTarget,
201
- value: Set<ReactDOMListener>,
202
-): void {
203
- (target: any)[internalEventListenersKey] = value;
204
-}
packages/react-dom/src/client/ReactDOMHostConfig.js
+7
-125
@@ -13,17 +13,12 @@ import type {
13
ReactDOMEventResponder,
14
ReactDOMEventResponderInstance,
15
ReactDOMFundamentalComponentInstance,
16
- ReactDOMListener,
17
- ReactDOMListenerEvent,
18
- ReactDOMListenerMap,
16
} from '../shared/ReactDOMTypes';
20
-import type {ReactScopeMethods} from 'shared/ReactTypes';
17
18
import {
19
precacheFiberNode,
20
updateFiberProps,
21
getClosestInstanceFromNode,
26
- getListenersFromTarget,
22
} from './ReactDOMComponentTree';
23
import {
24
createElement,
@@ -69,32 +64,9 @@ import {
64
enableSuspenseServerRenderer,
65
enableDeprecatedFlareAPI,
66
enableFundamentalAPI,
72
- enableUseEventAPI,
73
- enableScopeAPI,
67
} from 'shared/ReactFeatureFlags';
75
-import {
76
- PLUGIN_EVENT_SYSTEM,
77
- USE_EVENT_SYSTEM,
78
-} from '../events/EventSystemFlags';
79
-import {
80
- isManagedDOMElement,
81
- isValidEventTarget,
82
- listenToTopLevelEvent,
83
- attachListenerToManagedDOMElement,
84
- detachListenerFromManagedDOMElement,
85
- attachTargetEventListener,
86
- detachTargetEventListener,
87
- isReactScope,
88
- attachListenerToReactScope,
89
- detachListenerFromReactScope,
90
-} from '../events/DOMModernPluginEventSystem';
91
-import {getListenerMapForElement} from '../events/DOMEventListenerMap';
68
import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
69
94
-export type ReactListenerEvent = ReactDOMListenerEvent;
95
-export type ReactListenerMap = ReactDOMListenerMap;
96
-export type ReactListener = ReactDOMListener;
97
-
70
export type Type = string;
71
export type Props = {
72
autoFocus?: boolean,
@@ -246,7 +218,7 @@ export function prepareForCommit(containerInfo: Container): Object | null {
218
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
219
selectionInformation = getSelectionInformation();
220
let activeInstance = null;
249
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
221
+ if (enableDeprecatedFlareAPI) {
222
const focusedElem = selectionInformation.focusedElem;
223
if (focusedElem !== null) {
224
activeInstance = getClosestInstanceFromNode(focusedElem);
@@ -257,7 +229,7 @@ export function prepareForCommit(containerInfo: Container): Object | null {
229
}
230
231
export function beforeActiveInstanceBlur(): void {
260
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
232
+ if (enableDeprecatedFlareAPI) {
233
ReactBrowserEventEmitterSetEnabled(true);
234
dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem);
235
ReactBrowserEventEmitterSetEnabled(false);
@@ -265,7 +237,7 @@ export function beforeActiveInstanceBlur(): void {
237
}
238
239
export function afterActiveInstanceBlur(): void {
268
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
240
+ if (enableDeprecatedFlareAPI) {
241
ReactBrowserEventEmitterSetEnabled(true);
242
dispatchAfterDetachedBlur((selectionInformation: any).focusedElem);
243
ReactBrowserEventEmitterSetEnabled(false);
@@ -528,7 +500,7 @@ function createEvent(type: TopLevelType): Event {
500
}
501
502
function dispatchBeforeDetachedBlur(target: HTMLElement): void {
531
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
503
+ if (enableDeprecatedFlareAPI) {
504
const event = createEvent(TOP_BEFORE_BLUR);
505
// Dispatch "beforeblur" directly on the target,
506
// so it gets picked up by the event system and
@@ -538,7 +510,7 @@ function dispatchBeforeDetachedBlur(target: HTMLElement): void {
510
}
511
512
function dispatchAfterDetachedBlur(target: HTMLElement): void {
541
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
513
+ if (enableDeprecatedFlareAPI) {
514
const event = createEvent(TOP_AFTER_BLUR);
515
// So we know what was detached, make the relatedTarget the
516
// detached target on the "afterblur" event.
@@ -550,23 +522,8 @@ function dispatchAfterDetachedBlur(target: HTMLElement): void {
522
523
export function beforeRemoveInstance(
524
instance: Instance | TextInstance | SuspenseInstance,
553
-): void {
554
- if (enableUseEventAPI) {
555
- // It's unfortunate that we have to do this cleanup, but
556
- // it's necessary otherwise we will leak the host instances
557
- // from the useEvent hook instances Map. We call destroy
558
- // on each listener to ensure we properly remove the instance
559
- // from the instances Map. Note: we have this Map so that we
560
- // can properly unmount instances when the function component
561
- // that the hook is attached to gets unmounted.
562
- const listenersSet = getListenersFromTarget(instance);
563
- if (listenersSet !== null) {
564
- const listeners = Array.from(listenersSet);
565
- for (let i = 0; i < listeners.length; i++) {
566
- listeners[i].destroy(instance);
567
- }
568
- }
569
- }
525
+) {
526
+ // TODO for ReactDOM.createEventInstance
527
}
528
529
export function removeChild(
@@ -1141,78 +1098,3 @@ export function makeOpaqueHydratingObject(
1098
valueOf: attemptToReadValue,
1099
};
1100
}
1144
-
1145
-export function registerEvent(
1146
- event: ReactDOMListenerEvent,
1147
- rootContainerInstance: Container,
1148
-): void {
1149
- const {passive, priority, type} = event;
1150
- const listenerMap = getListenerMapForElement(rootContainerInstance);
1151
- // Add the event listener to the target container (falling back to
1152
- // the target if we didn't find one).
1153
- listenToTopLevelEvent(
1154
- type,
1155
- rootContainerInstance,
1156
- listenerMap,
1157
- PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM,
1158
- passive,
1159
- priority,
1160
- );
1161
-}
1162
-
1163
-export function mountEventListener(listener: ReactDOMListener): void {
1164
- if (enableUseEventAPI) {
1165
- const {target} = listener;
1166
- if (isManagedDOMElement(target)) {
1167
- attachListenerToManagedDOMElement(listener);
1168
- } else if (enableScopeAPI && isReactScope(target)) {
1169
- attachListenerToReactScope(listener);
1170
- } else {
1171
- attachTargetEventListener(listener);
1172
- }
1173
- }
1174
-}
1175
-
1176
-export function unmountEventListener(listener: ReactDOMListener): void {
1177
- if (enableUseEventAPI) {
1178
- const {target} = listener;
1179
- if (isManagedDOMElement(target)) {
1180
- detachListenerFromManagedDOMElement(listener);
1181
- } else if (enableScopeAPI && isReactScope(target)) {
1182
- detachListenerFromReactScope(listener);
1183
- } else {
1184
- detachTargetEventListener(listener);
1185
- }
1186
- }
1187
-}
1188
-
1189
-export function validateEventListenerTarget(
1190
- target: EventTarget | ReactScopeMethods,
1191
- listener: ?(SyntheticEvent<EventTarget>) => void,
1192
-): boolean {
1193
- if (enableUseEventAPI) {
1194
- if (
1195
- target != null &&
1196
- (isManagedDOMElement(target) ||
1197
- isValidEventTarget(target) ||
1198
- isReactScope(target))
1199
- ) {
1200
- if (listener == null || typeof listener === 'function') {
1201
- return true;
1202
- }
1203
- if (__DEV__) {
1204
- console.warn(
1205
- 'Event listener method setListener() from useEvent() hook requires the second argument' +
1206
- ' to be either a valid function callback or null/undefined.',
1207
- );
1208
- }
1209
- }
1210
- if (__DEV__) {
1211
- console.warn(
1212
- 'Event listener method setListener() from useEvent() hook requires the first argument to be ' +
1213
- 'a valid DOM EventTarget. If using a ref, ensure the current value is not null.',
1214
- );
1215
- }
1216
- }
1217
- return false;
1218
-}
packages/react-dom/src/client/ReactDOMUseEvent.js
deleted
-79
@@ -1,79 +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 {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
11
-import type {EventPriority} from 'shared/ReactTypes';
12
-import type {
13
- ReactDOMListenerEvent,
14
- ReactDOMListenerMap,
15
-} from '../shared/ReactDOMTypes';
16
-
17
-import ReactSharedInternals from 'shared/ReactSharedInternals';
18
-import invariant from 'shared/invariant';
19
-
20
-import {getEventPriorityForListenerSystem} from '../events/DOMEventProperties';
21
-
22
-type EventOptions = {|
23
- capture?: boolean,
24
- passive?: boolean,
25
- priority?: EventPriority,
26
-|};
27
-
28
-const {ReactCurrentDispatcher} = ReactSharedInternals;
29
-
30
-function resolveDispatcher() {
31
- const dispatcher = ReactCurrentDispatcher.current;
32
- invariant(
33
- dispatcher !== null,
34
- 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
35
- ' one of the following reasons:\n' +
36
- '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
37
- '2. You might be breaking the Rules of Hooks\n' +
38
- '3. You might have more than one copy of React in the same app\n' +
39
- 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
40
- );
41
- return dispatcher;
42
-}
43
-
44
-export function useEvent(
45
- type: string,
46
- options?: EventOptions,
47
-): ReactDOMListenerMap {
48
- const topLevelType = ((type: any): DOMTopLevelEventType);
49
- const dispatcher = resolveDispatcher();
50
- let capture = false;
51
- let passive = undefined; // Undefined means to use the browser default
52
- let priority;
53
-
54
- if (options != null) {
55
- const optionsCapture = options.capture;
56
- const optionsPassive = options.passive;
57
- const optionsPriority = options.priority;
58
-
59
- if (typeof optionsCapture === 'boolean') {
60
- capture = optionsCapture;
61
- }
62
- if (typeof optionsPassive === 'boolean') {
63
- passive = optionsPassive;
64
- }
65
- if (typeof optionsPriority === 'number') {
66
- priority = optionsPriority;
67
- }
68
- }
69
- if (priority === undefined) {
70
- priority = getEventPriorityForListenerSystem(topLevelType);
71
- }
72
- const event: ReactDOMListenerEvent = {
73
- capture,
74
- passive,
75
- priority,
76
- type: topLevelType,
77
- };
78
- return dispatcher.useEvent(event);
79
-}
packages/react-dom/src/events/DOMEventProperties.js
-8
@@ -23,7 +23,6 @@ import {
23
UserBlockingEvent,
24
ContinuousEvent,
25
} from 'shared/ReactTypes';
26
-import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
26
27
// Needed for SimpleEventPlugin, rather than
28
// do it in two places, which duplicates logic
@@ -96,13 +95,6 @@ const otherDiscreteEvents = [
95
DOMTopLevelEventTypes.TOP_COMPOSITION_UPDATE,
96
];
97
99
-if (enableUseEventAPI) {
100
- otherDiscreteEvents.push(
101
- DOMTopLevelEventTypes.TOP_BEFORE_BLUR,
102
- DOMTopLevelEventTypes.TOP_AFTER_BLUR,
103
- );
104
-}
105
-
98
// prettier-ignore
99
const userBlockingPairsForSimpleEventPlugin = [
100
DOMTopLevelEventTypes.TOP_DRAG, 'drag',
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+4
-347
@@ -17,11 +17,7 @@ import type {EventSystemFlags} from './EventSystemFlags';
17
import type {EventPriority, ReactScopeMethods} from 'shared/ReactTypes';
18
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
19
import type {PluginModule} from 'legacy-events/PluginModuleType';
20
-import type {
21
- ReactSyntheticEvent,
22
- CustomDispatchConfig,
23
-} from 'legacy-events/ReactSyntheticEventType';
24
-import type {ReactDOMListener} from '../shared/ReactDOMTypes';
20
+import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
21
22
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
23
import {plugins} from 'legacy-events/EventPluginRegistry';
@@ -36,7 +32,6 @@ import {
32
import {
33
HostRoot,
34
HostPortal,
39
- ScopeComponent,
35
HostComponent,
36
} from 'react-reconciler/src/ReactWorkTags';
37
@@ -80,25 +75,14 @@ import {
75
TOP_PROGRESS,
76
TOP_PLAYING,
77
TOP_CLICK,
83
- TOP_BEFORE_BLUR,
84
- TOP_AFTER_BLUR,
78
TOP_SELECTION_CHANGE,
79
} from './DOMTopLevelEventTypes';
87
-import {
88
- getClosestInstanceFromNode,
89
- getListenersFromTarget,
90
- initListenersSet,
91
-} from '../client/ReactDOMComponentTree';
80
+import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
81
import {COMMENT_NODE} from '../shared/HTMLNodeType';
93
-import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
82
import {batchedEventUpdates} from './ReactDOMUpdateBatching';
83
import getListener from './getListener';
84
97
-import {
98
- enableLegacyFBSupport,
99
- enableUseEventAPI,
100
- enableScopeAPI,
101
-} from 'shared/ReactFeatureFlags';
85
+import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
86
import {
87
invokeGuardedCallbackAndCatchFirstError,
88
rethrowCaughtError,
@@ -140,44 +124,8 @@ const capturePhaseEvents = new Set([
124
TOP_WAITING,
125
]);
126
143
-if (enableUseEventAPI) {
144
- capturePhaseEvents.add(TOP_BEFORE_BLUR);
145
- capturePhaseEvents.add(TOP_AFTER_BLUR);
146
-}
147
-
148
-const emptyDispatchConfigForCustomEvents: CustomDispatchConfig = {
149
- customEvent: true,
150
- phasedRegistrationNames: {
151
- bubbled: null,
152
- captured: null,
153
- },
154
-};
155
-
127
const isArray = Array.isArray;
128
158
-// TODO: we should remove the FlowFixMes and the casting to figure out how to make
159
-// these patterns work properly.
160
-// $FlowFixMe: Flow struggles with this pattern, so we also have to cast it.
161
-const PossiblyWeakMap = ((typeof WeakMap === 'function' ? WeakMap : Map): any);
162
-
163
-// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
164
-const eventTargetEventListenerStore: WeakMap<
165
- EventTarget,
166
- Map<
167
- DOMTopLevelEventType,
168
- {bubbled: Set<ReactDOMListener>, captured: Set<ReactDOMListener>},
169
- >,
170
-> = new PossiblyWeakMap();
171
-
172
-// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
173
-const reactScopeListenerStore: WeakMap<
174
- ReactScopeMethods,
175
- Map<
176
- DOMTopLevelEventType,
177
- {bubbled: Set<ReactDOMListener>, captured: Set<ReactDOMListener>},
178
- >,
179
-> = new PossiblyWeakMap();
180
-
129
function executeDispatch(
130
event: ReactSyntheticEvent,
131
listener: Function,
@@ -514,183 +462,7 @@ export function dispatchEventForPluginEventSystem(
462
);
463
}
464
517
-function getNearestRootOrPortalContainer(instance: Element): Element {
518
- let node = getClosestInstanceFromNode(instance);
519
- while (node !== null) {
520
- const tag = node.tag;
521
- // Once we encounter a host container or root container
522
- // we can return their DOM instance.
523
- if (tag === HostRoot || tag === HostPortal) {
524
- return node.stateNode.containerInfo;
525
- }
526
- node = node.return;
527
- }
528
- return instance;
529
-}
530
-
531
-function addEventTypeToDispatchConfig(type: DOMTopLevelEventType): void {
532
- const dispatchConfig = topLevelEventsToDispatchConfig.get(type);
533
- // If we don't have a dispatchConfig, then we're dealing with
534
- // an event type that React does not know about (i.e. a custom event).
535
- // We need to register an event config for this or the SimpleEventPlugin
536
- // will not appropriately provide a SyntheticEvent, so we use out empty
537
- // dispatch config for custom events.
538
- if (dispatchConfig === undefined) {
539
- topLevelEventsToDispatchConfig.set(
540
- type,
541
- emptyDispatchConfigForCustomEvents,
542
- );
543
- }
544
-}
545
-
546
-export function attachListenerToManagedDOMElement(
547
- listener: ReactDOMListener,
548
-): void {
549
- const {event, target} = listener;
550
- const {passive, priority, type} = event;
551
-
552
- const managedTargetElement = ((target: any): Element);
553
- const containerEventTarget = getNearestRootOrPortalContainer(
554
- managedTargetElement,
555
- );
556
- const listenerMap = getListenerMapForElement(containerEventTarget);
557
- // Add the event listener to the target container (falling back to
558
- // the target if we didn't find one).
559
- listenToTopLevelEvent(
560
- type,
561
- containerEventTarget,
562
- listenerMap,
563
- PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM,
564
- passive,
565
- priority,
566
- );
567
- // Get the internal listeners Set from the target instance.
568
- let listeners = getListenersFromTarget(managedTargetElement);
569
- // If we don't have any listeners, then we need to init them.
570
- if (listeners === null) {
571
- listeners = new Set();
572
- initListenersSet(managedTargetElement, listeners);
573
- }
574
- // Add our listener to the listeners Set.
575
- listeners.add(listener);
576
- // Finally, add the event to our known event types list.
577
- addEventTypeToDispatchConfig(type);
578
-}
579
-
580
-export function detachListenerFromManagedDOMElement(
581
- listener: ReactDOMListener,
582
-): void {
583
- const {target} = listener;
584
- const managedTargetElement = ((target: any): Element);
585
- // Get the internal listeners Set from the target instance.
586
- const listeners = getListenersFromTarget(managedTargetElement);
587
- if (listeners !== null) {
588
- // Remove out listener from the listeners Set.
589
- listeners.delete(listener);
590
- }
591
-}
592
-
593
-export function attachTargetEventListener(listener: ReactDOMListener): void {
594
- const {event, target} = listener;
595
- const {capture, passive, priority, type} = event;
596
- const eventTarget = ((target: any): EventTarget);
597
- const listenerMap = getListenerMapForElement(eventTarget);
598
- // Add the event listener to the TargetEvent object.
599
- listenToTopLevelEvent(
600
- type,
601
- eventTarget,
602
- listenerMap,
603
- PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
604
- passive,
605
- priority,
606
- capture,
607
- );
608
- let eventTypeMap = eventTargetEventListenerStore.get(eventTarget);
609
- if (eventTypeMap === undefined) {
610
- eventTypeMap = new Map();
611
- eventTargetEventListenerStore.set(eventTarget, eventTypeMap);
612
- }
613
- // Get the listeners by the event type
614
- let listeners = eventTypeMap.get(type);
615
- if (listeners === undefined) {
616
- listeners = {captured: new Set(), bubbled: new Set()};
617
- eventTypeMap.set(type, listeners);
618
- }
619
- // Add our listener to the listeners Set.
620
- if (capture) {
621
- listeners.captured.add(listener);
622
- } else {
623
- listeners.bubbled.add(listener);
624
- }
625
- // Finally, add the event to our known event types list.
626
- addEventTypeToDispatchConfig(type);
627
-}
628
-
629
-export function detachTargetEventListener(listener: ReactDOMListener): void {
630
- const {event, target} = listener;
631
- const {capture, type} = event;
632
- const validEventTarget = ((target: any): EventTarget);
633
- const eventTypeMap = eventTargetEventListenerStore.get(validEventTarget);
634
- if (eventTypeMap !== undefined) {
635
- const listeners = eventTypeMap.get(type);
636
- if (listeners !== undefined) {
637
- // Remove out listener from the listeners Set.
638
- if (capture) {
639
- listeners.captured.delete(listener);
640
- } else {
641
- listeners.bubbled.delete(listener);
642
- }
643
- }
644
- }
645
-}
646
-
647
-export function attachListenerToReactScope(listener: ReactDOMListener): void {
648
- const {event, target} = listener;
649
- const {capture, type} = event;
650
- const reactScope = ((target: any): ReactScopeMethods);
651
- let eventTypeMap = reactScopeListenerStore.get(reactScope);
652
- if (eventTypeMap === undefined) {
653
- eventTypeMap = new Map();
654
- reactScopeListenerStore.set(reactScope, eventTypeMap);
655
- }
656
- // Get the listeners by the event type
657
- let listeners = eventTypeMap.get(type);
658
- if (listeners === undefined) {
659
- listeners = {captured: new Set(), bubbled: new Set()};
660
- eventTypeMap.set(type, listeners);
661
- }
662
- // Add our listener to the listeners Set.
663
- if (capture) {
664
- listeners.captured.add(listener);
665
- } else {
666
- listeners.bubbled.add(listener);
667
- }
668
- // Finally, add the event to our known event types list.
669
- addEventTypeToDispatchConfig(type);
670
-}
671
-
672
-export function detachListenerFromReactScope(listener: ReactDOMListener): void {
673
- const {event, target} = listener;
674
- const {capture, type} = event;
675
- const reactScope = ((target: any): ReactScopeMethods);
676
- const eventTypeMap = reactScopeListenerStore.get(reactScope);
677
- if (eventTypeMap !== undefined) {
678
- const listeners = eventTypeMap.get(type);
679
- if (listeners !== undefined) {
680
- // Remove out listener from the listeners Set.
681
- if (capture) {
682
- listeners.captured.delete(listener);
683
- } else {
684
- listeners.bubbled.delete(listener);
685
- }
686
- }
687
- }
688
-}
689
-
690
-export function accumulateTwoPhaseListeners(
691
- event: ReactSyntheticEvent,
692
- accumulateUseEventListeners?: boolean,
693
-): void {
465
+export function accumulateTwoPhaseListeners(event: ReactSyntheticEvent): void {
466
const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
467
const dispatchListeners = [];
468
const dispatchInstances: Array<Fiber | null> = [];
@@ -701,7 +473,6 @@ export function accumulateTwoPhaseListeners(
473
// usual two phase accumulation using the React fiber tree to pick up
474
// all relevant useEvent and on* prop events.
475
let instance = event._targetInst;
704
- let lastHostComponent = null;
476
477
// Accumulate all instances and listeners via the target -> root path.
478
while (instance !== null) {
@@ -709,35 +480,6 @@ export function accumulateTwoPhaseListeners(
480
// Handle listeners that are on HostComponents (i.e. <div>)
481
if (tag === HostComponent && stateNode !== null) {
482
const currentTarget = stateNode;
712
- lastHostComponent = currentTarget;
713
- // For useEvent listenrs
714
- if (enableUseEventAPI && accumulateUseEventListeners) {
715
- // useEvent event listeners
716
- const targetType = event.type;
717
- const listeners = getListenersFromTarget(currentTarget);
718
-
719
- if (listeners !== null) {
720
- const listenersArr = Array.from(listeners);
721
- for (let i = 0; i < listenersArr.length; i++) {
722
- const listener = listenersArr[i];
723
- const {
724
- callback,
725
- event: {capture, type},
726
- } = listener;
727
- if (type === targetType) {
728
- if (capture === true) {
729
- dispatchListeners.unshift(callback);
730
- dispatchInstances.unshift(instance);
731
- dispatchCurrentTargets.unshift(currentTarget);
732
- } else {
733
- dispatchListeners.push(callback);
734
- dispatchInstances.push(instance);
735
- dispatchCurrentTargets.push(currentTarget);
736
- }
737
- }
738
- }
739
- }
740
- }
483
// Standard React on* listeners, i.e. onClick prop
484
if (captured !== null) {
485
const captureListener = getListener(instance, captured);
@@ -760,40 +502,6 @@ export function accumulateTwoPhaseListeners(
502
}
503
}
504
}
763
- if (
764
- enableUseEventAPI &&
765
- enableScopeAPI &&
766
- accumulateUseEventListeners &&
767
- tag === ScopeComponent &&
768
- lastHostComponent !== null
769
- ) {
770
- const reactScope = stateNode.methods;
771
- const eventTypeMap = reactScopeListenerStore.get(reactScope);
772
- if (eventTypeMap !== undefined) {
773
- const type = ((event.type: any): DOMTopLevelEventType);
774
- const listeners = eventTypeMap.get(type);
775
- if (listeners !== undefined) {
776
- const captureListeners = Array.from(listeners.captured);
777
- const bubbleListeners = Array.from(listeners.bubbled);
778
- const lastCurrentTarget = ((lastHostComponent: any): Element);
779
-
780
- for (let i = 0; i < captureListeners.length; i++) {
781
- const listener = captureListeners[i];
782
- const {callback} = listener;
783
- dispatchListeners.unshift(callback);
784
- dispatchInstances.unshift(instance);
785
- dispatchCurrentTargets.unshift(lastCurrentTarget);
786
- }
787
- for (let i = 0; i < bubbleListeners.length; i++) {
788
- const listener = bubbleListeners[i];
789
- const {callback} = listener;
790
- dispatchListeners.push(callback);
791
- dispatchInstances.push(instance);
792
- dispatchCurrentTargets.push(lastCurrentTarget);
793
- }
794
- }
795
- }
796
- }
505
instance = instance.return;
506
}
507
@@ -806,57 +514,6 @@ export function accumulateTwoPhaseListeners(
514
}
515
}
516
809
-export function accumulateEventTargetListeners(
810
- event: ReactSyntheticEvent,
811
- currentTarget: EventTarget,
812
-): void {
813
- const dispatchListeners = [];
814
- const dispatchInstances: Array<Fiber | null> = [];
815
- const dispatchCurrentTargets = [];
816
-
817
- const eventTypeMap = eventTargetEventListenerStore.get(currentTarget);
818
- if (eventTypeMap !== undefined) {
819
- const type = ((event.type: any): DOMTopLevelEventType);
820
- const listeners = eventTypeMap.get(type);
821
- if (listeners !== undefined) {
822
- const isCapturePhase = (event: any).eventPhase === 1;
823
-
824
- if (isCapturePhase) {
825
- const captureListeners = Array.from(listeners.captured);
826
-
827
- for (let i = captureListeners.length - 1; i >= 0; i--) {
828
- const listener = captureListeners[i];
829
- const {callback} = listener;
830
- dispatchListeners.push(callback);
831
- // EventTarget listeners do not have instances, as there
832
- // is no backing Fiber instance for them (window, document etc).
833
- dispatchInstances.push(null);
834
- dispatchCurrentTargets.push(currentTarget);
835
- }
836
- } else {
837
- const bubbleListeners = Array.from(listeners.bubbled);
838
-
839
- for (let i = 0; i < bubbleListeners.length; i++) {
840
- const listener = bubbleListeners[i];
841
- const {callback} = listener;
842
- dispatchListeners.push(callback);
843
- // EventTarget listeners do not have instances, as there
844
- // is no backing Fiber instance for them (window, document etc).
845
- dispatchInstances.push(null);
846
- dispatchCurrentTargets.push(currentTarget);
847
- }
848
- }
849
- }
850
- }
851
- // To prevent allocation to the event unless we actually
852
- // have listeners we check the length of one of the arrays.
853
- if (dispatchListeners.length > 0) {
854
- event._dispatchListeners = dispatchListeners;
855
- event._dispatchInstances = dispatchInstances;
856
- event._dispatchCurrentTargets = dispatchCurrentTargets;
857
- }
858
-}
859
-
517
function getParent(inst: Fiber | null): Fiber | null {
518
if (inst === null) {
519
return null;
packages/react-dom/src/events/ReactDOMEventListener.js
+10
-32
@@ -44,7 +44,6 @@ import {
44
addEventBubbleListener,
45
addEventCaptureListener,
46
addEventCaptureListenerWithPassiveFlag,
47
- addEventBubbleListenerWithPassiveFlag,
47
removeEventListener,
48
} from './EventListener';
49
import getEventTarget from './getEventTarget';
@@ -56,7 +55,6 @@ import {
55
enableDeprecatedFlareAPI,
56
enableModernEventSystem,
57
enableLegacyFBSupport,
59
- enableUseEventAPI,
58
} from 'shared/ReactFeatureFlags';
59
import {
60
UserBlockingEvent,
@@ -200,37 +198,17 @@ export function addTrappedEventListener(
198
};
199
}
200
if (capture) {
203
- if (enableUseEventAPI && passive !== undefined) {
204
- // This is only used with passive is either true or false.
205
- unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
206
- targetContainer,
207
- rawEventName,
208
- listener,
209
- passive,
210
- );
211
- } else {
212
- unsubscribeListener = addEventCaptureListener(
213
- targetContainer,
214
- rawEventName,
215
- listener,
216
- );
217
- }
201
+ unsubscribeListener = addEventCaptureListener(
202
+ targetContainer,
203
+ rawEventName,
204
+ listener,
205
+ );
206
} else {
219
- if (enableUseEventAPI && passive !== undefined) {
220
- // This is only used with passive is either true or false.
221
- unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
222
- targetContainer,
223
- rawEventName,
224
- listener,
225
- passive,
226
- );
227
- } else {
228
- unsubscribeListener = addEventBubbleListener(
229
- targetContainer,
230
- rawEventName,
231
- listener,
232
- );
233
- }
207
+ unsubscribeListener = addEventBubbleListener(
208
+ targetContainer,
209
+ rawEventName,
210
+ listener,
211
+ );
212
}
213
return unsubscribeListener;
214
}
packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js
-1549
@@ -9,13 +9,10 @@
9
10
'use strict';
11
12
-import {createEventTarget} from 'dom-event-testing-library';
13
-
12
let React;
13
let ReactFeatureFlags;
14
let ReactDOM;
15
let ReactDOMServer;
18
-let ReactTestUtils;
16
let Scheduler;
17
18
function dispatchEvent(element, type) {
@@ -67,7 +64,6 @@ describe('DOMModernPluginEventSystem', () => {
64
ReactDOM = require('react-dom');
65
Scheduler = require('scheduler');
66
ReactDOMServer = require('react-dom/server');
70
- ReactTestUtils = require('react-dom/test-utils');
67
container = document.createElement('div');
68
document.body.appendChild(container);
69
startNativeEventListenerClearDown();
@@ -1082,1551 +1078,6 @@ describe('DOMModernPluginEventSystem', () => {
1078
]);
1079
}
1080
});
1085
-
1086
- describe('ReactDOM.useEvent', () => {
1087
- beforeEach(() => {
1088
- jest.resetModules();
1089
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
1090
- ReactFeatureFlags.enableModernEventSystem = true;
1091
- ReactFeatureFlags.enableUseEventAPI = true;
1092
-
1093
- React = require('react');
1094
- ReactDOM = require('react-dom');
1095
- Scheduler = require('scheduler');
1096
- ReactDOMServer = require('react-dom/server');
1097
- ReactTestUtils = require('react-dom/test-utils');
1098
- });
1099
-
1100
- // @gate experimental
1101
- it('should create the same event listener map', () => {
1102
- const listenerMaps = [];
1103
-
1104
- function Test() {
1105
- const listenerMap = ReactDOM.unstable_useEvent('click');
1106
-
1107
- listenerMaps.push(listenerMap);
1108
-
1109
- return <div />;
1110
- }
1111
-
1112
- ReactDOM.render(<Test />, container);
1113
- ReactDOM.render(<Test />, container);
1114
- expect(listenerMaps.length).toEqual(2);
1115
- expect(listenerMaps[0]).toEqual(listenerMaps[1]);
1116
- });
1117
-
1118
- // @gate experimental
1119
- it('can render correctly with the ReactDOMServer', () => {
1120
- const clickEvent = jest.fn();
1121
-
1122
- function Test() {
1123
- const divRef = React.useRef(null);
1124
- const click = ReactDOM.unstable_useEvent('click');
1125
-
1126
- React.useEffect(() => {
1127
- click.setListener(divRef.current, clickEvent);
1128
- });
1129
-
1130
- return <div ref={divRef}>Hello world</div>;
1131
- }
1132
- const output = ReactDOMServer.renderToString(<Test />);
1133
- expect(output).toBe(`<div data-reactroot="">Hello world</div>`);
1134
- });
1135
-
1136
- // @gate experimental
1137
- it('can render correctly with the ReactDOMServer hydration', () => {
1138
- const clickEvent = jest.fn();
1139
- const spanRef = React.createRef();
1140
-
1141
- function Test() {
1142
- const click = ReactDOM.unstable_useEvent('click');
1143
-
1144
- React.useEffect(() => {
1145
- click.setListener(spanRef.current, clickEvent);
1146
- });
1147
-
1148
- return (
1149
- <div>
1150
- <span ref={spanRef}>Hello world</span>
1151
- </div>
1152
- );
1153
- }
1154
- const output = ReactDOMServer.renderToString(<Test />);
1155
- expect(output).toBe(
1156
- `<div data-reactroot=""><span>Hello world</span></div>`,
1157
- );
1158
- container.innerHTML = output;
1159
- ReactDOM.hydrate(<Test />, container);
1160
- Scheduler.unstable_flushAll();
1161
- dispatchClickEvent(spanRef.current);
1162
- expect(clickEvent).toHaveBeenCalledTimes(1);
1163
- });
1164
-
1165
- // @gate experimental
1166
- it('should correctly work for a basic "click" listener', () => {
1167
- let log = [];
1168
- const clickEvent = jest.fn(event => {
1169
- log.push({
1170
- eventPhase: event.eventPhase,
1171
- type: event.type,
1172
- currentTarget: event.currentTarget,
1173
- target: event.target,
1174
- });
1175
- });
1176
- const divRef = React.createRef();
1177
- const buttonRef = React.createRef();
1178
-
1179
- function Test() {
1180
- const click = ReactDOM.unstable_useEvent('click');
1181
-
1182
- React.useEffect(() => {
1183
- click.setListener(buttonRef.current, clickEvent);
1184
- });
1185
-
1186
- return (
1187
- <button ref={buttonRef}>
1188
- <div ref={divRef}>Click me!</div>
1189
- </button>
1190
- );
1191
- }
1192
-
1193
- ReactDOM.render(<Test />, container);
1194
- Scheduler.unstable_flushAll();
1195
-
1196
- expect(container.innerHTML).toBe(
1197
- '<button><div>Click me!</div></button>',
1198
- );
1199
-
1200
- // Clicking the button should trigger the event callback
1201
- let divElement = divRef.current;
1202
- dispatchClickEvent(divElement);
1203
- expect(log).toEqual([
1204
- {
1205
- eventPhase: 3,
1206
- type: 'click',
1207
- currentTarget: buttonRef.current,
1208
- target: divRef.current,
1209
- },
1210
- ]);
1211
- expect(clickEvent).toBeCalledTimes(1);
1212
-
1213
- // Unmounting the container and clicking should not work
1214
- ReactDOM.render(null, container);
1215
- Scheduler.unstable_flushAll();
1216
-
1217
- dispatchClickEvent(divElement);
1218
- expect(clickEvent).toBeCalledTimes(1);
1219
-
1220
- // Re-rendering the container and clicking should work
1221
- ReactDOM.render(<Test />, container);
1222
- Scheduler.unstable_flushAll();
1223
-
1224
- divElement = divRef.current;
1225
- dispatchClickEvent(divElement);
1226
- expect(clickEvent).toBeCalledTimes(2);
1227
-
1228
- log = [];
1229
-
1230
- // Clicking the button should also work
1231
- const buttonElement = buttonRef.current;
1232
- dispatchClickEvent(buttonElement);
1233
- expect(log).toEqual([
1234
- {
1235
- eventPhase: 3,
1236
- type: 'click',
1237
- currentTarget: buttonRef.current,
1238
- target: buttonRef.current,
1239
- },
1240
- ]);
1241
-
1242
- function Test2({clickEvent2}) {
1243
- const click = ReactDOM.unstable_useEvent('click', clickEvent2);
1244
-
1245
- React.useEffect(() => {
1246
- click.setListener(buttonRef.current, clickEvent2);
1247
- });
1248
-
1249
- return (
1250
- <button ref={buttonRef}>
1251
- <div ref={divRef}>Click me!</div>
1252
- </button>
1253
- );
1254
- }
1255
-
1256
- let clickEvent2 = jest.fn();
1257
- ReactDOM.render(<Test2 clickEvent2={clickEvent2} />, container);
1258
- Scheduler.unstable_flushAll();
1259
-
1260
- divElement = divRef.current;
1261
- dispatchClickEvent(divElement);
1262
- expect(clickEvent2).toBeCalledTimes(1);
1263
-
1264
- // Reset the function we pass in, so it's different
1265
- clickEvent2 = jest.fn();
1266
- ReactDOM.render(<Test2 clickEvent2={clickEvent2} />, container);
1267
- Scheduler.unstable_flushAll();
1268
-
1269
- divElement = divRef.current;
1270
- dispatchClickEvent(divElement);
1271
- expect(clickEvent2).toBeCalledTimes(1);
1272
- });
1273
-
1274
- // @gate experimental
1275
- it('should correctly work for setting and clearing a basic "click" listener', () => {
1276
- const clickEvent = jest.fn();
1277
- const divRef = React.createRef();
1278
- const buttonRef = React.createRef();
1279
-
1280
- function Test({off}) {
1281
- const click = ReactDOM.unstable_useEvent('click');
1282
-
1283
- React.useEffect(() => {
1284
- click.setListener(buttonRef.current, clickEvent);
1285
- });
1286
-
1287
- React.useEffect(() => {
1288
- if (off) {
1289
- click.setListener(buttonRef.current, null);
1290
- }
1291
- }, [off]);
1292
-
1293
- return (
1294
- <button ref={buttonRef}>
1295
- <div ref={divRef}>Click me!</div>
1296
- </button>
1297
- );
1298
- }
1299
-
1300
- ReactDOM.render(<Test off={false} />, container);
1301
- Scheduler.unstable_flushAll();
1302
-
1303
- let divElement = divRef.current;
1304
- dispatchClickEvent(divElement);
1305
- expect(clickEvent).toBeCalledTimes(1);
1306
-
1307
- // The listener should get unmounted in the second effect
1308
- ReactDOM.render(<Test off={true} />, container);
1309
- Scheduler.unstable_flushAll();
1310
-
1311
- clickEvent.mockClear();
1312
-
1313
- divElement = divRef.current;
1314
- dispatchClickEvent(divElement);
1315
- expect(clickEvent).toBeCalledTimes(0);
1316
- });
1317
-
1318
- // @gate experimental
1319
- it('should handle the target being a text node', () => {
1320
- const clickEvent = jest.fn();
1321
- const buttonRef = React.createRef();
1322
-
1323
- function Test() {
1324
- const click = ReactDOM.unstable_useEvent('click');
1325
-
1326
- React.useEffect(() => {
1327
- click.setListener(buttonRef.current, clickEvent);
1328
- });
1329
-
1330
- return <button ref={buttonRef}>Click me!</button>;
1331
- }
1332
-
1333
- ReactDOM.render(<Test />, container);
1334
- Scheduler.unstable_flushAll();
1335
-
1336
- const textNode = buttonRef.current.firstChild;
1337
- dispatchClickEvent(textNode);
1338
- expect(clickEvent).toBeCalledTimes(1);
1339
- });
1340
-
1341
- // @gate experimental
1342
- it('handle propagation of click events', () => {
1343
- const buttonRef = React.createRef();
1344
- const divRef = React.createRef();
1345
- const log = [];
1346
- const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
1347
- const onClickCapture = jest.fn(e =>
1348
- log.push(['capture', e.currentTarget]),
1349
- );
1350
-
1351
- function Test() {
1352
- const click = ReactDOM.unstable_useEvent('click');
1353
- const clickCapture = ReactDOM.unstable_useEvent('click', {
1354
- capture: true,
1355
- });
1356
-
1357
- React.useEffect(() => {
1358
- click.setListener(buttonRef.current, onClick);
1359
- clickCapture.setListener(buttonRef.current, onClickCapture);
1360
- click.setListener(divRef.current, onClick);
1361
- clickCapture.setListener(divRef.current, onClickCapture);
1362
- });
1363
-
1364
- return (
1365
- <button ref={buttonRef}>
1366
- <div ref={divRef}>Click me!</div>
1367
- </button>
1368
- );
1369
- }
1370
-
1371
- ReactDOM.render(<Test />, container);
1372
- Scheduler.unstable_flushAll();
1373
-
1374
- const buttonElement = buttonRef.current;
1375
- dispatchClickEvent(buttonElement);
1376
- expect(onClick).toHaveBeenCalledTimes(1);
1377
- expect(onClickCapture).toHaveBeenCalledTimes(1);
1378
- expect(log[0]).toEqual(['capture', buttonElement]);
1379
- expect(log[1]).toEqual(['bubble', buttonElement]);
1380
-
1381
- log.length = 0;
1382
- onClick.mockClear();
1383
- onClickCapture.mockClear();
1384
-
1385
- const divElement = divRef.current;
1386
- dispatchClickEvent(divElement);
1387
- expect(onClick).toHaveBeenCalledTimes(2);
1388
- expect(onClickCapture).toHaveBeenCalledTimes(2);
1389
- expect(log[0]).toEqual(['capture', buttonElement]);
1390
- expect(log[1]).toEqual(['capture', divElement]);
1391
- expect(log[2]).toEqual(['bubble', divElement]);
1392
- expect(log[3]).toEqual(['bubble', buttonElement]);
1393
- });
1394
-
1395
- // @gate experimental
1396
- it('handle propagation of click events mixed with onClick events', () => {
1397
- const buttonRef = React.createRef();
1398
- const divRef = React.createRef();
1399
- const log = [];
1400
- const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
1401
- const onClickCapture = jest.fn(e =>
1402
- log.push(['capture', e.currentTarget]),
1403
- );
1404
-
1405
- function Test() {
1406
- const click = ReactDOM.unstable_useEvent('click');
1407
- const clickCapture = ReactDOM.unstable_useEvent('click', {
1408
- capture: true,
1409
- });
1410
-
1411
- React.useEffect(() => {
1412
- click.setListener(buttonRef.current, onClick);
1413
- clickCapture.setListener(buttonRef.current, onClickCapture);
1414
- });
1415
-
1416
- return (
1417
- <button ref={buttonRef}>
1418
- <div
1419
- ref={divRef}
1420
- onClick={onClick}
1421
- onClickCapture={onClickCapture}>
1422
- Click me!
1423
- </div>
1424
- </button>
1425
- );
1426
- }
1427
-
1428
- ReactDOM.render(<Test />, container);
1429
- Scheduler.unstable_flushAll();
1430
-
1431
- const buttonElement = buttonRef.current;
1432
- dispatchClickEvent(buttonElement);
1433
- expect(onClick).toHaveBeenCalledTimes(1);
1434
- expect(onClickCapture).toHaveBeenCalledTimes(1);
1435
- expect(log[0]).toEqual(['capture', buttonElement]);
1436
- expect(log[1]).toEqual(['bubble', buttonElement]);
1437
-
1438
- const divElement = divRef.current;
1439
- dispatchClickEvent(divElement);
1440
- expect(onClick).toHaveBeenCalledTimes(3);
1441
- expect(onClickCapture).toHaveBeenCalledTimes(3);
1442
- expect(log[2]).toEqual(['capture', buttonElement]);
1443
- expect(log[3]).toEqual(['capture', divElement]);
1444
- expect(log[4]).toEqual(['bubble', divElement]);
1445
- expect(log[5]).toEqual(['bubble', buttonElement]);
1446
- });
1447
-
1448
- // @gate experimental
1449
- it('should correctly work for a basic "click" listener on the outer target', () => {
1450
- const log = [];
1451
- const clickEvent = jest.fn(event => {
1452
- log.push({
1453
- eventPhase: event.eventPhase,
1454
- type: event.type,
1455
- currentTarget: event.currentTarget,
1456
- target: event.target,
1457
- });
1458
- });
1459
- const divRef = React.createRef();
1460
- const buttonRef = React.createRef();
1461
-
1462
- function Test() {
1463
- const click = ReactDOM.unstable_useEvent('click');
1464
-
1465
- React.useEffect(() => {
1466
- click.setListener(divRef.current, clickEvent);
1467
- });
1468
-
1469
- return (
1470
- <button ref={buttonRef}>
1471
- <div ref={divRef}>Click me!</div>
1472
- </button>
1473
- );
1474
- }
1475
-
1476
- ReactDOM.render(<Test />, container);
1477
- Scheduler.unstable_flushAll();
1478
-
1479
- expect(container.innerHTML).toBe(
1480
- '<button><div>Click me!</div></button>',
1481
- );
1482
-
1483
- // Clicking the button should trigger the event callback
1484
- let divElement = divRef.current;
1485
- dispatchClickEvent(divElement);
1486
- expect(log).toEqual([
1487
- {
1488
- eventPhase: 3,
1489
- type: 'click',
1490
- currentTarget: divRef.current,
1491
- target: divRef.current,
1492
- },
1493
- ]);
1494
-
1495
- // Unmounting the container and clicking should not work
1496
- ReactDOM.render(null, container);
1497
- dispatchClickEvent(divElement);
1498
- expect(clickEvent).toBeCalledTimes(1);
1499
-
1500
- // Re-rendering the container and clicking should work
1501
- ReactDOM.render(<Test />, container);
1502
- Scheduler.unstable_flushAll();
1503
-
1504
- divElement = divRef.current;
1505
- dispatchClickEvent(divElement);
1506
- expect(clickEvent).toBeCalledTimes(2);
1507
-
1508
- // Clicking the button should not work
1509
- const buttonElement = buttonRef.current;
1510
- dispatchClickEvent(buttonElement);
1511
- expect(clickEvent).toBeCalledTimes(2);
1512
- });
1513
-
1514
- // @gate experimental
1515
- it('should correctly handle many nested target listeners', () => {
1516
- const buttonRef = React.createRef();
1517
- const targetListener1 = jest.fn();
1518
- const targetListener2 = jest.fn();
1519
- const targetListener3 = jest.fn();
1520
- const targetListener4 = jest.fn();
1521
-
1522
- function Test() {
1523
- const click1 = ReactDOM.unstable_useEvent('click', {
1524
- capture: true,
1525
- });
1526
- const click2 = ReactDOM.unstable_useEvent('click', {
1527
- capture: true,
1528
- });
1529
- const click3 = ReactDOM.unstable_useEvent('click');
1530
- const click4 = ReactDOM.unstable_useEvent('click');
1531
-
1532
- React.useEffect(() => {
1533
- click1.setListener(buttonRef.current, targetListener1);
1534
- click2.setListener(buttonRef.current, targetListener2);
1535
- click3.setListener(buttonRef.current, targetListener3);
1536
- click4.setListener(buttonRef.current, targetListener4);
1537
- });
1538
-
1539
- return <button ref={buttonRef}>Click me!</button>;
1540
- }
1541
-
1542
- ReactDOM.render(<Test />, container);
1543
- Scheduler.unstable_flushAll();
1544
-
1545
- let buttonElement = buttonRef.current;
1546
- dispatchClickEvent(buttonElement);
1547
-
1548
- expect(targetListener1).toHaveBeenCalledTimes(1);
1549
- expect(targetListener2).toHaveBeenCalledTimes(1);
1550
- expect(targetListener3).toHaveBeenCalledTimes(1);
1551
- expect(targetListener4).toHaveBeenCalledTimes(1);
1552
-
1553
- function Test2() {
1554
- const click1 = ReactDOM.unstable_useEvent('click');
1555
- const click2 = ReactDOM.unstable_useEvent('click');
1556
- const click3 = ReactDOM.unstable_useEvent('click');
1557
- const click4 = ReactDOM.unstable_useEvent('click');
1558
-
1559
- React.useEffect(() => {
1560
- click1.setListener(buttonRef.current, targetListener1);
1561
- click2.setListener(buttonRef.current, targetListener2);
1562
- click3.setListener(buttonRef.current, targetListener3);
1563
- click4.setListener(buttonRef.current, targetListener4);
1564
- });
1565
-
1566
- return <button ref={buttonRef}>Click me!</button>;
1567
- }
1568
-
1569
- ReactDOM.render(<Test2 />, container);
1570
- Scheduler.unstable_flushAll();
1571
-
1572
- buttonElement = buttonRef.current;
1573
- dispatchClickEvent(buttonElement);
1574
- expect(targetListener1).toHaveBeenCalledTimes(2);
1575
- expect(targetListener2).toHaveBeenCalledTimes(2);
1576
- expect(targetListener3).toHaveBeenCalledTimes(2);
1577
- expect(targetListener4).toHaveBeenCalledTimes(2);
1578
- });
1579
-
1580
- // @gate experimental
1581
- it('should correctly handle stopPropagation corrrectly for target events', () => {
1582
- const buttonRef = React.createRef();
1583
- const divRef = React.createRef();
1584
- const clickEvent = jest.fn();
1585
-
1586
- function Test() {
1587
- const click1 = ReactDOM.unstable_useEvent('click', {
1588
- bind: buttonRef,
1589
- });
1590
- const click2 = ReactDOM.unstable_useEvent('click');
1591
-
1592
- React.useEffect(() => {
1593
- click1.setListener(buttonRef.current, clickEvent);
1594
- click2.setListener(divRef.current, e => {
1595
- e.stopPropagation();
1596
- });
1597
- });
1598
-
1599
- return (
1600
- <button ref={buttonRef}>
1601
- <div ref={divRef}>Click me!</div>
1602
- </button>
1603
- );
1604
- }
1605
-
1606
- ReactDOM.render(<Test />, container);
1607
- Scheduler.unstable_flushAll();
1608
-
1609
- const divElement = divRef.current;
1610
- dispatchClickEvent(divElement);
1611
- expect(clickEvent).toHaveBeenCalledTimes(0);
1612
- });
1613
-
1614
- // @gate experimental
1615
- it('should correctly handle stopPropagation corrrectly for many target events', () => {
1616
- const buttonRef = React.createRef();
1617
- const targetListerner1 = jest.fn(e => e.stopPropagation());
1618
- const targetListerner2 = jest.fn(e => e.stopPropagation());
1619
- const targetListerner3 = jest.fn(e => e.stopPropagation());
1620
- const targetListerner4 = jest.fn(e => e.stopPropagation());
1621
-
1622
- function Test() {
1623
- const click1 = ReactDOM.unstable_useEvent('click');
1624
- const click2 = ReactDOM.unstable_useEvent('click');
1625
- const click3 = ReactDOM.unstable_useEvent('click');
1626
- const click4 = ReactDOM.unstable_useEvent('click');
1627
-
1628
- React.useEffect(() => {
1629
- click1.setListener(buttonRef.current, targetListerner1);
1630
- click2.setListener(buttonRef.current, targetListerner2);
1631
- click3.setListener(buttonRef.current, targetListerner3);
1632
- click4.setListener(buttonRef.current, targetListerner4);
1633
- });
1634
-
1635
- return <button ref={buttonRef}>Click me!</button>;
1636
- }
1637
-
1638
- ReactDOM.render(<Test />, container);
1639
- Scheduler.unstable_flushAll();
1640
-
1641
- const buttonElement = buttonRef.current;
1642
- dispatchClickEvent(buttonElement);
1643
- expect(targetListerner1).toHaveBeenCalledTimes(1);
1644
- expect(targetListerner2).toHaveBeenCalledTimes(1);
1645
- expect(targetListerner3).toHaveBeenCalledTimes(1);
1646
- expect(targetListerner4).toHaveBeenCalledTimes(1);
1647
- });
1648
-
1649
- // @gate experimental
1650
- it('should correctly handle stopPropagation for mixed capture/bubbling target listeners', () => {
1651
- const buttonRef = React.createRef();
1652
- const targetListerner1 = jest.fn(e => e.stopPropagation());
1653
- const targetListerner2 = jest.fn(e => e.stopPropagation());
1654
- const targetListerner3 = jest.fn(e => e.stopPropagation());
1655
- const targetListerner4 = jest.fn(e => e.stopPropagation());
1656
-
1657
- function Test() {
1658
- const click1 = ReactDOM.unstable_useEvent('click', {
1659
- capture: true,
1660
- });
1661
- const click2 = ReactDOM.unstable_useEvent('click', {
1662
- capture: true,
1663
- });
1664
- const click3 = ReactDOM.unstable_useEvent('click');
1665
- const click4 = ReactDOM.unstable_useEvent('click');
1666
-
1667
- React.useEffect(() => {
1668
- click1.setListener(buttonRef.current, targetListerner1);
1669
- click2.setListener(buttonRef.current, targetListerner2);
1670
- click3.setListener(buttonRef.current, targetListerner3);
1671
- click4.setListener(buttonRef.current, targetListerner4);
1672
- });
1673
-
1674
- return <button ref={buttonRef}>Click me!</button>;
1675
- }
1676
-
1677
- ReactDOM.render(<Test />, container);
1678
- Scheduler.unstable_flushAll();
1679
-
1680
- const buttonElement = buttonRef.current;
1681
- dispatchClickEvent(buttonElement);
1682
- expect(targetListerner1).toHaveBeenCalledTimes(1);
1683
- expect(targetListerner2).toHaveBeenCalledTimes(1);
1684
- expect(targetListerner3).toHaveBeenCalledTimes(1);
1685
- expect(targetListerner4).toHaveBeenCalledTimes(1);
1686
- });
1687
-
1688
- // @gate experimental
1689
- it('should work with concurrent mode updates', async () => {
1690
- const log = [];
1691
- const ref = React.createRef();
1692
-
1693
- function Test({counter}) {
1694
- const click = ReactDOM.unstable_useEvent('click');
1695
-
1696
- React.useLayoutEffect(() => {
1697
- click.setListener(ref.current, () => {
1698
- log.push({counter});
1699
- });
1700
- });
1701
-
1702
- Scheduler.unstable_yieldValue('Test');
1703
- return <button ref={ref}>Press me</button>;
1704
- }
1705
-
1706
- const root = ReactDOM.createRoot(container);
1707
- root.render(<Test counter={0} />);
1708
-
1709
- expect(Scheduler).toFlushAndYield(['Test']);
1710
-
1711
- // Click the button
1712
- dispatchClickEvent(ref.current);
1713
- expect(log).toEqual([{counter: 0}]);
1714
-
1715
- // Clear log
1716
- log.length = 0;
1717
-
1718
- // Increase counter
1719
- root.render(<Test counter={1} />);
1720
- // Yield before committing
1721
- expect(Scheduler).toFlushAndYieldThrough(['Test']);
1722
-
1723
- // Click the button again
1724
- dispatchClickEvent(ref.current);
1725
- expect(log).toEqual([{counter: 0}]);
1726
-
1727
- // Clear log
1728
- log.length = 0;
1729
-
1730
- // Commit
1731
- expect(Scheduler).toFlushAndYield([]);
1732
- dispatchClickEvent(ref.current);
1733
- expect(log).toEqual([{counter: 1}]);
1734
- });
1735
-
1736
- // @gate experimental
1737
- it('should correctly work for a basic "click" listener that upgrades', () => {
1738
- const clickEvent = jest.fn();
1739
- const buttonRef = React.createRef();
1740
- const button2Ref = React.createRef();
1741
-
1742
- function Test2() {
1743
- const click = ReactDOM.unstable_useEvent('click', {
1744
- passive: false,
1745
- });
1746
-
1747
- React.useEffect(() => {
1748
- click.setListener(button2Ref.current, clickEvent);
1749
- });
1750
-
1751
- return <button ref={button2Ref}>Click me!</button>;
1752
- }
1753
-
1754
- function Test({extra}) {
1755
- const click = ReactDOM.unstable_useEvent('click', {
1756
- passive: true,
1757
- });
1758
-
1759
- React.useEffect(() => {
1760
- click.setListener(buttonRef.current, clickEvent);
1761
- });
1762
-
1763
- return (
1764
- <>
1765
- <button ref={buttonRef}>Click me!</button>
1766
- {extra && <Test2 />}
1767
- </>
1768
- );
1769
- }
1770
-
1771
- ReactDOM.render(<Test />, container);
1772
- Scheduler.unstable_flushAll();
1773
-
1774
- let button = buttonRef.current;
1775
- dispatchClickEvent(button);
1776
- expect(clickEvent).toHaveBeenCalledTimes(1);
1777
-
1778
- ReactDOM.render(<Test extra={true} />, container);
1779
- Scheduler.unstable_flushAll();
1780
-
1781
- clickEvent.mockClear();
1782
-
1783
- button = button2Ref.current;
1784
- dispatchClickEvent(button);
1785
- expect(clickEvent).toHaveBeenCalledTimes(1);
1786
- });
1787
-
1788
- // @gate experimental
1789
- it('should correctly work for a basic "click" listener that upgrades #2', () => {
1790
- const clickEvent = jest.fn();
1791
- const buttonRef = React.createRef();
1792
- const button2Ref = React.createRef();
1793
-
1794
- function Test2() {
1795
- const click = ReactDOM.unstable_useEvent('click', {
1796
- passive: false,
1797
- });
1798
-
1799
- React.useEffect(() => {
1800
- click.setListener(button2Ref.current, clickEvent);
1801
- });
1802
-
1803
- return <button ref={button2Ref}>Click me!</button>;
1804
- }
1805
-
1806
- function Test({extra}) {
1807
- const click = ReactDOM.unstable_useEvent('click', {
1808
- passive: undefined,
1809
- });
1810
-
1811
- React.useEffect(() => {
1812
- click.setListener(buttonRef.current, clickEvent);
1813
- });
1814
-
1815
- return (
1816
- <>
1817
- <button ref={buttonRef}>Click me!</button>
1818
- {extra && <Test2 />}
1819
- </>
1820
- );
1821
- }
1822
-
1823
- ReactDOM.render(<Test />, container);
1824
- Scheduler.unstable_flushAll();
1825
-
1826
- let button = buttonRef.current;
1827
- dispatchClickEvent(button);
1828
- expect(clickEvent).toHaveBeenCalledTimes(1);
1829
-
1830
- ReactDOM.render(<Test extra={true} />, container);
1831
- Scheduler.unstable_flushAll();
1832
-
1833
- clickEvent.mockClear();
1834
-
1835
- button = button2Ref.current;
1836
- dispatchClickEvent(button);
1837
- expect(clickEvent).toHaveBeenCalledTimes(1);
1838
- });
1839
-
1840
- // @gate experimental
1841
- it('should correctly work for a basic "click" window listener', () => {
1842
- const log = [];
1843
- const clickEvent = jest.fn(event => {
1844
- log.push({
1845
- eventPhase: event.eventPhase,
1846
- type: event.type,
1847
- currentTarget: event.currentTarget,
1848
- target: event.target,
1849
- });
1850
- });
1851
-
1852
- function Test() {
1853
- const click = ReactDOM.unstable_useEvent('click');
1854
-
1855
- React.useEffect(() => {
1856
- click.setListener(window, clickEvent);
1857
- });
1858
-
1859
- return <button>Click anything!</button>;
1860
- }
1861
- ReactDOM.render(<Test />, container);
1862
- Scheduler.unstable_flushAll();
1863
-
1864
- expect(container.innerHTML).toBe(
1865
- '<button>Click anything!</button>',
1866
- );
1867
-
1868
- // Clicking outside the button should trigger the event callback
1869
- dispatchClickEvent(document.body);
1870
- expect(log[0]).toEqual({
1871
- eventPhase: 3,
1872
- type: 'click',
1873
- currentTarget: window,
1874
- target: document.body,
1875
- });
1876
-
1877
- // Unmounting the container and clicking should not work
1878
- ReactDOM.render(null, container);
1879
- Scheduler.unstable_flushAll();
1880
-
1881
- dispatchClickEvent(document.body);
1882
- expect(clickEvent).toBeCalledTimes(1);
1883
-
1884
- // Re-rendering and clicking the body should work again
1885
- ReactDOM.render(<Test />, container);
1886
- Scheduler.unstable_flushAll();
1887
-
1888
- dispatchClickEvent(document.body);
1889
- expect(clickEvent).toBeCalledTimes(2);
1890
- });
1891
-
1892
- // @gate experimental
1893
- it('handle propagation of click events on the window', () => {
1894
- const buttonRef = React.createRef();
1895
- const divRef = React.createRef();
1896
- const log = [];
1897
- const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
1898
- const onClickCapture = jest.fn(e =>
1899
- log.push(['capture', e.currentTarget]),
1900
- );
1901
-
1902
- function Test() {
1903
- const click = ReactDOM.unstable_useEvent('click');
1904
- const clickCapture = ReactDOM.unstable_useEvent('click', {
1905
- capture: true,
1906
- });
1907
-
1908
- React.useEffect(() => {
1909
- click.setListener(window, onClick);
1910
- clickCapture.setListener(window, onClickCapture);
1911
- click.setListener(buttonRef.current, onClick);
1912
- clickCapture.setListener(buttonRef.current, onClickCapture);
1913
- click.setListener(divRef.current, onClick);
1914
- clickCapture.setListener(divRef.current, onClickCapture);
1915
- });
1916
-
1917
- return (
1918
- <button ref={buttonRef}>
1919
- <div ref={divRef}>Click me!</div>
1920
- </button>
1921
- );
1922
- }
1923
-
1924
- ReactDOM.render(<Test />, container);
1925
- Scheduler.unstable_flushAll();
1926
-
1927
- const buttonElement = buttonRef.current;
1928
- dispatchClickEvent(buttonElement);
1929
- expect(onClick).toHaveBeenCalledTimes(2);
1930
- expect(onClickCapture).toHaveBeenCalledTimes(2);
1931
- expect(log[0]).toEqual(['capture', window]);
1932
- expect(log[1]).toEqual(['capture', buttonElement]);
1933
- expect(log[2]).toEqual(['bubble', buttonElement]);
1934
- expect(log[3]).toEqual(['bubble', window]);
1935
-
1936
- log.length = 0;
1937
- onClick.mockClear();
1938
- onClickCapture.mockClear();
1939
-
1940
- const divElement = divRef.current;
1941
- dispatchClickEvent(divElement);
1942
- expect(onClick).toHaveBeenCalledTimes(3);
1943
- expect(onClickCapture).toHaveBeenCalledTimes(3);
1944
- expect(log[0]).toEqual(['capture', window]);
1945
- expect(log[1]).toEqual(['capture', buttonElement]);
1946
- expect(log[2]).toEqual(['capture', divElement]);
1947
- expect(log[3]).toEqual(['bubble', divElement]);
1948
- expect(log[4]).toEqual(['bubble', buttonElement]);
1949
- expect(log[5]).toEqual(['bubble', window]);
1950
- });
1951
-
1952
- // @gate experimental
1953
- it('should correctly handle stopPropagation for mixed listeners', () => {
1954
- const buttonRef = React.createRef();
1955
- const rootListerner1 = jest.fn(e => e.stopPropagation());
1956
- const rootListerner2 = jest.fn();
1957
- const targetListerner1 = jest.fn();
1958
- const targetListerner2 = jest.fn();
1959
-
1960
- function Test() {
1961
- const click1 = ReactDOM.unstable_useEvent('click', {
1962
- capture: true,
1963
- });
1964
- const click2 = ReactDOM.unstable_useEvent('click', {
1965
- capture: true,
1966
- });
1967
- const click3 = ReactDOM.unstable_useEvent('click');
1968
- const click4 = ReactDOM.unstable_useEvent('click');
1969
-
1970
- React.useEffect(() => {
1971
- click1.setListener(window, rootListerner1);
1972
- click2.setListener(buttonRef.current, targetListerner1);
1973
- click3.setListener(window, rootListerner2);
1974
- click4.setListener(buttonRef.current, targetListerner2);
1975
- });
1976
-
1977
- return <button ref={buttonRef}>Click me!</button>;
1978
- }
1979
-
1980
- ReactDOM.render(<Test />, container);
1981
- Scheduler.unstable_flushAll();
1982
-
1983
- const buttonElement = buttonRef.current;
1984
- dispatchClickEvent(buttonElement);
1985
- expect(rootListerner1).toHaveBeenCalledTimes(1);
1986
- expect(targetListerner1).toHaveBeenCalledTimes(0);
1987
- expect(targetListerner2).toHaveBeenCalledTimes(0);
1988
- expect(rootListerner2).toHaveBeenCalledTimes(0);
1989
- });
1990
-
1991
- // @gate experimental
1992
- it('should correctly handle stopPropagation for delegated listeners', () => {
1993
- const buttonRef = React.createRef();
1994
- const rootListerner1 = jest.fn(e => e.stopPropagation());
1995
- const rootListerner2 = jest.fn();
1996
- const rootListerner3 = jest.fn(e => e.stopPropagation());
1997
- const rootListerner4 = jest.fn();
1998
-
1999
- function Test() {
2000
- const click1 = ReactDOM.unstable_useEvent('click', {
2001
- capture: true,
2002
- });
2003
- const click2 = ReactDOM.unstable_useEvent('click', {
2004
- capture: true,
2005
- });
2006
- const click3 = ReactDOM.unstable_useEvent('click');
2007
- const click4 = ReactDOM.unstable_useEvent('click');
2008
-
2009
- React.useEffect(() => {
2010
- click1.setListener(window, rootListerner1);
2011
- click2.setListener(window, rootListerner2);
2012
- click3.setListener(window, rootListerner3);
2013
- click4.setListener(window, rootListerner4);
2014
- });
2015
-
2016
- return <button ref={buttonRef}>Click me!</button>;
2017
- }
2018
-
2019
- ReactDOM.render(<Test />, container);
2020
-
2021
- Scheduler.unstable_flushAll();
2022
-
2023
- const buttonElement = buttonRef.current;
2024
- dispatchClickEvent(buttonElement);
2025
- expect(rootListerner1).toHaveBeenCalledTimes(1);
2026
- expect(rootListerner2).toHaveBeenCalledTimes(1);
2027
- expect(rootListerner3).toHaveBeenCalledTimes(0);
2028
- expect(rootListerner4).toHaveBeenCalledTimes(0);
2029
- });
2030
-
2031
- // @gate experimental
2032
- it('handle propagation of click events on the window and document', () => {
2033
- const buttonRef = React.createRef();
2034
- const divRef = React.createRef();
2035
- const log = [];
2036
- const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
2037
- const onClickCapture = jest.fn(e =>
2038
- log.push(['capture', e.currentTarget]),
2039
- );
2040
-
2041
- function Test() {
2042
- const click = ReactDOM.unstable_useEvent('click');
2043
- const clickCapture = ReactDOM.unstable_useEvent('click', {
2044
- capture: true,
2045
- });
2046
-
2047
- React.useEffect(() => {
2048
- click.setListener(window, onClick);
2049
- clickCapture.setListener(window, onClickCapture);
2050
- click.setListener(document, onClick);
2051
- clickCapture.setListener(document, onClickCapture);
2052
- click.setListener(buttonRef.current, onClick);
2053
- clickCapture.setListener(buttonRef.current, onClickCapture);
2054
- click.setListener(divRef.current, onClick);
2055
- clickCapture.setListener(divRef.current, onClickCapture);
2056
- });
2057
-
2058
- return (
2059
- <button ref={buttonRef}>
2060
- <div ref={divRef}>Click me!</div>
2061
- </button>
2062
- );
2063
- }
2064
-
2065
- ReactDOM.render(<Test />, container);
2066
- Scheduler.unstable_flushAll();
2067
-
2068
- const buttonElement = buttonRef.current;
2069
- dispatchClickEvent(buttonElement);
2070
- expect(onClick).toHaveBeenCalledTimes(3);
2071
- expect(onClickCapture).toHaveBeenCalledTimes(3);
2072
- expect(log[0]).toEqual(['capture', window]);
2073
- expect(log[1]).toEqual(['capture', document]);
2074
- expect(log[2]).toEqual(['capture', buttonElement]);
2075
- expect(log[3]).toEqual(['bubble', buttonElement]);
2076
- expect(log[4]).toEqual(['bubble', document]);
2077
- expect(log[5]).toEqual(['bubble', window]);
2078
-
2079
- log.length = 0;
2080
- onClick.mockClear();
2081
- onClickCapture.mockClear();
2082
-
2083
- const divElement = divRef.current;
2084
- dispatchClickEvent(divElement);
2085
- expect(onClick).toHaveBeenCalledTimes(4);
2086
- expect(onClickCapture).toHaveBeenCalledTimes(4);
2087
- expect(log[0]).toEqual(['capture', window]);
2088
- expect(log[1]).toEqual(['capture', document]);
2089
- expect(log[2]).toEqual(['capture', buttonElement]);
2090
- expect(log[3]).toEqual(['capture', divElement]);
2091
- expect(log[4]).toEqual(['bubble', divElement]);
2092
- expect(log[5]).toEqual(['bubble', buttonElement]);
2093
- expect(log[6]).toEqual(['bubble', document]);
2094
- expect(log[7]).toEqual(['bubble', window]);
2095
- });
2096
-
2097
- // @gate experimental
2098
- it('handles propagation of custom user events', () => {
2099
- const buttonRef = React.createRef();
2100
- const divRef = React.createRef();
2101
- const log = [];
2102
- const onCustomEvent = jest.fn(e =>
2103
- log.push(['bubble', e.currentTarget]),
2104
- );
2105
- const onCustomEventCapture = jest.fn(e =>
2106
- log.push(['capture', e.currentTarget]),
2107
- );
2108
-
2109
- function Test() {
2110
- let customEventHandle;
2111
-
2112
- // Test that we get a warning when we don't provide an explicit priortiy
2113
- expect(() => {
2114
- customEventHandle = ReactDOM.unstable_useEvent('custom-event');
2115
- }).toWarnDev(
2116
- 'Warning: The event "type" provided to useEvent() does not have a known priority type. ' +
2117
- 'It is recommended to provide a "priority" option to specify a priority.',
2118
- );
2119
-
2120
- customEventHandle = ReactDOM.unstable_useEvent('custom-event', {
2121
- priority: 0, // Discrete
2122
- });
2123
-
2124
- const customCaptureHandle = ReactDOM.unstable_useEvent(
2125
- 'custom-event',
2126
- {
2127
- capture: true,
2128
- priority: 0, // Discrete
2129
- },
2130
- );
2131
-
2132
- React.useEffect(() => {
2133
- customEventHandle.setListener(buttonRef.current, onCustomEvent);
2134
- customCaptureHandle.setListener(
2135
- buttonRef.current,
2136
- onCustomEventCapture,
2137
- );
2138
- customEventHandle.setListener(divRef.current, onCustomEvent);
2139
- customCaptureHandle.setListener(
2140
- divRef.current,
2141
- onCustomEventCapture,
2142
- );
2143
- });
2144
-
2145
- return (
2146
- <button ref={buttonRef}>
2147
- <div ref={divRef}>Click me!</div>
2148
- </button>
2149
- );
2150
- }
2151
-
2152
- ReactDOM.render(<Test />, container);
2153
- Scheduler.unstable_flushAll();
2154
-
2155
- const buttonElement = buttonRef.current;
2156
- dispatchEvent(buttonElement, 'custom-event');
2157
- expect(onCustomEvent).toHaveBeenCalledTimes(1);
2158
- expect(onCustomEventCapture).toHaveBeenCalledTimes(1);
2159
- expect(log[0]).toEqual(['capture', buttonElement]);
2160
- expect(log[1]).toEqual(['bubble', buttonElement]);
2161
-
2162
- const divElement = divRef.current;
2163
- dispatchEvent(divElement, 'custom-event');
2164
- expect(onCustomEvent).toHaveBeenCalledTimes(3);
2165
- expect(onCustomEventCapture).toHaveBeenCalledTimes(3);
2166
- expect(log[2]).toEqual(['capture', buttonElement]);
2167
- expect(log[3]).toEqual(['capture', divElement]);
2168
- expect(log[4]).toEqual(['bubble', divElement]);
2169
- expect(log[5]).toEqual(['bubble', buttonElement]);
2170
- });
2171
-
2172
- // @gate experimental
2173
- it('beforeblur and afterblur are called after a focused element is unmounted', () => {
2174
- const log = [];
2175
- // We have to persist here because we want to read relatedTarget later.
2176
- const onAfterBlur = jest.fn(e => {
2177
- e.persist();
2178
- log.push(e.type);
2179
- });
2180
- const onBeforeBlur = jest.fn(e => log.push(e.type));
2181
- const innerRef = React.createRef();
2182
- const innerRef2 = React.createRef();
2183
-
2184
- const Component = ({show}) => {
2185
- const ref = React.useRef(null);
2186
- const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
2187
- const beforeBlurHandle = ReactDOM.unstable_useEvent('beforeblur');
2188
-
2189
- React.useEffect(() => {
2190
- afterBlurHandle.setListener(document, onAfterBlur);
2191
- beforeBlurHandle.setListener(ref.current, onBeforeBlur);
2192
- });
2193
-
2194
- return (
2195
- <div ref={ref}>
2196
- {show && <input ref={innerRef} />}
2197
- <div ref={innerRef2} />
2198
- </div>
2199
- );
2200
- };
2201
-
2202
- ReactDOM.render(<Component show={true} />, container);
2203
- Scheduler.unstable_flushAll();
2204
-
2205
- const inner = innerRef.current;
2206
- const target = createEventTarget(inner);
2207
- target.focus();
2208
- expect(onBeforeBlur).toHaveBeenCalledTimes(0);
2209
- expect(onAfterBlur).toHaveBeenCalledTimes(0);
2210
-
2211
- ReactDOM.render(<Component show={false} />, container);
2212
- Scheduler.unstable_flushAll();
2213
-
2214
- expect(onBeforeBlur).toHaveBeenCalledTimes(1);
2215
- expect(onAfterBlur).toHaveBeenCalledTimes(1);
2216
- expect(onAfterBlur).toHaveBeenCalledWith(
2217
- expect.objectContaining({relatedTarget: inner}),
2218
- );
2219
- expect(log).toEqual(['beforeblur', 'afterblur']);
2220
- });
2221
-
2222
- // @gate experimental
2223
- it('beforeblur and afterblur are called after a nested focused element is unmounted', () => {
2224
- const log = [];
2225
- // We have to persist here because we want to read relatedTarget later.
2226
- const onAfterBlur = jest.fn(e => {
2227
- e.persist();
2228
- log.push(e.type);
2229
- });
2230
- const onBeforeBlur = jest.fn(e => log.push(e.type));
2231
- const innerRef = React.createRef();
2232
- const innerRef2 = React.createRef();
2233
-
2234
- const Component = ({show}) => {
2235
- const ref = React.useRef(null);
2236
- const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
2237
- const beforeBlurHandle = ReactDOM.unstable_useEvent('beforeblur');
2238
-
2239
- React.useEffect(() => {
2240
- afterBlurHandle.setListener(document, onAfterBlur);
2241
- beforeBlurHandle.setListener(ref.current, onBeforeBlur);
2242
- });
2243
-
2244
- return (
2245
- <div ref={ref}>
2246
- {show && (
2247
- <div>
2248
- <input ref={innerRef} />
2249
- </div>
2250
- )}
2251
- <div ref={innerRef2} />
2252
- </div>
2253
- );
2254
- };
2255
-
2256
- ReactDOM.render(<Component show={true} />, container);
2257
- Scheduler.unstable_flushAll();
2258
-
2259
- const inner = innerRef.current;
2260
- const target = createEventTarget(inner);
2261
- target.focus();
2262
- expect(onBeforeBlur).toHaveBeenCalledTimes(0);
2263
- expect(onAfterBlur).toHaveBeenCalledTimes(0);
2264
-
2265
- ReactDOM.render(<Component show={false} />, container);
2266
- Scheduler.unstable_flushAll();
2267
-
2268
- expect(onBeforeBlur).toHaveBeenCalledTimes(1);
2269
- expect(onAfterBlur).toHaveBeenCalledTimes(1);
2270
- expect(onAfterBlur).toHaveBeenCalledWith(
2271
- expect.objectContaining({relatedTarget: inner}),
2272
- );
2273
- expect(log).toEqual(['beforeblur', 'afterblur']);
2274
- });
2275
-
2276
- // @gate experimental
2277
- it('beforeblur and afterblur are called after a focused element is suspended', () => {
2278
- const log = [];
2279
- // We have to persist here because we want to read relatedTarget later.
2280
- const onAfterBlur = jest.fn(e => {
2281
- e.persist();
2282
- log.push(e.type);
2283
- });
2284
- const onBeforeBlur = jest.fn(e => log.push(e.type));
2285
- const innerRef = React.createRef();
2286
- const Suspense = React.Suspense;
2287
- let suspend = false;
2288
- let resolve;
2289
- const promise = new Promise(
2290
- resolvePromise => (resolve = resolvePromise),
2291
- );
2292
-
2293
- function Child() {
2294
- if (suspend) {
2295
- throw promise;
2296
- } else {
2297
- return <input ref={innerRef} />;
2298
- }
2299
- }
2300
-
2301
- const Component = () => {
2302
- const ref = React.useRef(null);
2303
- const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
2304
- const beforeBlurHandle = ReactDOM.unstable_useEvent('beforeblur');
2305
-
2306
- React.useEffect(() => {
2307
- afterBlurHandle.setListener(document, onAfterBlur);
2308
- beforeBlurHandle.setListener(ref.current, onBeforeBlur);
2309
- });
2310
-
2311
- return (
2312
- <div ref={ref}>
2313
- <Suspense fallback="Loading...">
2314
- <Child />
2315
- </Suspense>
2316
- </div>
2317
- );
2318
- };
2319
-
2320
- const container2 = document.createElement('div');
2321
- document.body.appendChild(container2);
2322
-
2323
- const root = ReactDOM.createRoot(container2);
2324
-
2325
- ReactTestUtils.act(() => {
2326
- root.render(<Component />);
2327
- });
2328
- jest.runAllTimers();
2329
-
2330
- const inner = innerRef.current;
2331
- const target = createEventTarget(inner);
2332
- target.focus();
2333
- expect(onBeforeBlur).toHaveBeenCalledTimes(0);
2334
- expect(onAfterBlur).toHaveBeenCalledTimes(0);
2335
-
2336
- suspend = true;
2337
- ReactTestUtils.act(() => {
2338
- root.render(<Component />);
2339
- });
2340
- jest.runAllTimers();
2341
-
2342
- expect(onBeforeBlur).toHaveBeenCalledTimes(1);
2343
- expect(onAfterBlur).toHaveBeenCalledTimes(1);
2344
- expect(onAfterBlur).toHaveBeenCalledWith(
2345
- expect.objectContaining({relatedTarget: inner}),
2346
- );
2347
- resolve();
2348
- expect(log).toEqual(['beforeblur', 'afterblur']);
2349
-
2350
- document.body.removeChild(container2);
2351
- });
2352
-
2353
- describe('Compatibility with Scopes API', () => {
2354
- beforeEach(() => {
2355
- jest.resetModules();
2356
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
2357
- ReactFeatureFlags.enableModernEventSystem = true;
2358
- ReactFeatureFlags.enableUseEventAPI = true;
2359
- ReactFeatureFlags.enableScopeAPI = true;
2360
-
2361
- React = require('react');
2362
- ReactDOM = require('react-dom');
2363
- Scheduler = require('scheduler');
2364
- ReactDOMServer = require('react-dom/server');
2365
- });
2366
-
2367
- // @gate experimental
2368
- it('handle propagation of click events on a scope', () => {
2369
- const buttonRef = React.createRef();
2370
- const log = [];
2371
- const onClick = jest.fn(e =>
2372
- log.push(['bubble', e.currentTarget]),
2373
- );
2374
- const onClickCapture = jest.fn(e =>
2375
- log.push(['capture', e.currentTarget]),
2376
- );
2377
- const TestScope = React.unstable_createScope();
2378
-
2379
- function Test() {
2380
- const click = ReactDOM.unstable_useEvent('click');
2381
- const clickCapture = ReactDOM.unstable_useEvent('click', {
2382
- capture: true,
2383
- });
2384
- const scopeRef = React.useRef(null);
2385
-
2386
- React.useEffect(() => {
2387
- click.setListener(scopeRef.current, onClick);
2388
- clickCapture.setListener(scopeRef.current, onClickCapture);
2389
- });
2390
-
2391
- return (
2392
- <TestScope ref={scopeRef}>
2393
- <button ref={buttonRef} />
2394
- </TestScope>
2395
- );
2396
- }
2397
-
2398
- ReactDOM.render(<Test />, container);
2399
- Scheduler.unstable_flushAll();
2400
-
2401
- const buttonElement = buttonRef.current;
2402
- dispatchClickEvent(buttonElement);
2403
-
2404
- expect(onClick).toHaveBeenCalledTimes(1);
2405
- expect(onClickCapture).toHaveBeenCalledTimes(1);
2406
- expect(log).toEqual([
2407
- ['capture', buttonElement],
2408
- ['bubble', buttonElement],
2409
- ]);
2410
- });
2411
-
2412
- // @gate experimental
2413
- it('handle mixed propagation of click events on a scope', () => {
2414
- const buttonRef = React.createRef();
2415
- const divRef = React.createRef();
2416
- const log = [];
2417
- const onClick = jest.fn(e =>
2418
- log.push(['bubble', e.currentTarget]),
2419
- );
2420
- const onClickCapture = jest.fn(e =>
2421
- log.push(['capture', e.currentTarget]),
2422
- );
2423
- const TestScope = React.unstable_createScope();
2424
-
2425
- function Test() {
2426
- const click = ReactDOM.unstable_useEvent('click');
2427
- const clickCapture = ReactDOM.unstable_useEvent('click', {
2428
- capture: true,
2429
- });
2430
- const scopeRef = React.useRef(null);
2431
-
2432
- React.useEffect(() => {
2433
- click.setListener(scopeRef.current, onClick);
2434
- clickCapture.setListener(scopeRef.current, onClickCapture);
2435
- click.setListener(buttonRef.current, onClick);
2436
- clickCapture.setListener(buttonRef.current, onClickCapture);
2437
- });
2438
-
2439
- return (
2440
- <TestScope ref={scopeRef}>
2441
- <button ref={buttonRef}>
2442
- <div
2443
- ref={divRef}
2444
- onClick={onClick}
2445
- onClickCapture={onClickCapture}>
2446
- Click me!
2447
- </div>
2448
- </button>
2449
- </TestScope>
2450
- );
2451
- }
2452
-
2453
- ReactDOM.render(<Test />, container);
2454
- Scheduler.unstable_flushAll();
2455
-
2456
- const buttonElement = buttonRef.current;
2457
- dispatchClickEvent(buttonElement);
2458
-
2459
- expect(onClick).toHaveBeenCalledTimes(2);
2460
- expect(onClickCapture).toHaveBeenCalledTimes(2);
2461
- expect(log).toEqual([
2462
- ['capture', buttonElement],
2463
- ['capture', buttonElement],
2464
- ['bubble', buttonElement],
2465
- ['bubble', buttonElement],
2466
- ]);
2467
-
2468
- log.length = 0;
2469
- onClick.mockClear();
2470
- onClickCapture.mockClear();
2471
-
2472
- const divElement = divRef.current;
2473
- dispatchClickEvent(divElement);
2474
-
2475
- expect(onClick).toHaveBeenCalledTimes(3);
2476
- expect(onClickCapture).toHaveBeenCalledTimes(3);
2477
- expect(log).toEqual([
2478
- ['capture', buttonElement],
2479
- ['capture', buttonElement],
2480
- ['capture', divElement],
2481
- ['bubble', divElement],
2482
- ['bubble', buttonElement],
2483
- ['bubble', buttonElement],
2484
- ]);
2485
- });
2486
-
2487
- // @gate experimental
2488
- it('should not handle the target being a dangling text node within a scope', () => {
2489
- const clickEvent = jest.fn();
2490
- const buttonRef = React.createRef();
2491
-
2492
- const TestScope = React.unstable_createScope();
2493
-
2494
- function Test() {
2495
- const click = ReactDOM.unstable_useEvent('click');
2496
- const scopeRef = React.useRef(null);
2497
-
2498
- React.useEffect(() => {
2499
- click.setListener(scopeRef.current, clickEvent);
2500
- });
2501
-
2502
- return (
2503
- <button ref={buttonRef}>
2504
- <TestScope ref={scopeRef}>Click me!</TestScope>
2505
- </button>
2506
- );
2507
- }
2508
-
2509
- ReactDOM.render(<Test />, container);
2510
- Scheduler.unstable_flushAll();
2511
-
2512
- const textNode = buttonRef.current.firstChild;
2513
- dispatchClickEvent(textNode);
2514
- // This should not work, as the target instance will be the
2515
- // <button>, which is actually outside the scope.
2516
- expect(clickEvent).toBeCalledTimes(0);
2517
- });
2518
-
2519
- // @gate experimental
2520
- it('handle stopPropagation (inner) correctly between scopes', () => {
2521
- const buttonRef = React.createRef();
2522
- const outerOnClick = jest.fn();
2523
- const innerOnClick = jest.fn(e => e.stopPropagation());
2524
- const TestScope = React.unstable_createScope();
2525
- const TestScope2 = React.unstable_createScope();
2526
-
2527
- function Test() {
2528
- const click = ReactDOM.unstable_useEvent('click');
2529
- const scopeRef = React.useRef(null);
2530
- const scope2Ref = React.useRef(null);
2531
-
2532
- React.useEffect(() => {
2533
- click.setListener(scopeRef.current, outerOnClick);
2534
- click.setListener(scope2Ref.current, innerOnClick);
2535
- });
2536
-
2537
- return (
2538
- <TestScope ref={scopeRef}>
2539
- <TestScope2 ref={scope2Ref}>
2540
- <button ref={buttonRef} />
2541
- </TestScope2>
2542
- </TestScope>
2543
- );
2544
- }
2545
-
2546
- ReactDOM.render(<Test />, container);
2547
- Scheduler.unstable_flushAll();
2548
-
2549
- const buttonElement = buttonRef.current;
2550
- dispatchClickEvent(buttonElement);
2551
-
2552
- expect(innerOnClick).toHaveBeenCalledTimes(1);
2553
- expect(outerOnClick).toHaveBeenCalledTimes(0);
2554
- });
2555
-
2556
- // @gate experimental
2557
- it('handle stopPropagation (outer) correctly between scopes', () => {
2558
- const buttonRef = React.createRef();
2559
- const outerOnClick = jest.fn(e => e.stopPropagation());
2560
- const innerOnClick = jest.fn();
2561
- const TestScope = React.unstable_createScope();
2562
- const TestScope2 = React.unstable_createScope();
2563
-
2564
- function Test() {
2565
- const click = ReactDOM.unstable_useEvent('click');
2566
- const scopeRef = React.useRef(null);
2567
- const scope2Ref = React.useRef(null);
2568
-
2569
- React.useEffect(() => {
2570
- click.setListener(scopeRef.current, outerOnClick);
2571
- click.setListener(scope2Ref.current, innerOnClick);
2572
- });
2573
-
2574
- return (
2575
- <TestScope ref={scopeRef}>
2576
- <TestScope2 ref={scope2Ref}>
2577
- <button ref={buttonRef} />
2578
- </TestScope2>
2579
- </TestScope>
2580
- );
2581
- }
2582
-
2583
- ReactDOM.render(<Test />, container);
2584
- Scheduler.unstable_flushAll();
2585
-
2586
- const buttonElement = buttonRef.current;
2587
- dispatchClickEvent(buttonElement);
2588
-
2589
- expect(innerOnClick).toHaveBeenCalledTimes(1);
2590
- expect(outerOnClick).toHaveBeenCalledTimes(1);
2591
- });
2592
-
2593
- // @gate experimental
2594
-
2595
- it('handle stopPropagation (inner and outer) correctly between scopes', () => {
2596
- const buttonRef = React.createRef();
2597
- const onClick = jest.fn(e => e.stopPropagation());
2598
- const TestScope = React.unstable_createScope();
2599
- const TestScope2 = React.unstable_createScope();
2600
-
2601
- function Test() {
2602
- const click = ReactDOM.unstable_useEvent('click');
2603
- const scopeRef = React.useRef(null);
2604
- const scope2Ref = React.useRef(null);
2605
-
2606
- React.useEffect(() => {
2607
- click.setListener(scopeRef.current, onClick);
2608
- click.setListener(scope2Ref.current, onClick);
2609
- });
2610
-
2611
- return (
2612
- <TestScope ref={scopeRef}>
2613
- <TestScope2 ref={scope2Ref}>
2614
- <button ref={buttonRef} />
2615
- </TestScope2>
2616
- </TestScope>
2617
- );
2618
- }
2619
-
2620
- ReactDOM.render(<Test />, container);
2621
- Scheduler.unstable_flushAll();
2622
-
2623
- const buttonElement = buttonRef.current;
2624
- dispatchClickEvent(buttonElement);
2625
-
2626
- expect(onClick).toHaveBeenCalledTimes(1);
2627
- });
2628
- });
2629
- });
1081
},
1082
);
1083
}
packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js
+2
-23
@@ -17,17 +17,13 @@ import type {PluginModule} from 'legacy-events/PluginModuleType';
17
import type {EventSystemFlags} from '../EventSystemFlags';
18
19
import SyntheticEvent from 'legacy-events/SyntheticEvent';
20
-import {IS_TARGET_PHASE_ONLY} from '../EventSystemFlags';
20
21
import * as DOMTopLevelEventTypes from '../DOMTopLevelEventTypes';
22
import {
23
topLevelEventsToDispatchConfig,
24
simpleEventPluginEventTypes,
25
} from '../DOMEventProperties';
27
-import {
28
- accumulateEventTargetListeners,
29
- accumulateTwoPhaseListeners,
30
-} from '../DOMModernPluginEventSystem';
26
+import {accumulateTwoPhaseListeners} from '../DOMModernPluginEventSystem';
27
28
import SyntheticAnimationEvent from '../SyntheticAnimationEvent';
29
import SyntheticClipboardEvent from '../SyntheticClipboardEvent';
@@ -42,8 +38,6 @@ import SyntheticUIEvent from '../SyntheticUIEvent';
38
import SyntheticWheelEvent from '../SyntheticWheelEvent';
39
import getEventCharCode from '../getEventCharCode';
40
45
-import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
46
-
41
// Only used in DEV for exhaustiveness validation.
42
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
43
DOMTopLevelEventTypes.TOP_ABORT,
@@ -204,22 +198,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
198
nativeEventTarget,
199
);
200
207
- // For TargetEvent only accumulation, we do not traverse through
208
- // the React tree looking for managed React DOM elements that have
209
- // events. Instead we only check the EventTarget Store Map to see
210
- // if the container has listeners for the particular phase we're
211
- // interested in. This is because we attach the native event listener
212
- // only in the given phase.
213
- if (
214
- enableUseEventAPI &&
215
- eventSystemFlags !== undefined &&
216
- eventSystemFlags & IS_TARGET_PHASE_ONLY &&
217
- targetContainer != null
218
- ) {
219
- accumulateEventTargetListeners(event, targetContainer);
220
- } else {
221
- accumulateTwoPhaseListeners(event, true);
222
- }
201
+ accumulateTwoPhaseListeners(event);
202
return event;
203
},
204
};
packages/react-dom/src/server/ReactPartialRendererHooks.js
-9
@@ -19,7 +19,6 @@ import type {
19
ReactEventResponderListener,
20
} from 'shared/ReactTypes';
21
import type {SuspenseConfig} from 'react-reconciler/src/ReactFiberSuspenseConfig';
22
-import type {ReactDOMListenerMap} from '../shared/ReactDOMTypes';
22
23
import {validateContextBounds} from './ReactPartialRendererContext';
24
import {makeServerId} from '../client/ReactDOMHostConfig';
@@ -499,13 +498,6 @@ function useOpaqueIdentifier(): OpaqueIDType {
498
return makeServerId();
499
}
500
502
-function useEvent(event: any): ReactDOMListenerMap {
503
- return {
504
- clear: noop,
505
- setListener: noop,
506
- };
507
-}
508
-
501
function noop(): void {}
502
503
export let currentThreadID: ThreadID = 0;
@@ -532,7 +524,6 @@ export const Dispatcher: DispatcherType = {
524
useResponder,
525
useDeferredValue,
526
useTransition,
535
- useEvent,
527
useOpaqueIdentifier,
528
// Subscriptions are not setup in a server environment.
529
useMutableSource,
packages/react-dom/src/shared/ReactDOMTypes.js
-24
@@ -12,9 +12,7 @@ import type {
12
ReactEventResponder,
13
ReactEventResponderInstance,
14
EventPriority,
15
- ReactScopeMethods,
15
} from 'shared/ReactTypes';
17
-import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
16
17
type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
18
@@ -77,25 +75,3 @@ export type ReactDOMResponderContext = {
75
getResponderNode(): Element | null,
76
...
77
};
80
-
81
-export type ReactDOMListenerEvent = {|
82
- capture: boolean,
83
- passive: void | boolean,
84
- priority: EventPriority,
85
- type: DOMTopLevelEventType,
86
-|};
87
-
88
-export type ReactDOMListenerMap = {|
89
- clear: () => void,
90
- setListener: (
91
- target: EventTarget | ReactScopeMethods,
92
- callback: ?(SyntheticEvent<EventTarget>) => void,
93
- ) => void,
94
-|};
95
-
96
-export type ReactDOMListener = {|
97
- callback: (SyntheticEvent<EventTarget>) => void,
98
- destroy: Node => void,
99
- event: ReactDOMListenerEvent,
100
- target: EventTarget | ReactScopeMethods,
101
-|};
packages/react-interactions/events/focus.js
deleted
-10
@@ -1,10 +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
-export * from './src/dom/use-event/Focus';
packages/react-interactions/events/src/dom/use-event/Focus.js
deleted
-440
@@ -1,440 +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 * as React from 'react';
11
-import * as ReactDOM from 'react-dom';
12
-
13
-const useEvent = ReactDOM.unstable_useEvent;
14
-const {useEffect, useRef} = React;
15
-
16
-type UseFocusOptions = {|
17
- disabled?: boolean,
18
- onBlur?: (SyntheticEvent<EventTarget>) => void,
19
- onFocus?: (SyntheticEvent<EventTarget>) => void,
20
- onFocusChange?: boolean => void,
21
- onFocusVisibleChange?: boolean => void,
22
-|};
23
-
24
-type UseFocusWithinOptions = {|
25
- disabled?: boolean,
26
- onAfterBlurWithin?: (SyntheticEvent<EventTarget>) => void,
27
- onBeforeBlurWithin?: (SyntheticEvent<EventTarget>) => void,
28
- onBlurWithin?: (SyntheticEvent<EventTarget>) => void,
29
- onFocusWithin?: (SyntheticEvent<EventTarget>) => void,
30
- onFocusWithinChange?: boolean => void,
31
- onFocusWithinVisibleChange?: boolean => void,
32
-|};
33
-
34
-const isMac =
35
- typeof window !== 'undefined' && window.navigator != null
36
- ? /^Mac/.test(window.navigator.platform)
37
- : false;
38
-
39
-const canUseDOM: boolean = !!(
40
- typeof window !== 'undefined' &&
41
- typeof window.document !== 'undefined' &&
42
- typeof window.document.createElement !== 'undefined'
43
-);
44
-
45
-let passiveBrowserEventsSupported = false;
46
-
47
-// Check if browser support events with passive listeners
48
-// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
49
-if (canUseDOM) {
50
- try {
51
- const options = {};
52
- // $FlowFixMe: Ignore Flow complaining about needing a value
53
- Object.defineProperty(options, 'passive', {
54
- get: function() {
55
- passiveBrowserEventsSupported = true;
56
- },
57
- });
58
- window.addEventListener('test', options, options);
59
- window.removeEventListener('test', options, options);
60
- } catch (e) {
61
- passiveBrowserEventsSupported = false;
62
- }
63
-}
64
-
65
-const hasPointerEvents =
66
- typeof window !== 'undefined' && window.PointerEvent != null;
67
-
68
-const globalFocusVisibleEvents = hasPointerEvents
69
- ? ['keydown', 'pointermove', 'pointerdown', 'pointerup']
70
- : ['keydown', 'mousedown', 'touchmove', 'touchstart', 'touchend'];
71
-
72
-// Global state for tracking focus visible and emulation of mouse
73
-let isGlobalFocusVisible = true;
74
-let hasTrackedGlobalFocusVisible = false;
75
-let isEmulatingMouseEvents = false;
76
-
77
-function trackGlobalFocusVisible() {
78
- globalFocusVisibleEvents.forEach(type => {
79
- window.addEventListener(
80
- type,
81
- handleGlobalFocusVisibleEvent,
82
- passiveBrowserEventsSupported ? {capture: true, passive: true} : true,
83
- );
84
- });
85
-}
86
-
87
-function handleGlobalFocusVisibleEvent(
88
- nativeEvent: MouseEvent | TouchEvent | KeyboardEvent,
89
-): void {
90
- const {type} = nativeEvent;
91
-
92
- switch (type) {
93
- case 'pointermove':
94
- case 'pointerdown':
95
- case 'pointerup': {
96
- isGlobalFocusVisible = false;
97
- break;
98
- }
99
-
100
- case 'keydown': {
101
- const {metaKey, altKey, ctrlKey} = nativeEvent;
102
- const validKey = !(metaKey || (!isMac && altKey) || ctrlKey);
103
-
104
- if (validKey) {
105
- isGlobalFocusVisible = true;
106
- }
107
- break;
108
- }
109
-
110
- // fallbacks for no PointerEvent support
111
- case 'touchmove':
112
- case 'touchstart':
113
- case 'touchend': {
114
- isEmulatingMouseEvents = true;
115
- isGlobalFocusVisible = false;
116
- break;
117
- }
118
- case 'mousedown': {
119
- if (!isEmulatingMouseEvents) {
120
- isGlobalFocusVisible = false;
121
- } else {
122
- isEmulatingMouseEvents = false;
123
- }
124
- break;
125
- }
126
- }
127
-}
128
-
129
-const passiveObject = {passive: true};
130
-
131
-function handleFocusVisibleTargetEvent(
132
- type: string,
133
- focusTarget: EventTarget,
134
- callback: boolean => void,
135
-): void {
136
- isGlobalFocusVisible = false;
137
-
138
- // Focus should stop being visible if a pointer is used on the element
139
- // after it was focused using a keyboard.
140
- if (
141
- focusTarget !== null &&
142
- (type === 'mousedown' || type === 'touchstart' || type === 'pointerdown')
143
- ) {
144
- callback(false);
145
- }
146
-}
147
-
148
-function handleFocusVisibleTargetEvents(
149
- event: SyntheticEvent<EventTarget>,
150
- focusTarget,
151
- callback,
152
-): void {
153
- const {type} = event;
154
-
155
- switch (type) {
156
- case 'pointermove':
157
- case 'pointerdown':
158
- case 'pointerup': {
159
- handleFocusVisibleTargetEvent(type, focusTarget, callback);
160
- break;
161
- }
162
-
163
- case 'keydown':
164
- case 'keyup': {
165
- const {metaKey, altKey, ctrlKey} = (event: any);
166
- const validKey = !(metaKey || (!isMac && altKey) || ctrlKey);
167
-
168
- if (validKey) {
169
- if (focusTarget !== null) {
170
- callback(true);
171
- }
172
- }
173
- break;
174
- }
175
-
176
- // fallbacks for no PointerEvent support
177
- case 'touchmove':
178
- case 'touchstart':
179
- case 'touchend': {
180
- handleFocusVisibleTargetEvent(type, focusTarget, callback);
181
- break;
182
- }
183
- case 'mousedown': {
184
- if (!isEmulatingMouseEvents) {
185
- handleFocusVisibleTargetEvent(type, focusTarget, callback);
186
- }
187
- break;
188
- }
189
- }
190
-}
191
-
192
-function isRelatedTargetWithin(
193
- focusWithinTarget: Node,
194
- relatedTarget: null | Node,
195
-): boolean {
196
- if (relatedTarget == null) {
197
- return false;
198
- }
199
- // To support experimental scopes, which can be the target:
200
- const containsNode = (focusWithinTarget: any).containsNode;
201
- if (typeof containsNode === 'function') {
202
- return containsNode(relatedTarget);
203
- }
204
- return focusWithinTarget.contains(relatedTarget);
205
-}
206
-
207
-function setFocusVisibleListeners(focusVisibleHandles, focusTarget, callback) {
208
- focusVisibleHandles.forEach(focusVisibleHandle => {
209
- focusVisibleHandle.setListener(focusTarget, event =>
210
- handleFocusVisibleTargetEvents(event, focusTarget, callback),
211
- );
212
- });
213
-}
214
-
215
-function useFocusVisibleInputHandles() {
216
- return [
217
- useEvent('mousedown', passiveObject),
218
- useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart', passiveObject),
219
- useEvent(hasPointerEvents ? 'pointermove' : 'touchmove', passiveObject),
220
- useEvent(hasPointerEvents ? 'pointerup' : 'touchend', passiveObject),
221
- useEvent('keydown', passiveObject),
222
- ];
223
-}
224
-
225
-function useFocusLifecycles(stateRef) {
226
- useEffect(() => {
227
- if (!hasTrackedGlobalFocusVisible) {
228
- hasTrackedGlobalFocusVisible = true;
229
- trackGlobalFocusVisible();
230
- }
231
- }, []);
232
-}
233
-
234
-export function useFocus(
235
- focusTargetRef: {current: null | Node},
236
- {
237
- disabled,
238
- onBlur,
239
- onFocus,
240
- onFocusChange,
241
- onFocusVisibleChange,
242
- }: UseFocusOptions,
243
-): void {
244
- // Setup controlled state for this useFocus hook
245
- const stateRef = useRef({isFocused: false, isFocusVisible: false});
246
- const focusHandle = useEvent('focus', passiveObject);
247
- const blurHandle = useEvent('blur', passiveObject);
248
- const focusVisibleHandles = useFocusVisibleInputHandles();
249
-
250
- useEffect(() => {
251
- const focusTarget = focusTargetRef.current;
252
- const state = stateRef.current;
253
-
254
- if (focusTarget !== null && state !== null) {
255
- // Handle focus visible
256
- setFocusVisibleListeners(
257
- focusVisibleHandles,
258
- focusTarget,
259
- isFocusVisible => {
260
- if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
261
- state.isFocusVisible = isFocusVisible;
262
- if (onFocusVisibleChange) {
263
- onFocusVisibleChange(isFocusVisible);
264
- }
265
- }
266
- },
267
- );
268
-
269
- // Handle focus
270
- focusHandle.setListener(focusTarget, event => {
271
- if (disabled) {
272
- return;
273
- }
274
- // Limit focus events to the direct child of the event component.
275
- // Browser focus is not expected to bubble.
276
- if (!state.isFocused && focusTarget === event.target) {
277
- state.isFocused = true;
278
- state.isFocusVisible = isGlobalFocusVisible;
279
- if (onFocus) {
280
- onFocus(event);
281
- }
282
- if (onFocusChange) {
283
- onFocusChange(true);
284
- }
285
- if (state.isFocusVisible && onFocusVisibleChange) {
286
- onFocusVisibleChange(true);
287
- }
288
- isEmulatingMouseEvents = false;
289
- }
290
- });
291
-
292
- // Handle blur
293
- blurHandle.setListener(focusTarget, event => {
294
- if (disabled) {
295
- return;
296
- }
297
- if (state.isFocused) {
298
- state.isFocused = false;
299
- state.isFocusVisible = isGlobalFocusVisible;
300
- if (onBlur) {
301
- onBlur(event);
302
- }
303
- if (onFocusChange) {
304
- onFocusChange(false);
305
- }
306
- if (state.isFocusVisible && onFocusVisibleChange) {
307
- onFocusVisibleChange(false);
308
- }
309
- }
310
- isEmulatingMouseEvents = false;
311
- });
312
- }
313
- }, [disabled, onBlur, onFocus, onFocusChange, onFocusVisibleChange]);
314
-
315
- // Mount/Unmount logic
316
- useFocusLifecycles(stateRef);
317
-}
318
-
319
-export function useFocusWithin(
320
- focusWithinTargetRef: {current: null | Node},
321
- {
322
- disabled,
323
- onAfterBlurWithin,
324
- onBeforeBlurWithin,
325
- onBlurWithin,
326
- onFocusWithin,
327
- onFocusWithinChange,
328
- onFocusWithinVisibleChange,
329
- }: UseFocusWithinOptions,
330
-) {
331
- // Setup controlled state for this useFocus hook
332
- const stateRef = useRef({isFocused: false, isFocusVisible: false});
333
- const focusHandle = useEvent('focus', passiveObject);
334
- const blurHandle = useEvent('blur', passiveObject);
335
- const afterBlurHandle = useEvent('afterblur', passiveObject);
336
- const beforeBlurHandle = useEvent('beforeblur', passiveObject);
337
- const focusVisibleHandles = useFocusVisibleInputHandles();
338
-
339
- useEffect(() => {
340
- const focusWithinTarget = focusWithinTargetRef.current;
341
- const state = stateRef.current;
342
-
343
- if (focusWithinTarget !== null && state !== null) {
344
- // Handle focus visible
345
- setFocusVisibleListeners(
346
- focusVisibleHandles,
347
- focusWithinTarget,
348
- isFocusVisible => {
349
- if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
350
- state.isFocusVisible = isFocusVisible;
351
- if (onFocusWithinVisibleChange) {
352
- onFocusWithinVisibleChange(isFocusVisible);
353
- }
354
- }
355
- },
356
- );
357
-
358
- // Handle focus
359
- focusHandle.setListener(focusWithinTarget, event => {
360
- if (disabled) {
361
- return;
362
- }
363
- if (!state.isFocused) {
364
- state.isFocused = true;
365
- state.isFocusVisible = isGlobalFocusVisible;
366
- if (onFocusWithinChange) {
367
- onFocusWithinChange(true);
368
- }
369
- if (state.isFocusVisible && onFocusWithinVisibleChange) {
370
- onFocusWithinVisibleChange(true);
371
- }
372
- }
373
- if (!state.isFocusVisible && isGlobalFocusVisible) {
374
- state.isFocusVisible = isGlobalFocusVisible;
375
- if (onFocusWithinVisibleChange) {
376
- onFocusWithinVisibleChange(true);
377
- }
378
- }
379
- if (onFocusWithin) {
380
- onFocusWithin(event);
381
- }
382
- isEmulatingMouseEvents = false;
383
- });
384
-
385
- // Handle blur
386
- blurHandle.setListener(focusWithinTarget, event => {
387
- if (disabled) {
388
- return;
389
- }
390
- const {relatedTarget} = (event: any);
391
-
392
- if (
393
- state.isFocused &&
394
- !isRelatedTargetWithin(focusWithinTarget, relatedTarget)
395
- ) {
396
- state.isFocused = false;
397
- if (onFocusWithinChange) {
398
- onFocusWithinChange(false);
399
- }
400
- if (state.isFocusVisible && onFocusWithinVisibleChange) {
401
- onFocusWithinVisibleChange(false);
402
- }
403
- if (onBlurWithin) {
404
- onBlurWithin(event);
405
- }
406
- }
407
- isEmulatingMouseEvents = false;
408
- });
409
-
410
- // Handle before blur. This is a special
411
- // React provided event.
412
- beforeBlurHandle.setListener(focusWithinTarget, event => {
413
- if (disabled) {
414
- return;
415
- }
416
- if (onBeforeBlurWithin) {
417
- onBeforeBlurWithin(event);
418
- // Add an "afterblur" listener on document. This is a special
419
- // React provided event.
420
- afterBlurHandle.setListener(document, afterBlurEvent => {
421
- if (onAfterBlurWithin) {
422
- onAfterBlurWithin(afterBlurEvent);
423
- }
424
- // Clear listener on document
425
- afterBlurHandle.setListener(document, null);
426
- });
427
- }
428
- });
429
- }
430
- }, [
431
- disabled,
432
- onBlurWithin,
433
- onFocusWithin,
434
- onFocusWithinChange,
435
- onFocusWithinVisibleChange,
436
- ]);
437
-
438
- // Mount/Unmount logic
439
- useFocusLifecycles(stateRef);
440
-}
packages/react-interactions/events/src/dom/use-event/__tests__/useFocus-test.internal.js
deleted
-319
@@ -1,319 +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
- * @emails react-core
8
- */
9
-
10
-'use strict';
11
-
12
-import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';
13
-
14
-let React;
15
-let ReactFeatureFlags;
16
-let ReactDOM;
17
-let useFocus;
18
-
19
-function initializeModules(hasPointerEvents) {
20
- setPointerEvent(hasPointerEvents);
21
- jest.resetModules();
22
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
23
- ReactFeatureFlags.enableModernEventSystem = true;
24
- ReactFeatureFlags.enableUseEventAPI = true;
25
- React = require('react');
26
- ReactDOM = require('react-dom');
27
-
28
- // TODO: This import throws outside of experimental mode. Figure out better
29
- // strategy for gated imports.
30
- if (__EXPERIMENTAL__) {
31
- useFocus = require('react-interactions/events/focus').useFocus;
32
- }
33
-}
34
-
35
-const forcePointerEvents = true;
36
-const table = [[forcePointerEvents], [!forcePointerEvents]];
37
-
38
-describe.each(table)(`useFocus`, hasPointerEvents => {
39
- let container;
40
-
41
- beforeEach(() => {
42
- initializeModules(hasPointerEvents);
43
- container = document.createElement('div');
44
- document.body.appendChild(container);
45
- });
46
-
47
- afterEach(() => {
48
- ReactDOM.render(null, container);
49
- document.body.removeChild(container);
50
- container = null;
51
- });
52
-
53
- describe('disabled', () => {
54
- let onBlur, onFocus, ref;
55
-
56
- const componentInit = () => {
57
- onBlur = jest.fn();
58
- onFocus = jest.fn();
59
- ref = React.createRef();
60
- const Component = () => {
61
- useFocus(ref, {
62
- disabled: true,
63
- onBlur,
64
- onFocus,
65
- });
66
- return <div ref={ref} />;
67
- };
68
- ReactDOM.render(<Component />, container);
69
- };
70
-
71
- // @gate experimental
72
- it('does not call callbacks', () => {
73
- componentInit();
74
- const target = createEventTarget(ref.current);
75
- target.focus();
76
- target.blur();
77
- expect(onFocus).not.toBeCalled();
78
- expect(onBlur).not.toBeCalled();
79
- });
80
- });
81
-
82
- describe('onBlur', () => {
83
- let onBlur, ref;
84
-
85
- const componentInit = () => {
86
- onBlur = jest.fn();
87
- ref = React.createRef();
88
- const Component = () => {
89
- useFocus(ref, {
90
- onBlur,
91
- });
92
- return <div ref={ref} />;
93
- };
94
- ReactDOM.render(<Component />, container);
95
- };
96
-
97
- // @gate experimental
98
- it('is called after "blur" event', () => {
99
- componentInit();
100
- const target = createEventTarget(ref.current);
101
- target.focus();
102
- target.blur();
103
- expect(onBlur).toHaveBeenCalledTimes(1);
104
- });
105
- });
106
-
107
- describe('onFocus', () => {
108
- let onFocus, ref, innerRef;
109
-
110
- const componentInit = () => {
111
- onFocus = jest.fn();
112
- ref = React.createRef();
113
- innerRef = React.createRef();
114
- const Component = () => {
115
- useFocus(ref, {
116
- onFocus,
117
- });
118
- return (
119
- <div ref={ref}>
120
- <a ref={innerRef} />
121
- </div>
122
- );
123
- };
124
- ReactDOM.render(<Component />, container);
125
- };
126
-
127
- // @gate experimental
128
- it('is called after "focus" event', () => {
129
- componentInit();
130
- const target = createEventTarget(ref.current);
131
- target.focus();
132
- expect(onFocus).toHaveBeenCalledTimes(1);
133
- });
134
-
135
- // @gate experimental
136
- it('is not called if descendants of target receive focus', () => {
137
- componentInit();
138
- const target = createEventTarget(innerRef.current);
139
- target.focus();
140
- expect(onFocus).not.toBeCalled();
141
- });
142
- });
143
-
144
- describe('onFocusChange', () => {
145
- let onFocusChange, ref, innerRef;
146
-
147
- const componentInit = () => {
148
- onFocusChange = jest.fn();
149
- ref = React.createRef();
150
- innerRef = React.createRef();
151
- const Component = () => {
152
- useFocus(ref, {
153
- onFocusChange,
154
- });
155
- return (
156
- <div ref={ref}>
157
- <div ref={innerRef} />
158
- </div>
159
- );
160
- };
161
- ReactDOM.render(<Component />, container);
162
- };
163
-
164
- // @gate experimental
165
- it('is called after "blur" and "focus" events', () => {
166
- componentInit();
167
- const target = createEventTarget(ref.current);
168
- target.focus();
169
- expect(onFocusChange).toHaveBeenCalledTimes(1);
170
- expect(onFocusChange).toHaveBeenCalledWith(true);
171
- target.blur();
172
- expect(onFocusChange).toHaveBeenCalledTimes(2);
173
- expect(onFocusChange).toHaveBeenCalledWith(false);
174
- });
175
-
176
- // @gate experimental
177
- it('is not called after "blur" and "focus" events on descendants', () => {
178
- componentInit();
179
- const target = createEventTarget(innerRef.current);
180
- target.focus();
181
- expect(onFocusChange).toHaveBeenCalledTimes(0);
182
- target.blur();
183
- expect(onFocusChange).toHaveBeenCalledTimes(0);
184
- });
185
- });
186
-
187
- describe('onFocusVisibleChange', () => {
188
- let onFocusVisibleChange, ref, innerRef;
189
-
190
- const componentInit = () => {
191
- onFocusVisibleChange = jest.fn();
192
- ref = React.createRef();
193
- innerRef = React.createRef();
194
- const Component = () => {
195
- useFocus(ref, {
196
- onFocusVisibleChange,
197
- });
198
- return (
199
- <div ref={ref}>
200
- <div ref={innerRef} />
201
- </div>
202
- );
203
- };
204
- ReactDOM.render(<Component />, container);
205
- };
206
-
207
- // @gate experimental
208
- it('is called after "focus" and "blur" if keyboard navigation is active', () => {
209
- componentInit();
210
- const target = createEventTarget(ref.current);
211
- const containerTarget = createEventTarget(container);
212
- // use keyboard first
213
- containerTarget.keydown({key: 'Tab'});
214
- target.focus();
215
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
216
- expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
217
- target.blur({relatedTarget: container});
218
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
219
- expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
220
- });
221
-
222
- // @gate experimental
223
- it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
224
- componentInit();
225
- const target = createEventTarget(ref.current);
226
- const containerTarget = createEventTarget(container);
227
- // use keyboard first
228
- containerTarget.keydown({key: 'Tab'});
229
- target.focus();
230
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
231
- expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
232
- // then use pointer on the target, focus should no longer be visible
233
- target.pointerdown();
234
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
235
- expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
236
- // onFocusVisibleChange should not be called again
237
- target.blur({relatedTarget: container});
238
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
239
- });
240
-
241
- // @gate experimental
242
- it('is not called after "focus" and "blur" events without keyboard', () => {
243
- componentInit();
244
- const target = createEventTarget(ref.current);
245
- const containerTarget = createEventTarget(container);
246
- target.pointerdown();
247
- target.pointerup();
248
- containerTarget.pointerdown();
249
- target.blur({relatedTarget: container});
250
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
251
- });
252
-
253
- // @gate experimental
254
- it('is not called after "blur" and "focus" events on descendants', () => {
255
- componentInit();
256
- const innerTarget = createEventTarget(innerRef.current);
257
- const containerTarget = createEventTarget(container);
258
- containerTarget.keydown({key: 'Tab'});
259
- innerTarget.focus();
260
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
261
- innerTarget.blur({relatedTarget: container});
262
- expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
263
- });
264
- });
265
-
266
- describe('nested Focus components', () => {
267
- // @gate experimental
268
- it('propagates events in the correct order', () => {
269
- const events = [];
270
- const innerRef = React.createRef();
271
- const outerRef = React.createRef();
272
- const createEventHandler = msg => () => {
273
- events.push(msg);
274
- };
275
-
276
- const Inner = () => {
277
- useFocus(innerRef, {
278
- onBlur: createEventHandler('inner: onBlur'),
279
- onFocus: createEventHandler('inner: onFocus'),
280
- onFocusChange: createEventHandler('inner: onFocusChange'),
281
- });
282
- return <div ref={innerRef} />;
283
- };
284
-
285
- const Outer = () => {
286
- useFocus(outerRef, {
287
- onBlur: createEventHandler('outer: onBlur'),
288
- onFocus: createEventHandler('outer: onFocus'),
289
- onFocusChange: createEventHandler('outer: onFocusChange'),
290
- });
291
- return (
292
- <div ref={outerRef}>
293
- <Inner />
294
- </div>
295
- );
296
- };
297
-
298
- ReactDOM.render(<Outer />, container);
299
-
300
- const innerTarget = createEventTarget(innerRef.current);
301
- const outerTarget = createEventTarget(outerRef.current);
302
-
303
- outerTarget.focus();
304
- outerTarget.blur();
305
- innerTarget.focus();
306
- innerTarget.blur();
307
- expect(events).toEqual([
308
- 'outer: onFocus',
309
- 'outer: onFocusChange',
310
- 'outer: onBlur',
311
- 'outer: onFocusChange',
312
- 'inner: onFocus',
313
- 'inner: onFocusChange',
314
- 'inner: onBlur',
315
- 'inner: onFocusChange',
316
- ]);
317
- });
318
- });
319
-});
packages/react-interactions/events/src/dom/use-event/__tests__/useFocusWithin-test.internal.js
deleted
-569
@@ -1,569 +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
- * @emails react-core
8
- */
9
-
10
-'use strict';
11
-
12
-import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';
13
-
14
-let React;
15
-let ReactFeatureFlags;
16
-let ReactDOM;
17
-let useFocusWithin;
18
-let ReactTestRenderer;
19
-let act;
20
-
21
-function initializeModules(hasPointerEvents) {
22
- setPointerEvent(hasPointerEvents);
23
- jest.resetModules();
24
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
25
- ReactFeatureFlags.enableScopeAPI = true;
26
- ReactFeatureFlags.enableModernEventSystem = true;
27
- ReactFeatureFlags.enableUseEventAPI = true;
28
- React = require('react');
29
- ReactDOM = require('react-dom');
30
- ReactTestRenderer = require('react-test-renderer');
31
- act = ReactTestRenderer.act;
32
-
33
- // TODO: This import throws outside of experimental mode. Figure out better
34
- // strategy for gated imports.
35
- if (__EXPERIMENTAL__) {
36
- useFocusWithin = require('react-interactions/events/focus').useFocusWithin;
37
- }
38
-}
39
-
40
-const forcePointerEvents = true;
41
-const table = [[forcePointerEvents], [!forcePointerEvents]];
42
-
43
-describe.each(table)(`useFocus`, hasPointerEvents => {
44
- let container;
45
- let container2;
46
-
47
- beforeEach(() => {
48
- initializeModules(hasPointerEvents);
49
- container = document.createElement('div');
50
- document.body.appendChild(container);
51
- container2 = document.createElement('div');
52
- document.body.appendChild(container2);
53
- });
54
-
55
- afterEach(() => {
56
- ReactDOM.render(null, container);
57
- document.body.removeChild(container);
58
- document.body.removeChild(container2);
59
- container = null;
60
- container2 = null;
61
- });
62
-
63
- describe('disabled', () => {
64
- let onFocusWithinChange, onFocusWithinVisibleChange, ref;
65
-
66
- const componentInit = () => {
67
- onFocusWithinChange = jest.fn();
68
- onFocusWithinVisibleChange = jest.fn();
69
- ref = React.createRef();
70
- const Component = () => {
71
- useFocusWithin(ref, {
72
- disabled: true,
73
- onFocusWithinChange,
74
- onFocusWithinVisibleChange,
75
- });
76
- return <div ref={ref} />;
77
- };
78
- ReactDOM.render(<Component />, container);
79
- };
80
-
81
- // @gate experimental
82
- it('prevents custom events being dispatched', () => {
83
- componentInit();
84
- const target = createEventTarget(ref.current);
85
- target.focus();
86
- target.blur();
87
- expect(onFocusWithinChange).not.toBeCalled();
88
- expect(onFocusWithinVisibleChange).not.toBeCalled();
89
- });
90
- });
91
-
92
- describe('onFocusWithinChange', () => {
93
- let onFocusWithinChange, ref, innerRef, innerRef2;
94
-
95
- const Component = ({show}) => {
96
- useFocusWithin(ref, {
97
- onFocusWithinChange,
98
- });
99
- return (
100
- <div ref={ref}>
101
- {show && <input ref={innerRef} />}
102
- <div ref={innerRef2} />
103
- </div>
104
- );
105
- };
106
-
107
- const componentInit = () => {
108
- onFocusWithinChange = jest.fn();
109
- ref = React.createRef();
110
- innerRef = React.createRef();
111
- innerRef2 = React.createRef();
112
- ReactDOM.render(<Component show={true} />, container);
113
- };
114
-
115
- // @gate experimental
116
- it('is called after "blur" and "focus" events on focus target', () => {
117
- componentInit();
118
- const target = createEventTarget(ref.current);
119
- target.focus();
120
- expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
121
- expect(onFocusWithinChange).toHaveBeenCalledWith(true);
122
- target.blur({relatedTarget: container});
123
- expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
124
- expect(onFocusWithinChange).toHaveBeenCalledWith(false);
125
- });
126
-
127
- // @gate experimental
128
- it('is called after "blur" and "focus" events on descendants', () => {
129
- componentInit();
130
- const target = createEventTarget(innerRef.current);
131
- target.focus();
132
- expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
133
- expect(onFocusWithinChange).toHaveBeenCalledWith(true);
134
- target.blur({relatedTarget: container});
135
- expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
136
- expect(onFocusWithinChange).toHaveBeenCalledWith(false);
137
- });
138
-
139
- // @gate experimental
140
- it('is only called once when focus moves within and outside the subtree', () => {
141
- componentInit();
142
- const node = ref.current;
143
- const innerNode1 = innerRef.current;
144
- const innerNode2 = innerRef.current;
145
- const target = createEventTarget(node);
146
- const innerTarget1 = createEventTarget(innerNode1);
147
- const innerTarget2 = createEventTarget(innerNode2);
148
-
149
- // focus shifts into subtree
150
- innerTarget1.focus();
151
- expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
152
- expect(onFocusWithinChange).toHaveBeenCalledWith(true);
153
- // focus moves around subtree
154
- innerTarget1.blur({relatedTarget: innerNode2});
155
- innerTarget2.focus();
156
- innerTarget2.blur({relatedTarget: node});
157
- target.focus();
158
- target.blur({relatedTarget: innerNode1});
159
- expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
160
- // focus shifts outside subtree
161
- innerTarget1.blur({relatedTarget: container});
162
- expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
163
- expect(onFocusWithinChange).toHaveBeenCalledWith(false);
164
- });
165
- });
166
-
167
- describe('onFocusWithinVisibleChange', () => {
168
- let onFocusWithinVisibleChange, ref, innerRef, innerRef2;
169
-
170
- const Component = ({show}) => {
171
- useFocusWithin(ref, {
172
- onFocusWithinVisibleChange,
173
- });
174
- return (
175
- <div ref={ref}>
176
- {show && <input ref={innerRef} />}
177
- <div ref={innerRef2} />
178
- </div>
179
- );
180
- };
181
-
182
- const componentInit = () => {
183
- onFocusWithinVisibleChange = jest.fn();
184
- ref = React.createRef();
185
- innerRef = React.createRef();
186
- innerRef2 = React.createRef();
187
- ReactDOM.render(<Component show={true} />, container);
188
- };
189
-
190
- // @gate experimental
191
- it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
192
- componentInit();
193
- const target = createEventTarget(ref.current);
194
- const containerTarget = createEventTarget(container);
195
- // use keyboard first
196
- containerTarget.keydown({key: 'Tab'});
197
- target.focus();
198
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
199
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
200
- target.blur({relatedTarget: container});
201
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
202
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
203
- });
204
-
205
- // @gate experimental
206
- it('is called after "focus" and "blur" on descendants if keyboard was used', () => {
207
- componentInit();
208
- const innerTarget = createEventTarget(innerRef.current);
209
- const containerTarget = createEventTarget(container);
210
- // use keyboard first
211
- containerTarget.keydown({key: 'Tab'});
212
- innerTarget.focus();
213
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
214
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
215
- innerTarget.blur({relatedTarget: container});
216
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
217
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
218
- });
219
-
220
- // @gate experimental
221
- it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
222
- componentInit();
223
- const node = ref.current;
224
- const innerNode1 = innerRef.current;
225
- const innerNode2 = innerRef2.current;
226
-
227
- const target = createEventTarget(node);
228
- const innerTarget1 = createEventTarget(innerNode1);
229
- const innerTarget2 = createEventTarget(innerNode2);
230
- // use keyboard first
231
- target.focus();
232
- target.keydown({key: 'Tab'});
233
- target.blur({relatedTarget: innerNode1});
234
- innerTarget1.focus();
235
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
236
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
237
- // then use pointer on the next target, focus should no longer be visible
238
- innerTarget2.pointerdown();
239
- innerTarget1.blur({relatedTarget: innerNode2});
240
- innerTarget2.focus();
241
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
242
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
243
- // then use keyboard again
244
- innerTarget2.keydown({key: 'Tab', shiftKey: true});
245
- innerTarget2.blur({relatedTarget: innerNode1});
246
- innerTarget1.focus();
247
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3);
248
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
249
- // then use pointer on the target, focus should no longer be visible
250
- innerTarget1.pointerdown();
251
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
252
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
253
- // onFocusVisibleChange should not be called again
254
- innerTarget1.blur({relatedTarget: container});
255
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
256
- });
257
-
258
- // @gate experimental
259
- it('is not called after "focus" and "blur" events without keyboard', () => {
260
- componentInit();
261
- const innerTarget = createEventTarget(innerRef.current);
262
- innerTarget.pointerdown();
263
- innerTarget.pointerup();
264
- innerTarget.blur({relatedTarget: container});
265
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0);
266
- });
267
-
268
- // @gate experimental
269
- it('is only called once when focus moves within and outside the subtree', () => {
270
- componentInit();
271
- const node = ref.current;
272
- const innerNode1 = innerRef.current;
273
- const innerNode2 = innerRef2.current;
274
- const target = createEventTarget(node);
275
- const innerTarget1 = createEventTarget(innerNode1);
276
- const innerTarget2 = createEventTarget(innerNode2);
277
-
278
- // focus shifts into subtree
279
- innerTarget1.focus();
280
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
281
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
282
- // focus moves around subtree
283
- innerTarget1.blur({relatedTarget: innerNode2});
284
- innerTarget2.focus();
285
- innerTarget2.blur({relatedTarget: node});
286
- target.focus();
287
- target.blur({relatedTarget: innerNode1});
288
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
289
- // focus shifts outside subtree
290
- innerTarget1.blur({relatedTarget: container});
291
- expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
292
- expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
293
- });
294
- });
295
-
296
- describe('onBeforeBlurWithin', () => {
297
- let onBeforeBlurWithin, onAfterBlurWithin, ref, innerRef, innerRef2;
298
-
299
- beforeEach(() => {
300
- onBeforeBlurWithin = jest.fn();
301
- onAfterBlurWithin = jest.fn(e => {
302
- e.persist();
303
- });
304
- ref = React.createRef();
305
- innerRef = React.createRef();
306
- innerRef2 = React.createRef();
307
- });
308
-
309
- // @gate experimental
310
- it('is called after a focused element is unmounted', () => {
311
- const Component = ({show}) => {
312
- useFocusWithin(ref, {
313
- onBeforeBlurWithin,
314
- onAfterBlurWithin,
315
- });
316
- return (
317
- <div ref={ref}>
318
- {show && <input ref={innerRef} />}
319
- <div ref={innerRef2} />
320
- </div>
321
- );
322
- };
323
-
324
- ReactDOM.render(<Component show={true} />, container);
325
-
326
- const inner = innerRef.current;
327
- const target = createEventTarget(inner);
328
- target.keydown({key: 'Tab'});
329
- target.focus();
330
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
331
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
332
- ReactDOM.render(<Component show={false} />, container);
333
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
334
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
335
- expect(onAfterBlurWithin).toHaveBeenCalledWith(
336
- expect.objectContaining({relatedTarget: inner}),
337
- );
338
- });
339
-
340
- // @gate experimental
341
- it('is called after a nested focused element is unmounted', () => {
342
- const Component = ({show}) => {
343
- useFocusWithin(ref, {
344
- onBeforeBlurWithin,
345
- onAfterBlurWithin,
346
- });
347
- return (
348
- <div ref={ref}>
349
- {show && (
350
- <div>
351
- <input ref={innerRef} />
352
- </div>
353
- )}
354
- <div ref={innerRef2} />
355
- </div>
356
- );
357
- };
358
-
359
- ReactDOM.render(<Component show={true} />, container);
360
-
361
- const inner = innerRef.current;
362
- const target = createEventTarget(inner);
363
- target.keydown({key: 'Tab'});
364
- target.focus();
365
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
366
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
367
- ReactDOM.render(<Component show={false} />, container);
368
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
369
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
370
- expect(onAfterBlurWithin).toHaveBeenCalledWith(
371
- expect.objectContaining({relatedTarget: inner}),
372
- );
373
- });
374
-
375
- // @gate experimental
376
- it('is called after many elements are unmounted', () => {
377
- const buttonRef = React.createRef();
378
- const inputRef = React.createRef();
379
-
380
- const Component = ({show}) => {
381
- useFocusWithin(ref, {
382
- onBeforeBlurWithin,
383
- onAfterBlurWithin,
384
- });
385
- return (
386
- <div ref={ref}>
387
- {show && <button>Press me!</button>}
388
- {show && <button>Press me!</button>}
389
- {show && <input ref={inputRef} />}
390
- {show && <button>Press me!</button>}
391
- {!show && <button ref={buttonRef}>Press me!</button>}
392
- {show && <button>Press me!</button>}
393
- <button>Press me!</button>
394
- <button>Press me!</button>
395
- </div>
396
- );
397
- };
398
-
399
- ReactDOM.render(<Component show={true} />, container);
400
-
401
- inputRef.current.focus();
402
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
403
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
404
- ReactDOM.render(<Component show={false} />, container);
405
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
406
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
407
- });
408
-
409
- // @gate experimental
410
- it('is called after a nested focused element is unmounted (with scope query)', () => {
411
- const TestScope = React.unstable_createScope();
412
- const testScopeQuery = (type, props) => true;
413
- let targetNodes;
414
- let targetNode;
415
-
416
- const Component = ({show}) => {
417
- const scopeRef = React.useRef(null);
418
- useFocusWithin(scopeRef, {
419
- onBeforeBlurWithin(event) {
420
- const scope = scopeRef.current;
421
- targetNode = innerRef.current;
422
- targetNodes = scope.DO_NOT_USE_queryAllNodes(testScopeQuery);
423
- },
424
- });
425
-
426
- return (
427
- <TestScope ref={scopeRef}>
428
- {show && <input ref={innerRef} />}
429
- </TestScope>
430
- );
431
- };
432
-
433
- ReactDOM.render(<Component show={true} />, container);
434
-
435
- const inner = innerRef.current;
436
- const target = createEventTarget(inner);
437
- target.keydown({key: 'Tab'});
438
- target.focus();
439
- ReactDOM.render(<Component show={false} />, container);
440
- expect(targetNodes).toEqual([targetNode]);
441
- });
442
-
443
- // @gate experimental
444
- it('is called after a focused suspended element is hidden', () => {
445
- const Suspense = React.Suspense;
446
- let suspend = false;
447
- let resolve;
448
- const promise = new Promise(resolvePromise => (resolve = resolvePromise));
449
-
450
- function Child() {
451
- if (suspend) {
452
- throw promise;
453
- } else {
454
- return <input ref={innerRef} />;
455
- }
456
- }
457
-
458
- const Component = ({show}) => {
459
- useFocusWithin(ref, {
460
- onBeforeBlurWithin,
461
- onAfterBlurWithin,
462
- });
463
-
464
- return (
465
- <div ref={ref}>
466
- <Suspense fallback="Loading...">
467
- <Child />
468
- </Suspense>
469
- </div>
470
- );
471
- };
472
-
473
- const root = ReactDOM.createRoot(container2);
474
-
475
- act(() => {
476
- root.render(<Component />);
477
- });
478
- jest.runAllTimers();
479
- expect(container2.innerHTML).toBe('<div><input></div>');
480
-
481
- const inner = innerRef.current;
482
- const target = createEventTarget(inner);
483
- target.keydown({key: 'Tab'});
484
- target.focus();
485
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
486
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
487
-
488
- suspend = true;
489
- act(() => {
490
- root.render(<Component />);
491
- });
492
- jest.runAllTimers();
493
- expect(container2.innerHTML).toBe(
494
- '<div><input style="display: none;">Loading...</div>',
495
- );
496
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
497
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
498
- resolve();
499
- });
500
-
501
- // @gate experimental
502
- it('is called after a focused suspended element is hidden then shown', () => {
503
- const Suspense = React.Suspense;
504
- let suspend = false;
505
- let resolve;
506
- const promise = new Promise(resolvePromise => (resolve = resolvePromise));
507
- const buttonRef = React.createRef();
508
-
509
- function Child() {
510
- if (suspend) {
511
- throw promise;
512
- } else {
513
- return <input ref={innerRef} />;
514
- }
515
- }
516
-
517
- const Component = ({show}) => {
518
- useFocusWithin(ref, {
519
- onBeforeBlurWithin,
520
- onAfterBlurWithin,
521
- });
522
-
523
- return (
524
- <div ref={ref}>
525
- <Suspense fallback={<button ref={buttonRef}>Loading...</button>}>
526
- <Child />
527
- </Suspense>
528
- </div>
529
- );
530
- };
531
-
532
- const root = ReactDOM.createRoot(container2);
533
-
534
- act(() => {
535
- root.render(<Component />);
536
- });
537
- jest.runAllTimers();
538
-
539
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
540
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
541
-
542
- suspend = true;
543
- act(() => {
544
- root.render(<Component />);
545
- });
546
- jest.runAllTimers();
547
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
548
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
549
-
550
- act(() => {
551
- root.render(<Component />);
552
- });
553
- jest.runAllTimers();
554
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
555
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
556
-
557
- buttonRef.current.focus();
558
- suspend = false;
559
- act(() => {
560
- root.render(<Component />);
561
- });
562
- jest.runAllTimers();
563
- expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
564
- expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
565
-
566
- resolve();
567
- });
568
- });
569
-});
packages/react-native-renderer/src/ReactFabricHostConfig.js
-19
@@ -77,9 +77,6 @@ export type UpdatePayload = Object;
77
export type TimeoutHandle = TimeoutID;
78
export type NoTimeout = -1;
79
80
-export type ReactListenerEvent = Object;
81
-export type ReactListenerMap = Object;
82
-export type ReactListener = Object;
80
export type OpaqueIDType = void;
81
82
export type RendererInspectionConfig = $ReadOnly<{|
@@ -510,22 +507,6 @@ export function makeServerId(): OpaqueIDType {
507
throw new Error('Not yet implemented');
508
}
509
513
-export function registerEvent(event: any, rootContainerInstance: Container) {
514
- throw new Error('Not yet implemented.');
515
-}
516
-
517
-export function mountEventListener(listener: any) {
518
- throw new Error('Not yet implemented.');
519
-}
520
-
521
-export function unmountEventListener(listener: any) {
522
- throw new Error('Not yet implemented.');
523
-}
524
-
525
-export function validateEventListenerTarget(target: any, listener: any) {
526
- throw new Error('Not yet implemented.');
527
-}
528
-
510
export function beforeActiveInstanceBlur() {
511
// noop
512
}
packages/react-native-renderer/src/ReactNativeHostConfig.js
-20
@@ -28,10 +28,6 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
28
29
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
30
31
-export type ReactListenerEvent = Object;
32
-export type ReactListenerMap = Object;
33
-export type ReactListener = Object;
34
-
31
export type Type = string;
32
export type Props = Object;
33
export type Container = number;
@@ -559,22 +555,6 @@ export function makeServerId(): OpaqueIDType {
555
throw new Error('Not yet implemented');
556
}
557
562
-export function registerEvent(event: any, rootContainerInstance: Container) {
563
- throw new Error('Not yet implemented.');
564
-}
565
-
566
-export function mountEventListener(listener: any) {
567
- throw new Error('Not yet implemented.');
568
-}
569
-
570
-export function unmountEventListener(listener: any) {
571
- throw new Error('Not yet implemented.');
572
-}
573
-
574
-export function validateEventListenerTarget(target: any, listener: any) {
575
- throw new Error('Not yet implemented.');
576
-}
577
-
558
export function beforeActiveInstanceBlur() {
559
// noop
560
}
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+1
-4
@@ -36,7 +36,6 @@ import {
36
enableSuspenseCallback,
37
enableScopeAPI,
38
runAllPassiveEffectDestroysBeforeCreates,
39
- enableUseEventAPI,
39
} from 'shared/ReactFeatureFlags';
40
import {
41
FunctionComponent,
@@ -1020,9 +1019,7 @@ function commitUnmount(
1019
if (enableDeprecatedFlareAPI) {
1020
unmountDeprecatedResponderListeners(current);
1021
}
1023
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
1024
- beforeRemoveInstance(current.stateNode);
1025
- }
1022
+ beforeRemoveInstance(current.stateNode);
1023
safelyDetachRef(current);
1024
return;
1025
}
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+1
-4
@@ -36,7 +36,6 @@ import {
36
enableSuspenseCallback,
37
enableScopeAPI,
38
runAllPassiveEffectDestroysBeforeCreates,
39
- enableUseEventAPI,
39
} from 'shared/ReactFeatureFlags';
40
import {
41
FunctionComponent,
@@ -1020,9 +1019,7 @@ function commitUnmount(
1019
if (enableDeprecatedFlareAPI) {
1020
unmountDeprecatedResponderListeners(current);
1021
}
1023
- if (enableDeprecatedFlareAPI || enableUseEventAPI) {
1024
- beforeRemoveInstance(current.stateNode);
1025
- }
1022
+ beforeRemoveInstance(current.stateNode);
1023
safelyDetachRef(current);
1024
return;
1025
}
packages/react-reconciler/src/ReactFiberHooks.new.js
+2
-211
@@ -14,7 +14,6 @@ import type {
14
ReactEventResponder,
15
ReactContext,
16
ReactEventResponderListener,
17
- ReactScopeMethods,
17
} from 'shared/ReactTypes';
18
import type {Fiber, Dispatcher} from './ReactInternalTypes';
19
import type {ExpirationTime} from './ReactFiberExpirationTime.new';
@@ -22,15 +21,10 @@ import type {HookEffectTag} from './ReactHookEffectTags';
21
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
22
import type {ReactPriorityLevel} from './ReactInternalTypes';
23
import type {FiberRoot} from './ReactInternalTypes';
25
-import type {
26
- OpaqueIDType,
27
- ReactListenerEvent,
28
- ReactListenerMap,
29
- ReactListener,
30
-} from './ReactFiberHostConfig';
24
+import type {OpaqueIDType} from './ReactFiberHostConfig';
25
26
import ReactSharedInternals from 'shared/ReactSharedInternals';
33
-import {enableDebugTracing, enableUseEventAPI} from 'shared/ReactFeatureFlags';
27
+import {enableDebugTracing} from 'shared/ReactFeatureFlags';
28
29
import {markRootExpiredAtTime} from './ReactFiberRoot.new';
30
import {
@@ -46,7 +40,6 @@ import {
40
Passive as PassiveEffect,
41
} from './ReactSideEffectTags';
42
import {
49
- NoEffect as NoHookEffect,
43
HasEffect as HookHasEffect,
44
Layout as HookLayout,
45
Passive as HookPassive,
@@ -63,12 +56,6 @@ import {
56
markUnprocessedUpdateTime,
57
priorityLevelToLabel,
58
} from './ReactFiberWorkLoop.new';
66
-import {
67
- registerEvent,
68
- mountEventListener as mountHostEventListener,
69
- unmountEventListener as unmountHostEventListener,
70
- validateEventListenerTarget,
71
-} from './ReactFiberHostConfig';
59
60
import invariant from 'shared/invariant';
61
import getComponentName from 'shared/getComponentName';
@@ -95,7 +82,6 @@ import {
82
setWorkInProgressVersion,
83
warnAboutMultipleRenderersDEV,
84
} from './ReactMutableSource.new';
98
-import {getRootHostContainer} from './ReactFiberHostContext.new';
85
import {getIsRendering} from './ReactCurrentFiber';
86
import {logStateUpdateScheduled} from './DebugTracing';
87
@@ -136,7 +122,6 @@ export type HookType =
122
| 'useDeferredValue'
123
| 'useTransition'
124
| 'useMutableSource'
139
- | 'useEvent'
125
| 'useOpaqueIdentifier';
126
127
let didWarnAboutMismatchedHooksForComponent;
@@ -1760,158 +1745,6 @@ function dispatchAction<S, A>(
1745
}
1746
}
1747
1763
-const noOpMount = () => {};
1764
-
1765
-function validateNotInFunctionRender(): boolean {
1766
- if (currentlyRenderingFiber === null) {
1767
- return true;
1768
- }
1769
- if (__DEV__) {
1770
- console.warn(
1771
- 'Event listener methods from useEvent() cannot be called during render.' +
1772
- ' These methods should be called in an effect or event callback outside the render.',
1773
- );
1774
- }
1775
- return false;
1776
-}
1777
-
1778
-function createReactListener(
1779
- event: ReactListenerEvent,
1780
- callback: (SyntheticEvent<EventTarget>) => void,
1781
- target: EventTarget | ReactScopeMethods,
1782
- destroy: Node => void,
1783
-): ReactListener {
1784
- return {
1785
- callback,
1786
- destroy,
1787
- event,
1788
- target,
1789
- };
1790
-}
1791
-
1792
-function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
1793
- if (enableUseEventAPI) {
1794
- const hook = mountWorkInProgressHook();
1795
- const listenerMap: Map<
1796
- EventTarget | ReactScopeMethods,
1797
- ReactListener,
1798
- > = new Map();
1799
- const rootContainerInstance = getRootHostContainer();
1800
-
1801
- // Register the event to the current root to ensure event
1802
- // replaying can pick up the event ahead of time.
1803
- registerEvent(event, rootContainerInstance);
1804
-
1805
- const clear = () => {
1806
- if (validateNotInFunctionRender()) {
1807
- const listeners = Array.from(listenerMap.values());
1808
- for (let i = 0; i < listeners.length; i++) {
1809
- unmountHostEventListener(listeners[i]);
1810
- }
1811
- listenerMap.clear();
1812
- }
1813
- };
1814
-
1815
- const destroy = (target: Node) => {
1816
- // We don't need to call detachListenerFromInstance
1817
- // here as this method should only ever be called
1818
- // from renderers that need to remove the instance
1819
- // from the map representing an instance that still
1820
- // holds a reference to the listenerMap. This means
1821
- // things like "window" listeners on ReactDOM should
1822
- // never enter this call path as the the instance in
1823
- // those cases would be that of "window", which
1824
- // should be handled via an optimized route in the
1825
- // renderer, making less overhead here. If we change
1826
- // this heuristic we should update this path to make
1827
- // sure we call detachListenerFromInstance.
1828
- listenerMap.delete(target);
1829
- };
1830
-
1831
- const reactListenerMap: ReactListenerMap = {
1832
- clear,
1833
- setListener(
1834
- target: EventTarget | ReactScopeMethods,
1835
- callback: ?(SyntheticEvent<EventTarget>) => void,
1836
- ): void {
1837
- if (
1838
- validateNotInFunctionRender() &&
1839
- validateEventListenerTarget(target, callback)
1840
- ) {
1841
- let listener = listenerMap.get(target);
1842
- if (listener === undefined) {
1843
- if (callback == null) {
1844
- return;
1845
- }
1846
- listener = createReactListener(event, callback, target, destroy);
1847
- listenerMap.set(target, listener);
1848
- } else {
1849
- if (callback == null) {
1850
- listenerMap.delete(target);
1851
- unmountHostEventListener(listener);
1852
- return;
1853
- }
1854
- listener.callback = callback;
1855
- }
1856
- mountHostEventListener(listener);
1857
- }
1858
- },
1859
- };
1860
- // In order to clear up upon the hook unmounting,
1861
- // we ensure we set the effecrt tag so that we visit
1862
- // this effect in the commit phase, so we can handle
1863
- // clean-up accordingly.
1864
- currentlyRenderingFiber.effectTag |= UpdateEffect;
1865
- pushEffect(NoHookEffect, noOpMount, clear, null);
1866
- hook.memoizedState = [reactListenerMap, event, clear];
1867
- return reactListenerMap;
1868
- }
1869
- // To make Flow not complain
1870
- return (undefined: any);
1871
-}
1872
-
1873
-function updateEventListener(event: ReactListenerEvent): ReactListenerMap {
1874
- if (enableUseEventAPI) {
1875
- const hook = updateWorkInProgressHook();
1876
- const [reactListenerMap, memoizedEvent, clear] = hook.memoizedState;
1877
- if (__DEV__) {
1878
- if (memoizedEvent.type !== event.type) {
1879
- console.warn(
1880
- 'The event type argument passed to the useEvent() hook was different between renders.' +
1881
- ' The event type is static and should never change between renders.',
1882
- );
1883
- }
1884
- if (memoizedEvent.capture !== event.capture) {
1885
- console.warn(
1886
- 'The "capture" option passed to the useEvent() hook was different between renders.' +
1887
- ' The "capture" option is static and should never change between renders.',
1888
- );
1889
- }
1890
- if (memoizedEvent.priority !== event.priority) {
1891
- console.warn(
1892
- 'The "priority" option passed to the useEvent() hook was different between renders.' +
1893
- ' The "priority" option is static and should never change between renders.',
1894
- );
1895
- }
1896
- if (memoizedEvent.passive !== event.passive) {
1897
- console.warn(
1898
- 'The "passive" option passed to the useEvent() hook was different between renders.' +
1899
- ' The "passive" option is static and should never change between renders.',
1900
- );
1901
- }
1902
- }
1903
- // In order to clear up upon the hook unmounting,
1904
- // we ensure we set the effecrt tag so that we visit
1905
- // this effect in the commit phase, so we can handle
1906
- // clean-up accordingly.
1907
- currentlyRenderingFiber.effectTag |= UpdateEffect;
1908
- pushEffect(NoHookEffect, noOpMount, clear, null);
1909
- return reactListenerMap;
1910
- }
1911
- // To make Flow not complain
1912
- return (undefined: any);
1913
-}
1914
-
1748
export const ContextOnlyDispatcher: Dispatcher = {
1749
readContext,
1750
@@ -1929,7 +1762,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
1762
useDeferredValue: throwInvalidHookError,
1763
useTransition: throwInvalidHookError,
1764
useMutableSource: throwInvalidHookError,
1932
- useEvent: throwInvalidHookError,
1765
useOpaqueIdentifier: throwInvalidHookError,
1766
};
1767
@@ -1950,7 +1782,6 @@ const HooksDispatcherOnMount: Dispatcher = {
1782
useDeferredValue: mountDeferredValue,
1783
useTransition: mountTransition,
1784
useMutableSource: mountMutableSource,
1953
- useEvent: mountEventListener,
1785
useOpaqueIdentifier: mountOpaqueIdentifier,
1786
};
1787
@@ -1971,7 +1802,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
1802
useDeferredValue: updateDeferredValue,
1803
useTransition: updateTransition,
1804
useMutableSource: updateMutableSource,
1974
- useEvent: updateEventListener,
1805
useOpaqueIdentifier: updateOpaqueIdentifier,
1806
};
1807
@@ -1992,7 +1822,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
1822
useDeferredValue: rerenderDeferredValue,
1823
useTransition: rerenderTransition,
1824
useMutableSource: updateMutableSource,
1995
- useEvent: updateEventListener,
1825
useOpaqueIdentifier: rerenderOpaqueIdentifier,
1826
};
1827
@@ -2151,11 +1980,6 @@ if (__DEV__) {
1980
mountHookTypesDev();
1981
return mountMutableSource(source, getSnapshot, subscribe);
1982
},
2154
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2155
- currentHookNameInDev = 'useEvent';
2156
- mountHookTypesDev();
2157
- return mountEventListener(event);
2158
- },
1983
useOpaqueIdentifier(): OpaqueIDType | void {
1984
currentHookNameInDev = 'useOpaqueIdentifier';
1985
mountHookTypesDev();
@@ -2286,11 +2110,6 @@ if (__DEV__) {
2110
updateHookTypesDev();
2111
return mountMutableSource(source, getSnapshot, subscribe);
2112
},
2289
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2290
- currentHookNameInDev = 'useEvent';
2291
- updateHookTypesDev();
2292
- return mountEventListener(event);
2293
- },
2113
useOpaqueIdentifier(): OpaqueIDType | void {
2114
currentHookNameInDev = 'useOpaqueIdentifier';
2115
updateHookTypesDev();
@@ -2421,11 +2240,6 @@ if (__DEV__) {
2240
updateHookTypesDev();
2241
return updateMutableSource(source, getSnapshot, subscribe);
2242
},
2424
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2425
- currentHookNameInDev = 'useEvent';
2426
- updateHookTypesDev();
2427
- return updateEventListener(event);
2428
- },
2243
useOpaqueIdentifier(): OpaqueIDType | void {
2244
currentHookNameInDev = 'useOpaqueIdentifier';
2245
updateHookTypesDev();
@@ -2557,11 +2371,6 @@ if (__DEV__) {
2371
updateHookTypesDev();
2372
return updateMutableSource(source, getSnapshot, subscribe);
2373
},
2560
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2561
- currentHookNameInDev = 'useEvent';
2562
- updateHookTypesDev();
2563
- return updateEventListener(event);
2564
- },
2374
useOpaqueIdentifier(): OpaqueIDType | void {
2375
currentHookNameInDev = 'useOpaqueIdentifier';
2376
updateHookTypesDev();
@@ -2707,12 +2516,6 @@ if (__DEV__) {
2516
mountHookTypesDev();
2517
return mountMutableSource(source, getSnapshot, subscribe);
2518
},
2710
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2711
- currentHookNameInDev = 'useEvent';
2712
- warnInvalidHookAccess();
2713
- mountHookTypesDev();
2714
- return mountEventListener(event);
2715
- },
2519
useOpaqueIdentifier(): OpaqueIDType | void {
2520
currentHookNameInDev = 'useOpaqueIdentifier';
2521
warnInvalidHookAccess();
@@ -2859,12 +2662,6 @@ if (__DEV__) {
2662
updateHookTypesDev();
2663
return updateMutableSource(source, getSnapshot, subscribe);
2664
},
2862
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2863
- currentHookNameInDev = 'useEvent';
2864
- warnInvalidHookAccess();
2865
- updateHookTypesDev();
2866
- return updateEventListener(event);
2867
- },
2665
useOpaqueIdentifier(): OpaqueIDType | void {
2666
currentHookNameInDev = 'useOpaqueIdentifier';
2667
warnInvalidHookAccess();
@@ -3012,12 +2809,6 @@ if (__DEV__) {
2809
updateHookTypesDev();
2810
return updateMutableSource(source, getSnapshot, subscribe);
2811
},
3015
- useEvent(event: ReactListenerEvent): ReactListenerMap {
3016
- currentHookNameInDev = 'useEvent';
3017
- warnInvalidHookAccess();
3018
- updateHookTypesDev();
3019
- return updateEventListener(event);
3020
- },
2812
useOpaqueIdentifier(): OpaqueIDType | void {
2813
currentHookNameInDev = 'useOpaqueIdentifier';
2814
warnInvalidHookAccess();
packages/react-reconciler/src/ReactFiberHooks.old.js
+2
-211
@@ -14,7 +14,6 @@ import type {
14
ReactEventResponder,
15
ReactContext,
16
ReactEventResponderListener,
17
- ReactScopeMethods,
17
} from 'shared/ReactTypes';
18
import type {Fiber, Dispatcher} from './ReactInternalTypes';
19
import type {ExpirationTime} from './ReactFiberExpirationTime.old';
@@ -22,15 +21,10 @@ import type {HookEffectTag} from './ReactHookEffectTags';
21
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
22
import type {ReactPriorityLevel} from './ReactInternalTypes';
23
import type {FiberRoot} from './ReactInternalTypes';
25
-import type {
26
- OpaqueIDType,
27
- ReactListenerEvent,
28
- ReactListenerMap,
29
- ReactListener,
30
-} from './ReactFiberHostConfig';
24
+import type {OpaqueIDType} from './ReactFiberHostConfig';
25
26
import ReactSharedInternals from 'shared/ReactSharedInternals';
33
-import {enableDebugTracing, enableUseEventAPI} from 'shared/ReactFeatureFlags';
27
+import {enableDebugTracing} from 'shared/ReactFeatureFlags';
28
29
import {markRootExpiredAtTime} from './ReactFiberRoot.old';
30
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
@@ -46,7 +40,6 @@ import {
40
Passive as PassiveEffect,
41
} from './ReactSideEffectTags';
42
import {
49
- NoEffect as NoHookEffect,
43
HasEffect as HookHasEffect,
44
Layout as HookLayout,
45
Passive as HookPassive,
@@ -63,12 +56,6 @@ import {
56
markUnprocessedUpdateTime,
57
priorityLevelToLabel,
58
} from './ReactFiberWorkLoop.old';
66
-import {
67
- registerEvent,
68
- mountEventListener as mountHostEventListener,
69
- unmountEventListener as unmountHostEventListener,
70
- validateEventListenerTarget,
71
-} from './ReactFiberHostConfig';
59
60
import invariant from 'shared/invariant';
61
import getComponentName from 'shared/getComponentName';
@@ -95,7 +82,6 @@ import {
82
setWorkInProgressVersion,
83
warnAboutMultipleRenderersDEV,
84
} from './ReactMutableSource.old';
98
-import {getRootHostContainer} from './ReactFiberHostContext.old';
85
import {getIsRendering} from './ReactCurrentFiber';
86
import {logStateUpdateScheduled} from './DebugTracing';
87
@@ -133,7 +119,6 @@ export type HookType =
119
| 'useDeferredValue'
120
| 'useTransition'
121
| 'useMutableSource'
136
- | 'useEvent'
122
| 'useOpaqueIdentifier';
123
124
let didWarnAboutMismatchedHooksForComponent;
@@ -1755,158 +1740,6 @@ function dispatchAction<S, A>(
1740
}
1741
}
1742
1758
-const noOpMount = () => {};
1759
-
1760
-function validateNotInFunctionRender(): boolean {
1761
- if (currentlyRenderingFiber === null) {
1762
- return true;
1763
- }
1764
- if (__DEV__) {
1765
- console.warn(
1766
- 'Event listener methods from useEvent() cannot be called during render.' +
1767
- ' These methods should be called in an effect or event callback outside the render.',
1768
- );
1769
- }
1770
- return false;
1771
-}
1772
-
1773
-function createReactListener(
1774
- event: ReactListenerEvent,
1775
- callback: (SyntheticEvent<EventTarget>) => void,
1776
- target: EventTarget | ReactScopeMethods,
1777
- destroy: Node => void,
1778
-): ReactListener {
1779
- return {
1780
- callback,
1781
- destroy,
1782
- event,
1783
- target,
1784
- };
1785
-}
1786
-
1787
-function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
1788
- if (enableUseEventAPI) {
1789
- const hook = mountWorkInProgressHook();
1790
- const listenerMap: Map<
1791
- EventTarget | ReactScopeMethods,
1792
- ReactListener,
1793
- > = new Map();
1794
- const rootContainerInstance = getRootHostContainer();
1795
-
1796
- // Register the event to the current root to ensure event
1797
- // replaying can pick up the event ahead of time.
1798
- registerEvent(event, rootContainerInstance);
1799
-
1800
- const clear = () => {
1801
- if (validateNotInFunctionRender()) {
1802
- const listeners = Array.from(listenerMap.values());
1803
- for (let i = 0; i < listeners.length; i++) {
1804
- unmountHostEventListener(listeners[i]);
1805
- }
1806
- listenerMap.clear();
1807
- }
1808
- };
1809
-
1810
- const destroy = (target: Node) => {
1811
- // We don't need to call detachListenerFromInstance
1812
- // here as this method should only ever be called
1813
- // from renderers that need to remove the instance
1814
- // from the map representing an instance that still
1815
- // holds a reference to the listenerMap. This means
1816
- // things like "window" listeners on ReactDOM should
1817
- // never enter this call path as the the instance in
1818
- // those cases would be that of "window", which
1819
- // should be handled via an optimized route in the
1820
- // renderer, making less overhead here. If we change
1821
- // this heuristic we should update this path to make
1822
- // sure we call detachListenerFromInstance.
1823
- listenerMap.delete(target);
1824
- };
1825
-
1826
- const reactListenerMap: ReactListenerMap = {
1827
- clear,
1828
- setListener(
1829
- target: EventTarget | ReactScopeMethods,
1830
- callback: ?(SyntheticEvent<EventTarget>) => void,
1831
- ): void {
1832
- if (
1833
- validateNotInFunctionRender() &&
1834
- validateEventListenerTarget(target, callback)
1835
- ) {
1836
- let listener = listenerMap.get(target);
1837
- if (listener === undefined) {
1838
- if (callback == null) {
1839
- return;
1840
- }
1841
- listener = createReactListener(event, callback, target, destroy);
1842
- listenerMap.set(target, listener);
1843
- } else {
1844
- if (callback == null) {
1845
- listenerMap.delete(target);
1846
- unmountHostEventListener(listener);
1847
- return;
1848
- }
1849
- listener.callback = callback;
1850
- }
1851
- mountHostEventListener(listener);
1852
- }
1853
- },
1854
- };
1855
- // In order to clear up upon the hook unmounting,
1856
- // we ensure we set the effecrt tag so that we visit
1857
- // this effect in the commit phase, so we can handle
1858
- // clean-up accordingly.
1859
- currentlyRenderingFiber.effectTag |= UpdateEffect;
1860
- pushEffect(NoHookEffect, noOpMount, clear, null);
1861
- hook.memoizedState = [reactListenerMap, event, clear];
1862
- return reactListenerMap;
1863
- }
1864
- // To make Flow not complain
1865
- return (undefined: any);
1866
-}
1867
-
1868
-function updateEventListener(event: ReactListenerEvent): ReactListenerMap {
1869
- if (enableUseEventAPI) {
1870
- const hook = updateWorkInProgressHook();
1871
- const [reactListenerMap, memoizedEvent, clear] = hook.memoizedState;
1872
- if (__DEV__) {
1873
- if (memoizedEvent.type !== event.type) {
1874
- console.warn(
1875
- 'The event type argument passed to the useEvent() hook was different between renders.' +
1876
- ' The event type is static and should never change between renders.',
1877
- );
1878
- }
1879
- if (memoizedEvent.capture !== event.capture) {
1880
- console.warn(
1881
- 'The "capture" option passed to the useEvent() hook was different between renders.' +
1882
- ' The "capture" option is static and should never change between renders.',
1883
- );
1884
- }
1885
- if (memoizedEvent.priority !== event.priority) {
1886
- console.warn(
1887
- 'The "priority" option passed to the useEvent() hook was different between renders.' +
1888
- ' The "priority" option is static and should never change between renders.',
1889
- );
1890
- }
1891
- if (memoizedEvent.passive !== event.passive) {
1892
- console.warn(
1893
- 'The "passive" option passed to the useEvent() hook was different between renders.' +
1894
- ' The "passive" option is static and should never change between renders.',
1895
- );
1896
- }
1897
- }
1898
- // In order to clear up upon the hook unmounting,
1899
- // we ensure we set the effecrt tag so that we visit
1900
- // this effect in the commit phase, so we can handle
1901
- // clean-up accordingly.
1902
- currentlyRenderingFiber.effectTag |= UpdateEffect;
1903
- pushEffect(NoHookEffect, noOpMount, clear, null);
1904
- return reactListenerMap;
1905
- }
1906
- // To make Flow not complain
1907
- return (undefined: any);
1908
-}
1909
-
1743
export const ContextOnlyDispatcher: Dispatcher = {
1744
readContext,
1745
@@ -1924,7 +1757,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
1757
useDeferredValue: throwInvalidHookError,
1758
useTransition: throwInvalidHookError,
1759
useMutableSource: throwInvalidHookError,
1927
- useEvent: throwInvalidHookError,
1760
useOpaqueIdentifier: throwInvalidHookError,
1761
};
1762
@@ -1945,7 +1777,6 @@ const HooksDispatcherOnMount: Dispatcher = {
1777
useDeferredValue: mountDeferredValue,
1778
useTransition: mountTransition,
1779
useMutableSource: mountMutableSource,
1948
- useEvent: mountEventListener,
1780
useOpaqueIdentifier: mountOpaqueIdentifier,
1781
};
1782
@@ -1966,7 +1797,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
1797
useDeferredValue: updateDeferredValue,
1798
useTransition: updateTransition,
1799
useMutableSource: updateMutableSource,
1969
- useEvent: updateEventListener,
1800
useOpaqueIdentifier: updateOpaqueIdentifier,
1801
};
1802
@@ -1987,7 +1817,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
1817
useDeferredValue: rerenderDeferredValue,
1818
useTransition: rerenderTransition,
1819
useMutableSource: updateMutableSource,
1990
- useEvent: updateEventListener,
1820
useOpaqueIdentifier: rerenderOpaqueIdentifier,
1821
};
1822
@@ -2146,11 +1975,6 @@ if (__DEV__) {
1975
mountHookTypesDev();
1976
return mountMutableSource(source, getSnapshot, subscribe);
1977
},
2149
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2150
- currentHookNameInDev = 'useEvent';
2151
- mountHookTypesDev();
2152
- return mountEventListener(event);
2153
- },
1978
useOpaqueIdentifier(): OpaqueIDType | void {
1979
currentHookNameInDev = 'useOpaqueIdentifier';
1980
mountHookTypesDev();
@@ -2281,11 +2105,6 @@ if (__DEV__) {
2105
updateHookTypesDev();
2106
return mountMutableSource(source, getSnapshot, subscribe);
2107
},
2284
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2285
- currentHookNameInDev = 'useEvent';
2286
- updateHookTypesDev();
2287
- return mountEventListener(event);
2288
- },
2108
useOpaqueIdentifier(): OpaqueIDType | void {
2109
currentHookNameInDev = 'useOpaqueIdentifier';
2110
updateHookTypesDev();
@@ -2416,11 +2235,6 @@ if (__DEV__) {
2235
updateHookTypesDev();
2236
return updateMutableSource(source, getSnapshot, subscribe);
2237
},
2419
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2420
- currentHookNameInDev = 'useEvent';
2421
- updateHookTypesDev();
2422
- return updateEventListener(event);
2423
- },
2238
useOpaqueIdentifier(): OpaqueIDType | void {
2239
currentHookNameInDev = 'useOpaqueIdentifier';
2240
updateHookTypesDev();
@@ -2552,11 +2366,6 @@ if (__DEV__) {
2366
updateHookTypesDev();
2367
return updateMutableSource(source, getSnapshot, subscribe);
2368
},
2555
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2556
- currentHookNameInDev = 'useEvent';
2557
- updateHookTypesDev();
2558
- return updateEventListener(event);
2559
- },
2369
useOpaqueIdentifier(): OpaqueIDType | void {
2370
currentHookNameInDev = 'useOpaqueIdentifier';
2371
updateHookTypesDev();
@@ -2702,12 +2511,6 @@ if (__DEV__) {
2511
mountHookTypesDev();
2512
return mountMutableSource(source, getSnapshot, subscribe);
2513
},
2705
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2706
- currentHookNameInDev = 'useEvent';
2707
- warnInvalidHookAccess();
2708
- mountHookTypesDev();
2709
- return mountEventListener(event);
2710
- },
2514
useOpaqueIdentifier(): OpaqueIDType | void {
2515
currentHookNameInDev = 'useOpaqueIdentifier';
2516
warnInvalidHookAccess();
@@ -2854,12 +2657,6 @@ if (__DEV__) {
2657
updateHookTypesDev();
2658
return updateMutableSource(source, getSnapshot, subscribe);
2659
},
2857
- useEvent(event: ReactListenerEvent): ReactListenerMap {
2858
- currentHookNameInDev = 'useEvent';
2859
- warnInvalidHookAccess();
2860
- updateHookTypesDev();
2861
- return updateEventListener(event);
2862
- },
2660
useOpaqueIdentifier(): OpaqueIDType | void {
2661
currentHookNameInDev = 'useOpaqueIdentifier';
2662
warnInvalidHookAccess();
@@ -3007,12 +2804,6 @@ if (__DEV__) {
2804
updateHookTypesDev();
2805
return updateMutableSource(source, getSnapshot, subscribe);
2806
},
3010
- useEvent(event: ReactListenerEvent): ReactListenerMap {
3011
- currentHookNameInDev = 'useEvent';
3012
- warnInvalidHookAccess();
3013
- updateHookTypesDev();
3014
- return updateEventListener(event);
3015
- },
2807
useOpaqueIdentifier(): OpaqueIDType | void {
2808
currentHookNameInDev = 'useOpaqueIdentifier';
2809
warnInvalidHookAccess();
packages/react-reconciler/src/ReactInternalTypes.js
+2
-9
@@ -18,10 +18,7 @@ import type {
18
MutableSourceGetSnapshotFn,
19
MutableSource,
20
} from 'shared/ReactTypes';
21
-import type {
22
- SuspenseInstance,
23
- ReactListenerEvent,
24
-} from './ReactFiberHostConfig';
21
+import type {SuspenseInstance} from './ReactFiberHostConfig';
22
import type {WorkTag} from './ReactWorkTags';
23
import type {TypeOfMode} from './ReactTypeOfMode';
24
import type {SideEffectTag} from './ReactSideEffectTags';
@@ -32,10 +29,7 @@ import type {RootTag} from './ReactRootTags';
29
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
30
import type {Wakeable} from 'shared/ReactTypes';
31
import type {Interaction} from 'scheduler/src/Tracing';
35
-import type {
36
- OpaqueIDType,
37
- ReactListenerMap,
38
-} from 'react-reconciler/src/ReactFiberHostConfig';
32
+import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';
33
import type {SuspenseConfig, TimeoutConfig} from './ReactFiberSuspenseConfig';
34
35
export type ReactPriorityLevel = 99 | 98 | 97 | 96 | 95 | 90;
@@ -325,6 +319,5 @@ export type Dispatcher = {|
319
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
320
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
321
): Snapshot,
328
- useEvent(event: ReactListenerEvent): ReactListenerMap,
322
useOpaqueIdentifier(): OpaqueIDType | void,
323
|};
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
-8
@@ -40,9 +40,6 @@ export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
40
export opaque type RendererInspectionConfig = mixed; // eslint-disable-line no-undef
41
export opaque type OpaqueIDType = mixed;
42
export type EventResponder = any;
43
-export type ReactListenerEvent = Object;
44
-export type ReactListenerMap = Object;
45
-export type ReactListener = Object;
43
44
export const getPublicInstance = $$$hostConfig.getPublicInstance;
45
export const getRootHostContext = $$$hostConfig.getRootHostContext;
@@ -78,11 +75,6 @@ export const shouldUpdateFundamentalComponent =
75
$$$hostConfig.shouldUpdateFundamentalComponent;
76
export const getInstanceFromNode = $$$hostConfig.getInstanceFromNode;
77
export const beforeRemoveInstance = $$$hostConfig.beforeRemoveInstance;
81
-export const registerEvent = $$$hostConfig.registerEvent;
82
-export const mountEventListener = $$$hostConfig.mountEventListener;
83
-export const unmountEventListener = $$$hostConfig.unmountEventListener;
84
-export const validateEventListenerTarget =
85
- $$$hostConfig.validateEventListenerTarget;
78
export const isOpaqueHydratingObject = $$$hostConfig.isOpaqueHydratingObject;
79
export const makeOpaqueHydratingObject =
80
$$$hostConfig.makeOpaqueHydratingObject;
packages/react-test-renderer/src/ReactTestHostConfig.js
-19
@@ -52,9 +52,6 @@ export opaque type OpaqueIDType =
52
valueOf: () => string | void,
53
};
54
55
-export type ReactListenerEvent = Object;
56
-export type ReactListenerMap = Object;
57
-export type ReactListener = Object;
55
export type RendererInspectionConfig = $ReadOnly<{||}>;
56
57
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
@@ -431,22 +428,6 @@ export function makeOpaqueHydratingObject(
428
};
429
}
430
434
-export function registerEvent(event: any, rootContainerInstance: Container) {
435
- throw new Error('Not yet implemented.');
436
-}
437
-
438
-export function mountEventListener(listener: any) {
439
- throw new Error('Not yet implemented.');
440
-}
441
-
442
-export function unmountEventListener(listener: any) {
443
- throw new Error('Not yet implemented.');
444
-}
445
-
446
-export function validateEventListenerTarget(target: any, listener: any) {
447
- throw new Error('Not yet implemented.');
448
-}
449
-
431
export function beforeActiveInstanceBlur() {
432
// noop
433
}
packages/shared/ReactFeatureFlags.js
-3
@@ -57,9 +57,6 @@ export const enableFundamentalAPI = false;
57
// Experimental Scope support.
58
export const enableScopeAPI = false;
59
60
-// Experimental useEvent support.
61
-export const enableUseEventAPI = false;
62
-
60
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
61
62
// We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?)
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -27,7 +27,6 @@ export const warnAboutDeprecatedLifecycles = true;
27
export const enableDeprecatedFlareAPI = false;
28
export const enableFundamentalAPI = false;
29
export const enableScopeAPI = false;
30
-export const enableUseEventAPI = false;
30
export const warnAboutUnmockedScheduler = true;
31
export const enableSuspenseCallback = false;
32
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -26,7 +26,6 @@ export const enableSchedulerDebugging = false;
26
export const enableDeprecatedFlareAPI = false;
27
export const enableFundamentalAPI = false;
28
export const enableScopeAPI = false;
29
-export const enableUseEventAPI = false;
29
export const warnAboutUnmockedScheduler = false;
30
export const enableSuspenseCallback = false;
31
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -26,7 +26,6 @@ export const enableSchedulerDebugging = false;
26
export const enableDeprecatedFlareAPI = false;
27
export const enableFundamentalAPI = false;
28
export const enableScopeAPI = false;
29
-export const enableUseEventAPI = false;
29
export const warnAboutUnmockedScheduler = false;
30
export const enableSuspenseCallback = false;
31
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -26,7 +26,6 @@ export const disableInputAttributeSyncing = false;
26
export const enableDeprecatedFlareAPI = true;
27
export const enableFundamentalAPI = false;
28
export const enableScopeAPI = true;
29
-export const enableUseEventAPI = true;
29
export const warnAboutUnmockedScheduler = true;
30
export const enableSuspenseCallback = true;
31
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.testing.js
-1
@@ -26,7 +26,6 @@ export const enableSchedulerDebugging = false;
26
export const enableDeprecatedFlareAPI = false;
27
export const enableFundamentalAPI = false;
28
export const enableScopeAPI = false;
29
-export const enableUseEventAPI = false;
29
export const warnAboutUnmockedScheduler = false;
30
export const enableSuspenseCallback = false;
31
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
-1
@@ -26,7 +26,6 @@ export const enableSchedulerDebugging = false;
26
export const enableDeprecatedFlareAPI = true;
27
export const enableFundamentalAPI = false;
28
export const enableScopeAPI = true;
29
-export const enableUseEventAPI = true;
29
export const warnAboutUnmockedScheduler = true;
30
export const enableSuspenseCallback = true;
31
export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.www.js
-2
@@ -60,8 +60,6 @@ export const enableFundamentalAPI = false;
60
61
export const enableScopeAPI = true;
62
63
-export const enableUseEventAPI = true;
64
-
63
export const warnAboutUnmockedScheduler = true;
64
65
export const enableSuspenseCallback = true;