mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 00:20:28 +01:00
* Add Event Replaying Infra * Wire up Roots and Suspense boundaries, to retry events, after they commit * Replay discrete events in order in a separate scheduler callback * Add continuous events These events only replay their last target if the target is not yet hydrated. That way we don't have to wait for a previously hovered boundary before invoking the current target. * Enable tests from before These tests were written with replaying in mind and now we can properly enable them. * Unify replaying and dispatching * Mark system flags as a replay and pass to legacy events That way we can check if this is a replay and therefore needs a special case. One such special case is "mouseover" where we check the relatedTarget. * Eagerly listen to all replayable events To minimize breakages in a minor, I only do this for the new root APIs since replaying only matters there anyway. Only if hydrating. For Flare, I have to attach all active listeners since the current system has one DOM listener for each. In a follow up I plan on optimizing that by only attaching one if there's at least one active listener which would allow us to start with only passive and then upgrade. * Desperate attempt to save bytese * Add test for mouseover replaying We need to check if the "relatedTarget" is mounted due to how the old event system dispatches from the "out" event. * Fix for nested boundaries and suspense in root container This is a follow up to #16673 which didn't have a test because it wasn't observable yet. This shows that it had a bug. * Rename RESPONDER_EVENT_SYSTEM to PLUGIN_EVENT_SYSTEM
176 lines
4.9 KiB
JavaScript
176 lines
4.9 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
* @flow
|
|
*/
|
|
|
|
import invariant from 'shared/invariant';
|
|
|
|
import {
|
|
injectEventPluginOrder,
|
|
injectEventPluginsByName,
|
|
plugins,
|
|
} from './EventPluginRegistry';
|
|
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
|
|
import accumulateInto from './accumulateInto';
|
|
import {runEventsInBatch} from './EventBatching';
|
|
|
|
import type {PluginModule} from './PluginModuleType';
|
|
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
|
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
|
import type {AnyNativeEvent} from './PluginModuleType';
|
|
import type {TopLevelType} from './TopLevelEventTypes';
|
|
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
|
|
|
function isInteractive(tag) {
|
|
return (
|
|
tag === 'button' ||
|
|
tag === 'input' ||
|
|
tag === 'select' ||
|
|
tag === 'textarea'
|
|
);
|
|
}
|
|
|
|
function shouldPreventMouseEvent(name, type, props) {
|
|
switch (name) {
|
|
case 'onClick':
|
|
case 'onClickCapture':
|
|
case 'onDoubleClick':
|
|
case 'onDoubleClickCapture':
|
|
case 'onMouseDown':
|
|
case 'onMouseDownCapture':
|
|
case 'onMouseMove':
|
|
case 'onMouseMoveCapture':
|
|
case 'onMouseUp':
|
|
case 'onMouseUpCapture':
|
|
return !!(props.disabled && isInteractive(type));
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a unified interface for event plugins to be installed and configured.
|
|
*
|
|
* Event plugins can implement the following properties:
|
|
*
|
|
* `extractEvents` {function(string, DOMEventTarget, string, object): *}
|
|
* Required. When a top-level event is fired, this method is expected to
|
|
* extract synthetic events that will in turn be queued and dispatched.
|
|
*
|
|
* `eventTypes` {object}
|
|
* Optional, plugins that fire events must publish a mapping of registration
|
|
* names that are used to register listeners. Values of this mapping must
|
|
* be objects that contain `registrationName` or `phasedRegistrationNames`.
|
|
*
|
|
* `executeDispatch` {function(object, function, string)}
|
|
* Optional, allows plugins to override how an event gets dispatched. By
|
|
* default, the listener is simply invoked.
|
|
*
|
|
* Each plugin that is injected into `EventsPluginHub` is immediately operable.
|
|
*
|
|
* @public
|
|
*/
|
|
|
|
/**
|
|
* Methods for injecting dependencies.
|
|
*/
|
|
export const injection = {
|
|
/**
|
|
* @param {array} InjectedEventPluginOrder
|
|
* @public
|
|
*/
|
|
injectEventPluginOrder,
|
|
|
|
/**
|
|
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
|
|
*/
|
|
injectEventPluginsByName,
|
|
};
|
|
|
|
/**
|
|
* @param {object} inst The instance, which is the source of events.
|
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
|
* @return {?function} The stored callback.
|
|
*/
|
|
export function getListener(inst: Fiber, registrationName: string) {
|
|
let listener;
|
|
|
|
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
|
|
// live here; needs to be moved to a better place soon
|
|
const stateNode = inst.stateNode;
|
|
if (!stateNode) {
|
|
// Work in progress (ex: onload events in incremental mode).
|
|
return null;
|
|
}
|
|
const props = getFiberCurrentPropsFromNode(stateNode);
|
|
if (!props) {
|
|
// Work in progress.
|
|
return null;
|
|
}
|
|
listener = props[registrationName];
|
|
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
|
|
return null;
|
|
}
|
|
invariant(
|
|
!listener || typeof listener === 'function',
|
|
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
|
|
registrationName,
|
|
typeof listener,
|
|
);
|
|
return listener;
|
|
}
|
|
|
|
/**
|
|
* Allows registered plugins an opportunity to extract events from top-level
|
|
* native browser events.
|
|
*
|
|
* @return {*} An accumulation of synthetic events.
|
|
* @internal
|
|
*/
|
|
function extractPluginEvents(
|
|
topLevelType: TopLevelType,
|
|
eventSystemFlags: EventSystemFlags,
|
|
targetInst: null | Fiber,
|
|
nativeEvent: AnyNativeEvent,
|
|
nativeEventTarget: EventTarget,
|
|
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
|
|
let events = null;
|
|
for (let i = 0; i < plugins.length; i++) {
|
|
// Not every plugin in the ordering may be loaded at runtime.
|
|
const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
|
|
if (possiblePlugin) {
|
|
const extractedEvents = possiblePlugin.extractEvents(
|
|
topLevelType,
|
|
eventSystemFlags,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
);
|
|
if (extractedEvents) {
|
|
events = accumulateInto(events, extractedEvents);
|
|
}
|
|
}
|
|
}
|
|
return events;
|
|
}
|
|
|
|
export function runExtractedPluginEventsInBatch(
|
|
topLevelType: TopLevelType,
|
|
eventSystemFlags: EventSystemFlags,
|
|
targetInst: null | Fiber,
|
|
nativeEvent: AnyNativeEvent,
|
|
nativeEventTarget: EventTarget,
|
|
) {
|
|
const events = extractPluginEvents(
|
|
topLevelType,
|
|
eventSystemFlags,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
);
|
|
runEventsInBatch(events);
|
|
}
|