Skip to content

[react-interactions] Add experimental ReactDOM Listener API #17508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/legacy-events/EventSystemFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export type EventSystemFlags = number;

export const PLUGIN_EVENT_SYSTEM = 1;
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
export const IS_PASSIVE = 1 << 2;
export const IS_ACTIVE = 1 << 3;
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
export const IS_REPLAYED = 1 << 5;
export const LISTENER_EVENT_SYSTEM = 1 << 2;
export const IS_PASSIVE = 1 << 3;
export const IS_ACTIVE = 1 << 4;
export const PASSIVE_NOT_SUPPORTED = 1 << 5;
export const IS_REPLAYED = 1 << 6;
2 changes: 1 addition & 1 deletion packages/legacy-events/PluginModuleType.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';

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

export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent;

export type PluginName = string;

Expand Down
4 changes: 2 additions & 2 deletions packages/legacy-events/ReactGenericBatching.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
restoreStateIfNeeded,
} from './ReactControlledComponent';

import {enableFlareAPI} from 'shared/ReactFeatureFlags';
import {enableFlareAPI, enableListenerAPI} from 'shared/ReactFeatureFlags';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';

// Used as a way to call batchedUpdates when we don't have a reference to
Expand Down Expand Up @@ -118,7 +118,7 @@ export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
// behaviour as we had before this change, so the risks are low.
if (
!isInsideEventHandler &&
(!enableFlareAPI ||
((!enableFlareAPI && !enableListenerAPI) ||
(timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp))
) {
lastFlushedEventTimeStamp = timeStamp;
Expand Down
20 changes: 18 additions & 2 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export function unhideTextInstance(textInstance, text): void {
// Noop
}

export function mountResponderInstance(
export function mountDeprecatedFlareResponderInstance(
responder: ReactEventResponder<any, any>,
responderInstance: ReactEventResponderInstance<any, any>,
props: Object,
Expand All @@ -436,7 +436,7 @@ export function mountResponderInstance(
throw new Error('Not yet implemented.');
}

export function unmountResponderInstance(
export function unmountDeprecatedFlareResponderInstance(
responderInstance: ReactEventResponderInstance<any, any>,
): void {
throw new Error('Not yet implemented.');
Expand Down Expand Up @@ -469,3 +469,19 @@ export function getInstanceFromNode(node) {
export function beforeRemoveInstance(instance) {
// noop
}

export function prepareListener(listener: any, rootContainerInstance: any) {
throw new Error('Not yet implemented.');
}

export function diffListeners(pendingListener: any, memoizedListener: any) {
throw new Error('Not yet implemented.');
}

export function commitListenerInstance(listenerInstance: any) {
throw new Error('Not yet implemented.');
}

export function unmountListenerInstance(listenerInstance: any) {
throw new Error('Not yet implemented.');
}
16 changes: 15 additions & 1 deletion packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import {
unmountComponentAtNode,
} from './ReactDOMLegacy';
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
import {
createListener,
createRootListener,
setEventPriority,
} from './ReactDOMListener';

import {
batchedEventUpdates,
Expand Down Expand Up @@ -55,7 +60,10 @@ import ReactVersion from 'shared/ReactVersion';
import invariant from 'shared/invariant';
import lowPriorityWarningWithoutStack from 'shared/lowPriorityWarningWithoutStack';
import warningWithoutStack from 'shared/warningWithoutStack';
import {exposeConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
import {
exposeConcurrentModeAPIs,
enableListenerAPI,
} from 'shared/ReactFeatureFlags';

import {
getInstanceFromNode,
Expand Down Expand Up @@ -195,6 +203,12 @@ if (exposeConcurrentModeAPIs) {
};
}

if (enableListenerAPI) {
ReactDOM.unstable_createListener = createListener;
ReactDOM.unstable_createRootListener = createRootListener;
ReactDOM.unstable_setEventPriority = setEventPriority;
}

const foundDevTools = injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: __DEV__ ? 1 : 0,
Expand Down
51 changes: 47 additions & 4 deletions packages/react-dom/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* @flow
*/

import type {ReactDOMListener} from 'shared/ReactDOMTypes';

// TODO: direct imports like some-package/src/* are bad. Fix me.
import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
Expand Down Expand Up @@ -65,8 +67,8 @@ import {
getListenerMapForElement,
} from '../events/ReactBrowserEventEmitter';
import {
addResponderEventSystemEvent,
removeActiveResponderEventSystemEvent,
addListenerSystemEvent,
removeListenerSystemEvent,
} from '../events/ReactDOMEventListener.js';
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
import {
Expand All @@ -92,6 +94,7 @@ import {toStringOrTrustedType} from './ToStringValue';
import {
enableFlareAPI,
enableTrustedTypesIntegration,
enableListenerAPI,
} from 'shared/ReactFeatureFlags';

let didWarnInvalidHydration = false;
Expand All @@ -106,6 +109,7 @@ const CHILDREN = 'children';
const STYLE = 'style';
const HTML = '__html';
const DEPRECATED_flareListeners = 'DEPRECATED_flareListeners';
const LISTENERS = 'listeners';

const {html: HTML_NAMESPACE} = Namespaces;

Expand Down Expand Up @@ -718,6 +722,7 @@ export function diffProperties(
// Noop. This is handled by the clear text mechanism.
} else if (
(enableFlareAPI && propKey === DEPRECATED_flareListeners) ||
(enableListenerAPI && propKey === LISTENERS) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
Expand Down Expand Up @@ -813,6 +818,7 @@ export function diffProperties(
}
} else if (
(enableFlareAPI && propKey === DEPRECATED_flareListeners) ||
(enableListenerAPI && propKey === LISTENERS) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
Expand Down Expand Up @@ -1068,6 +1074,7 @@ export function diffHydratedProperties(
// Don't bother comparing. We're ignoring all these warnings.
} else if (
(enableFlareAPI && propKey === DEPRECATED_flareListeners) ||
(enableListenerAPI && propKey === LISTENERS) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING ||
// Controlled attributes are not validated
Expand Down Expand Up @@ -1340,14 +1347,14 @@ export function listenToEventResponderEventTypes(
const passiveKey = targetEventType + '_passive';
const passiveListener = listenerMap.get(passiveKey);
if (passiveListener != null) {
removeActiveResponderEventSystemEvent(
removeListenerSystemEvent(
document,
targetEventType,
passiveListener,
);
}
}
const eventListener = addResponderEventSystemEvent(
const eventListener = addListenerSystemEvent(
document,
targetEventType,
isPassive,
Expand All @@ -1358,6 +1365,42 @@ export function listenToEventResponderEventTypes(
}
}

export function listenToReactListenerEvent(
listener: ReactDOMListener,
document: Document,
): void {
if (enableListenerAPI) {
// Get the listening Map for this element. We use this to track
// what events we're listening to.
const listenerMap = getListenerMapForElement(document);
const {type, passive} = listener;
const passiveKey = type + '_passive';
const activeKey = type + '_active';
const eventKey = passive ? passiveKey : activeKey;

if (!listenerMap.has(eventKey)) {
if (passive) {
if (listenerMap.has(activeKey)) {
// If we have an active event listener, do not register
// a passive event listener. We use the same active event
// listener.
return;
} else {
// If we have a passive event listener, remove the
// existing passive event listener before we add the
// active event listener.
const passiveListener = listenerMap.get(passiveKey);
if (passiveListener != null) {
removeListenerSystemEvent(document, type, passiveListener);
}
}
}
const eventListener = addListenerSystemEvent(document, type, passive);
listenerMap.set(eventKey, eventListener);
}
}
}

// We can remove this once the event API is stable and out of a flag
if (enableFlareAPI) {
setListenToResponderEventTypes(listenToEventResponderEventTypes);
Expand Down
Loading