Skip to content

Commit 91973e0

Browse files
trueadmsmagill
authored andcommitted
Add Listener API + useEvent/useDelegatedEvent
Update error codes fix lint DCE fix test Add event kinds to help propagation rules Add event kinds to help propagation rules #2 Fix kind bug cleanup Address feedback Fix flow Major refactor and re-design More revisions Cleanup Cleanup 2 Add DCE and unmounting logic for host instances
1 parent 1b9328c commit 91973e0

29 files changed

+1805
-22
lines changed

packages/legacy-events/EventSystemFlags.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ export type EventSystemFlags = number;
1111

1212
export const PLUGIN_EVENT_SYSTEM = 1;
1313
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
14-
export const IS_PASSIVE = 1 << 2;
15-
export const IS_ACTIVE = 1 << 3;
16-
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
17-
export const IS_REPLAYED = 1 << 5;
18-
export const IS_FIRST_ANCESTOR = 1 << 6;
14+
export const LISTENER_EVENT_SYSTEM = 1 << 2;
15+
export const IS_PASSIVE = 1 << 3;
16+
export const IS_ACTIVE = 1 << 4;
17+
export const PASSIVE_NOT_SUPPORTED = 1 << 5;
18+
export const IS_REPLAYED = 1 << 6;
19+
export const IS_FIRST_ANCESTOR = 1 << 7;

packages/legacy-events/PluginModuleType.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
1717

1818
export type EventTypes = {[key: string]: DispatchConfig};
1919

20-
export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
20+
export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent;
2121

2222
export type PluginName = string;
2323

packages/legacy-events/ReactGenericBatching.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
1010
restoreStateIfNeeded,
1111
} from './ReactControlledComponent';
1212

13-
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
13+
import {
14+
enableDeprecatedFlareAPI,
15+
enableListenerAPI,
16+
} from 'shared/ReactFeatureFlags';
1417
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
1518

1619
// Used as a way to call batchedUpdates when we don't have a reference to
@@ -118,7 +121,7 @@ export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
118121
// behaviour as we had before this change, so the risks are low.
119122
if (
120123
!isInsideEventHandler &&
121-
(!enableDeprecatedFlareAPI ||
124+
((!enableDeprecatedFlareAPI && !enableListenerAPI) ||
122125
(timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp))
123126
) {
124127
lastFlushedEventTimeStamp = timeStamp;

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,26 @@ export function getInstanceFromNode(node) {
469469
export function beforeRemoveInstance(instance) {
470470
// noop
471471
}
472+
473+
export function registerListenerEvent(
474+
event: any,
475+
rootContainerInstance: Container,
476+
): void {
477+
// noop
478+
}
479+
480+
export function attachListenerToInstance(listener: any): any {
481+
// noop
482+
}
483+
484+
export function detachListenerFromInstance(listener: any): any {
485+
// noop
486+
}
487+
488+
export function validateReactListenerDeleteListener(instance): void {
489+
// noop
490+
}
491+
492+
export function validateReactListenerMapListener(instance, listener): void {
493+
// noop
494+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ function useResponder(
238238
};
239239
}
240240

241+
const noOp = () => {};
242+
243+
function useEvent(options: any): any {
244+
hookLog.push({primitive: 'Event', stackError: new Error(), value: options});
245+
return {
246+
clear: noOp,
247+
listen: noOp,
248+
unlisten: noOp,
249+
};
250+
}
251+
241252
function useTransition(
242253
config: SuspenseConfig | null | void,
243254
): [(() => void) => void, boolean] {
@@ -275,6 +286,7 @@ const Dispatcher: DispatcherType = {
275286
useResponder,
276287
useTransition,
277288
useDeferredValue,
289+
useEvent,
278290
};
279291

280292
// Inspect

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ import {
5353
} from 'legacy-events/EventPropagators';
5454
import ReactVersion from 'shared/ReactVersion';
5555
import invariant from 'shared/invariant';
56-
import {exposeConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
56+
import {
57+
exposeConcurrentModeAPIs,
58+
enableListenerAPI,
59+
} from 'shared/ReactFeatureFlags';
5760

5861
import {
5962
getInstanceFromNode,
@@ -70,6 +73,7 @@ import {
7073
setAttemptHydrationAtCurrentPriority,
7174
queueExplicitHydrationTarget,
7275
} from '../events/ReactDOMEventReplaying';
76+
import {useEvent} from './ReactDOMEventListenerHooks';
7377

7478
setAttemptSynchronousHydration(attemptSynchronousHydration);
7579
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
@@ -193,6 +197,10 @@ if (exposeConcurrentModeAPIs) {
193197
};
194198
}
195199

200+
if (enableListenerAPI) {
201+
ReactDOM.unstable_useEvent = useEvent;
202+
}
203+
196204
const foundDevTools = injectIntoDevTools({
197205
findFiberByHostInstance: getClosestInstanceFromNode,
198206
bundleType: __DEV__ ? 1 : 0,

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ import {
6565
import {
6666
addResponderEventSystemEvent,
6767
removeActiveResponderEventSystemEvent,
68+
addListenerSystemEvent,
69+
removeListenerSystemEvent,
6870
} from '../events/ReactDOMEventListener.js';
6971
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
7072
import {
@@ -90,6 +92,7 @@ import {toStringOrTrustedType} from './ToStringValue';
9092
import {
9193
enableDeprecatedFlareAPI,
9294
enableTrustedTypesIntegration,
95+
enableListenerAPI,
9396
} from 'shared/ReactFeatureFlags';
9497

9598
let didWarnInvalidHydration = false;
@@ -1345,6 +1348,42 @@ export function listenToEventResponderEventTypes(
13451348
}
13461349
}
13471350

1351+
export function listenToEventListener(
1352+
type: string,
1353+
passive: boolean,
1354+
document: Document,
1355+
): void {
1356+
if (enableListenerAPI) {
1357+
// Get the listening Map for this element. We use this to track
1358+
// what events we're listening to.
1359+
const listenerMap = getListenerMapForElement(document);
1360+
const passiveKey = type + '_passive';
1361+
const activeKey = type + '_active';
1362+
const eventKey = passive ? passiveKey : activeKey;
1363+
1364+
if (!listenerMap.has(eventKey)) {
1365+
if (passive) {
1366+
if (listenerMap.has(activeKey)) {
1367+
// If we have an active event listener, do not register
1368+
// a passive event listener. We use the same active event
1369+
// listener.
1370+
return;
1371+
} else {
1372+
// If we have a passive event listener, remove the
1373+
// existing passive event listener before we add the
1374+
// active event listener.
1375+
const passiveListener = listenerMap.get(passiveKey);
1376+
if (passiveListener != null) {
1377+
removeListenerSystemEvent(document, type, passiveListener);
1378+
}
1379+
}
1380+
}
1381+
const eventListener = addListenerSystemEvent(document, type, passive);
1382+
listenerMap.set(eventKey, eventListener);
1383+
}
1384+
}
1385+
}
1386+
13481387
// We can remove this once the event API is stable and out of a flag
13491388
if (enableDeprecatedFlareAPI) {
13501389
setListenToResponderEventTypes(listenToEventResponderEventTypes);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const randomKey = Math.random()
2020
.slice(2);
2121
const internalInstanceKey = '__reactInternalInstance$' + randomKey;
2222
const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
23+
const internalEventListenersKey = '__reactEventListeners$' + randomKey;
2324
const internalContainerInstanceKey = '__reactContainere$' + randomKey;
2425

2526
export function precacheFiberNode(hostInst, node) {
@@ -164,3 +165,11 @@ export function getFiberCurrentPropsFromNode(node) {
164165
export function updateFiberProps(node, props) {
165166
node[internalEventHandlersKey] = props;
166167
}
168+
169+
export function getListenersFromNode(node) {
170+
return node[internalEventListenersKey] || null;
171+
}
172+
173+
export function initListenersSet(node, value) {
174+
node[internalEventListenersKey] = value;
175+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 {
11+
ReactDOMListenerEvent,
12+
ReactDOMListenerMap,
13+
} from 'shared/ReactDOMTypes';
14+
15+
import React from 'react';
16+
import invariant from 'shared/invariant';
17+
import {getEventPriority} from '../events/SimpleEventPlugin';
18+
19+
const ReactCurrentDispatcher =
20+
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
21+
.ReactCurrentDispatcher;
22+
23+
type EventOptions = {|
24+
capture?: boolean,
25+
passive?: boolean,
26+
priority?: number,
27+
|};
28+
29+
function resolveDispatcher() {
30+
const dispatcher = ReactCurrentDispatcher.current;
31+
invariant(
32+
dispatcher !== null,
33+
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
34+
' one of the following reasons:\n' +
35+
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
36+
'2. You might be breaking the Rules of Hooks\n' +
37+
'3. You might have more than one copy of React in the same app\n' +
38+
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
39+
);
40+
return dispatcher;
41+
}
42+
43+
export function useEvent(
44+
type: string,
45+
options?: EventOptions,
46+
): ReactDOMListenerMap {
47+
const dispatcher = resolveDispatcher();
48+
let capture = false;
49+
let passive = false;
50+
let priority = getEventPriority((type: any));
51+
52+
if (options != null) {
53+
const optionsCapture = options && options.capture;
54+
const optionsPassive = options && options.passive;
55+
const optionsPriority = options && options.priority;
56+
57+
if (typeof optionsCapture === 'boolean') {
58+
capture = optionsCapture;
59+
}
60+
if (typeof optionsPassive === 'boolean') {
61+
passive = optionsPassive;
62+
}
63+
if (typeof optionsPriority === 'number') {
64+
priority = optionsPriority;
65+
}
66+
}
67+
const event: ReactDOMListenerEvent = {
68+
capture,
69+
passive,
70+
priority,
71+
type,
72+
};
73+
return dispatcher.useEvent(event);
74+
}

0 commit comments

Comments
 (0)