mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[Events] Add support for events that are both user-blocking and continuous (#15811)
* [Events] Add EventPriority enum React DOM's DispatchConfig for synthetic events has an `isDiscrete` field that affects how updates triggered by an event are scheduled. Events are either discrete or continuous. This commit adds an additional type of configuration where an event has user-blocking priority, but is not discrete. E.g. updates triggered by hover are more important than the default, but they don't need to be processed serially. Because there are now three types of event priority instead of two, I've replaced the `isDiscrete` boolean with an enum: `eventPriority`. This commit implements the new enum value but does not change any behavior. I'll enable it behind a feature flag in the next commit. I've only implemented this in the legacy event system. I'll leave Flare for a follow-up. * enableUserBlockingEvents feature flag Adds a feature flag to increase the priority of events like `mouseover`, without making them discrete.
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export opaque type EventPriority = 0 | 1 | 2;
|
||||
|
||||
export const DiscreteEvent: EventPriority = 0;
|
||||
export const UserBlockingEvent: EventPriority = 1;
|
||||
export const ContinuousEvent: EventPriority = 2;
|
||||
|
||||
export type DispatchConfig = {
|
||||
dependencies: Array<TopLevelType>,
|
||||
phasedRegistrationNames?: {
|
||||
@@ -18,7 +24,7 @@ export type DispatchConfig = {
|
||||
captured: string,
|
||||
},
|
||||
registrationName?: string,
|
||||
isDiscrete?: boolean,
|
||||
eventPriority: EventPriority,
|
||||
};
|
||||
|
||||
export type ReactSyntheticEvent = {
|
||||
|
||||
+56
-7
@@ -11,6 +11,10 @@ import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
|
||||
// Intentionally not named imports because Rollup would use dynamic dispatch for
|
||||
// CommonJS interop named imports.
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
import {
|
||||
batchedEventUpdates,
|
||||
discreteUpdates,
|
||||
@@ -40,9 +44,22 @@ import SimpleEventPlugin from './SimpleEventPlugin';
|
||||
import {getRawEventName} from './DOMTopLevelEventTypes';
|
||||
import {passiveBrowserEventsSupported} from './checkPassiveEvents';
|
||||
|
||||
import {enableEventAPI} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableEventAPI,
|
||||
enableUserBlockingEvents,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
UserBlockingEvent,
|
||||
ContinuousEvent,
|
||||
DiscreteEvent,
|
||||
} from 'events/ReactSyntheticEventType';
|
||||
|
||||
const {isDiscreteTopLevelEventType} = SimpleEventPlugin;
|
||||
const {
|
||||
unstable_UserBlockingPriority: UserBlockingPriority,
|
||||
unstable_runWithPriority: runWithPriority,
|
||||
} = Scheduler;
|
||||
|
||||
const {getEventPriority} = SimpleEventPlugin;
|
||||
|
||||
const CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
|
||||
const callbackBookkeepingPool = [];
|
||||
@@ -212,12 +229,29 @@ function trapEventForPluginEventSystem(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
capture: boolean,
|
||||
): void {
|
||||
const dispatch = isDiscreteTopLevelEventType(topLevelType)
|
||||
? dispatchDiscreteEvent
|
||||
: dispatchEvent;
|
||||
let listener;
|
||||
switch (getEventPriority(topLevelType)) {
|
||||
case DiscreteEvent:
|
||||
listener = dispatchDiscreteEvent.bind(
|
||||
null,
|
||||
topLevelType,
|
||||
PLUGIN_EVENT_SYSTEM,
|
||||
);
|
||||
break;
|
||||
case UserBlockingEvent:
|
||||
listener = dispatchUserBlockingUpdate.bind(
|
||||
null,
|
||||
topLevelType,
|
||||
PLUGIN_EVENT_SYSTEM,
|
||||
);
|
||||
break;
|
||||
case ContinuousEvent:
|
||||
default:
|
||||
listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
|
||||
break;
|
||||
}
|
||||
|
||||
const rawEventName = getRawEventName(topLevelType);
|
||||
// Check if discrete and wrap in discreteUpdates
|
||||
const listener = dispatch.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
|
||||
if (capture) {
|
||||
addEventCaptureListener(element, rawEventName, listener);
|
||||
} else {
|
||||
@@ -230,6 +264,21 @@ function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
|
||||
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
|
||||
}
|
||||
|
||||
function dispatchUserBlockingUpdate(
|
||||
topLevelType,
|
||||
eventSystemFlags,
|
||||
nativeEvent,
|
||||
) {
|
||||
if (enableUserBlockingEvents) {
|
||||
runWithPriority(
|
||||
UserBlockingPriority,
|
||||
dispatchEvent.bind(null, topLevelType, eventSystemFlags, nativeEvent),
|
||||
);
|
||||
} else {
|
||||
dispatchEvent(topLevelType, eventSystemFlags, nativeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchEventForPluginEventSystem(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
|
||||
+130
-96
@@ -14,12 +14,18 @@ import type {
|
||||
import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
EventPriority,
|
||||
} from 'events/ReactSyntheticEventType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {EventTypes, PluginModule} from 'events/PluginModuleType';
|
||||
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import SyntheticEvent from 'events/SyntheticEvent';
|
||||
import {
|
||||
DiscreteEvent,
|
||||
UserBlockingEvent,
|
||||
ContinuousEvent,
|
||||
} from 'events/ReactSyntheticEventType';
|
||||
|
||||
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
@@ -55,84 +61,117 @@ import getEventCharCode from './getEventCharCode';
|
||||
* [TOP_ABORT, { sameConfig }],
|
||||
* ]);
|
||||
*/
|
||||
type EventTuple = [DOMTopLevelEventType, string];
|
||||
const discreteEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
|
||||
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
|
||||
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
|
||||
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
|
||||
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
|
||||
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
|
||||
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
|
||||
[DOMTopLevelEventTypes.TOP_AUX_CLICK, 'auxClick'],
|
||||
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
|
||||
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
|
||||
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
|
||||
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
|
||||
[DOMTopLevelEventTypes.TOP_INVALID, 'invalid'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
|
||||
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
|
||||
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_CANCEL, 'pointerCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_DOWN, 'pointerDown'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_UP, 'pointerUp'],
|
||||
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
|
||||
[DOMTopLevelEventTypes.TOP_RESET, 'reset'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
|
||||
[DOMTopLevelEventTypes.TOP_SUBMIT, 'submit'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
|
||||
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
|
||||
];
|
||||
const continuousEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
|
||||
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
|
||||
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
|
||||
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
|
||||
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
|
||||
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
|
||||
[DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
|
||||
[DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_MOVE, 'pointerMove'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OUT, 'pointerOut'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OVER, 'pointerOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
|
||||
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
|
||||
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
|
||||
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
|
||||
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
|
||||
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
|
||||
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
|
||||
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
|
||||
|
||||
type EventTuple = [DOMTopLevelEventType, string, EventPriority];
|
||||
|
||||
const eventTuples: Array<EventTuple> = [
|
||||
// Discrete events
|
||||
[DOMTopLevelEventTypes.TOP_BLUR, 'blur', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_CLICK, 'click', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_CLOSE, 'close', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_COPY, 'copy', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_CUT, 'cut', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_AUX_CLICK, 'auxClick', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DROP, 'drop', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_INPUT, 'input', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_INVALID, 'invalid', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_PASTE, 'paste', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_PLAY, 'play', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_CANCEL, 'pointerCancel', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_DOWN, 'pointerDown', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_UP, 'pointerUp', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_RESET, 'reset', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_SUBMIT, 'submit', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart', DiscreteEvent],
|
||||
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange', DiscreteEvent],
|
||||
|
||||
// User-blocking events
|
||||
[DOMTopLevelEventTypes.TOP_DRAG, 'drag', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_MOVE, 'pointerMove', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OUT, 'pointerOut', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OVER, 'pointerOver', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove', UserBlockingEvent],
|
||||
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel', UserBlockingEvent],
|
||||
|
||||
// Continuous events
|
||||
[DOMTopLevelEventTypes.TOP_ABORT, 'abort', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd', ContinuousEvent],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION,
|
||||
'animationIteration',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_ANIMATION_START,
|
||||
'animationStart',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay', ContinuousEvent],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH,
|
||||
'canPlayThrough',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_DURATION_CHANGE,
|
||||
'durationChange',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_ENDED, 'ended', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_ERROR, 'error', ContinuousEvent],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE,
|
||||
'gotPointerCapture',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD, 'load', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData', ContinuousEvent],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_LOADED_METADATA,
|
||||
'loadedMetadata',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart', ContinuousEvent],
|
||||
[
|
||||
DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE,
|
||||
'lostPointerCapture',
|
||||
ContinuousEvent,
|
||||
],
|
||||
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd', ContinuousEvent],
|
||||
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting', ContinuousEvent],
|
||||
];
|
||||
|
||||
const eventTypes: EventTypes = {};
|
||||
@@ -140,32 +179,27 @@ const topLevelEventsToDispatchConfig: {
|
||||
[key: TopLevelType]: DispatchConfig,
|
||||
} = {};
|
||||
|
||||
function addEventTypeNameToConfig(
|
||||
[topEvent, event]: EventTuple,
|
||||
isDiscrete: boolean,
|
||||
) {
|
||||
for (let i = 0; i < eventTuples.length; i++) {
|
||||
const eventTuple = eventTuples[i];
|
||||
const topEvent = eventTuple[0];
|
||||
const event = eventTuple[1];
|
||||
const eventPriority = eventTuple[2];
|
||||
|
||||
const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
|
||||
const onEvent = 'on' + capitalizedEvent;
|
||||
|
||||
const type = {
|
||||
const config = {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: onEvent,
|
||||
captured: onEvent + 'Capture',
|
||||
},
|
||||
dependencies: [topEvent],
|
||||
isDiscrete,
|
||||
eventPriority,
|
||||
};
|
||||
eventTypes[event] = type;
|
||||
topLevelEventsToDispatchConfig[topEvent] = type;
|
||||
eventTypes[event] = config;
|
||||
topLevelEventsToDispatchConfig[topEvent] = config;
|
||||
}
|
||||
|
||||
discreteEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, true);
|
||||
});
|
||||
continuousEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, false);
|
||||
});
|
||||
|
||||
// Only used in DEV for exhaustiveness validation.
|
||||
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
|
||||
DOMTopLevelEventTypes.TOP_ABORT,
|
||||
@@ -202,13 +236,13 @@ const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
|
||||
];
|
||||
|
||||
const SimpleEventPlugin: PluginModule<MouseEvent> & {
|
||||
isDiscreteTopLevelEventType: (topLevelType: TopLevelType) => boolean,
|
||||
getEventPriority: (topLevelType: TopLevelType) => EventPriority,
|
||||
} = {
|
||||
eventTypes: eventTypes,
|
||||
|
||||
isDiscreteTopLevelEventType(topLevelType: TopLevelType): boolean {
|
||||
getEventPriority(topLevelType: TopLevelType): EventPriority {
|
||||
const config = topLevelEventsToDispatchConfig[topLevelType];
|
||||
return config !== undefined && config.isDiscrete === true;
|
||||
return config !== undefined ? config.eventPriority : ContinuousEvent;
|
||||
},
|
||||
|
||||
extractEvents: function(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
let React = require('react');
|
||||
let ReactDOM = require('react-dom');
|
||||
let TestUtils = require('react-dom/test-utils');
|
||||
let ReactFeatureFlags;
|
||||
let Scheduler;
|
||||
|
||||
@@ -486,6 +487,7 @@ describe('ChangeEventPlugin', () => {
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
||||
@@ -750,5 +752,49 @@ describe('ChangeEventPlugin', () => {
|
||||
expect(ops).toEqual(['render: ']);
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
|
||||
it('mouse enter/leave should be user-blocking but not discrete', async () => {
|
||||
// This is currently behind a feature flag
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableUserBlockingEvents = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const {act} = TestUtils;
|
||||
const {useState} = React;
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
|
||||
const target = React.createRef(null);
|
||||
function Foo() {
|
||||
const [isHover, setHover] = useState(false);
|
||||
return (
|
||||
<div
|
||||
ref={target}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}>
|
||||
{isHover ? 'hovered' : 'not hovered'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
expect(container.textContent).toEqual('not hovered');
|
||||
|
||||
await act(async () => {
|
||||
const mouseOverEvent = document.createEvent('MouseEvents');
|
||||
mouseOverEvent.initEvent('mouseover', true, true);
|
||||
target.current.dispatchEvent(mouseOverEvent);
|
||||
|
||||
// 3s should be enough to expire the updates
|
||||
Scheduler.advanceTime(3000);
|
||||
expect(container.textContent).toEqual('hovered');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,3 +73,8 @@ export const enableJSXTransformAPI = false;
|
||||
export const warnAboutMissingMockScheduler = false;
|
||||
// Temporary flag to revert the fix in #15650
|
||||
export const revertPassiveEffectsChange = false;
|
||||
|
||||
// Changes priority of some events like mousemove to user-blocking priority,
|
||||
// but without making them discrete. The flag exists in case it causes
|
||||
// starvation problems.
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
@@ -36,6 +36,7 @@ export const enableEventAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutMissingMockScheduler = true;
|
||||
export const revertPassiveEffectsChange = false;
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -31,6 +31,7 @@ export const enableEventAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutMissingMockScheduler = false;
|
||||
export const revertPassiveEffectsChange = false;
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -31,6 +31,7 @@ export const enableEventAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutMissingMockScheduler = true;
|
||||
export const revertPassiveEffectsChange = false;
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -31,6 +31,7 @@ export const enableEventAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutMissingMockScheduler = false;
|
||||
export const revertPassiveEffectsChange = false;
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -31,6 +31,7 @@ export const disableYielding = false;
|
||||
export const enableEventAPI = true;
|
||||
export const enableJSXTransformAPI = true;
|
||||
export const warnAboutMissingMockScheduler = true;
|
||||
export const enableUserBlockingEvents = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -21,6 +21,7 @@ export const {
|
||||
warnAboutShorthandPropertyCollision,
|
||||
warnAboutDeprecatedSetNativeProps,
|
||||
revertPassiveEffectsChange,
|
||||
enableUserBlockingEvents,
|
||||
} = require('ReactFeatureFlags');
|
||||
|
||||
// In www, we have experimental support for gathering data
|
||||
|
||||
Reference in New Issue
Block a user