Make event plugin injection statically resolvable (#19234)

* Make plugins ESM

* Resolve extractEvents statically

* Resolve eventTypes statically

* Fix flow types and inconsistent naming

* Move injection into the plugin system itself

* Fix Flow
This commit is contained in:
Dan Abramov 2020-07-01 22:11:05 +01:00 committed by GitHub
parent 67eb6ff4a8
commit 9e7f5c02ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 574 additions and 607 deletions

View File

@ -8,7 +8,7 @@
*/
import {
registrationNameModules,
registrationNames,
possibleRegistrationNames,
} from '../events/EventPluginRegistry';
import {canUseDOM} from 'shared/ExecutionEnvironment';
@ -133,7 +133,7 @@ if (__DEV__) {
validateARIAProperties(type, props);
validateInputProperties(type, props);
validateUnknownProperties(type, props, {
registrationNameModules,
registrationNames,
possibleRegistrationNames,
});
};
@ -356,7 +356,7 @@ function setInitialDOMProperties(
// We could have excluded it in the property list instead of
// adding a special case here, but then it wouldn't be emitted
// on server rendering (but we *do* want to emit it in SSR).
} else if (registrationNameModules.hasOwnProperty(propKey)) {
} else if (registrationNames.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
@ -694,7 +694,7 @@ export function diffProperties(
// Noop
} else if (propKey === AUTOFOCUS) {
// Noop. It doesn't work on updates anyway.
} else if (registrationNameModules.hasOwnProperty(propKey)) {
} else if (registrationNames.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
// that the "current" fiber pointer gets updated so we need a commit
// to update this element.
@ -781,7 +781,7 @@ export function diffProperties(
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
} else if (registrationNames.hasOwnProperty(propKey)) {
if (nextProp != null) {
// We eagerly listen to this even though we haven't committed yet.
if (__DEV__ && typeof nextProp !== 'function') {
@ -978,7 +978,7 @@ export function diffHydratedProperties(
updatePayload = [CHILDREN, '' + nextProp];
}
}
} else if (registrationNameModules.hasOwnProperty(propKey)) {
} else if (registrationNames.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);

View File

@ -12,6 +12,7 @@ import type {
TopLevelType,
DOMTopLevelEventType,
} from '../events/TopLevelEventTypes';
import type {EventTypes} from '../events/PluginModuleType';
import type {
DispatchConfig,
CustomDispatchConfig,
@ -32,7 +33,7 @@ import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
// here once. If we remove or refactor the
// SimpleEventPlugin, we should also remove or
// update the below line.
export const simpleEventPluginEventTypes = {};
export const simpleEventPluginEventTypes: EventTypes = {};
export const topLevelEventsToDispatchConfig: Map<
TopLevelType,

View File

@ -7,29 +7,30 @@
* @flow
*/
import type {AnyNativeEvent} from '../events/PluginModuleType';
import type {DOMTopLevelEventType} from '../events/TopLevelEventTypes';
import type {
ElementListenerMap,
ElementListenerMapEntry,
} from '../client/ReactDOMComponentTree';
import type {TopLevelType, DOMTopLevelEventType} from './TopLevelEventTypes';
import type {EventSystemFlags} from './EventSystemFlags';
import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
ModernPluginModule,
AnyNativeEvent,
DispatchQueue,
DispatchQueueItem,
DispatchQueueItemPhase,
DispatchQueueItemPhaseEntry,
} from '../events/PluginModuleType';
} from './PluginModuleType';
import type {
ReactSyntheticEvent,
CustomDispatchConfig,
} from '../events/ReactSyntheticEventType';
} from './ReactSyntheticEventType';
import type {
ElementListenerMap,
ElementListenerMapEntry,
} from '../client/ReactDOMComponentTree';
import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {registrationNameDependencies} from '../events/EventPluginRegistry';
import {plugins} from '../events/EventPluginRegistry';
import {
injectEventPlugin,
registrationNameDependencies,
} from './EventPluginRegistry';
import {
PLUGIN_EVENT_SYSTEM,
LEGACY_FB_SUPPORT,
@ -113,6 +114,74 @@ import {
} from './EventListener';
import {removeTrappedEventListener} from './DeprecatedDOMEventResponderSystem';
import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
import * as ModernBeforeInputEventPlugin from './plugins/ModernBeforeInputEventPlugin';
import * as ModernChangeEventPlugin from './plugins/ModernChangeEventPlugin';
import * as ModernEnterLeaveEventPlugin from './plugins/ModernEnterLeaveEventPlugin';
import * as ModernSelectEventPlugin from './plugins/ModernSelectEventPlugin';
import * as ModernSimpleEventPlugin from './plugins/ModernSimpleEventPlugin';
// TODO: remove top-level side effect.
injectEventPlugin(ModernSimpleEventPlugin.eventTypes);
injectEventPlugin(ModernEnterLeaveEventPlugin.eventTypes);
injectEventPlugin(ModernChangeEventPlugin.eventTypes);
injectEventPlugin(ModernSelectEventPlugin.eventTypes);
injectEventPlugin(ModernBeforeInputEventPlugin.eventTypes);
function extractEvents(
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
ModernSimpleEventPlugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
ModernEnterLeaveEventPlugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
ModernChangeEventPlugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
ModernSelectEventPlugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
ModernBeforeInputEventPlugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
}
export const capturePhaseEvents: Set<DOMTopLevelEventType> = new Set([
TOP_FOCUS,
@ -218,22 +287,17 @@ function dispatchEventsForPlugins(
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
const modernPlugins = ((plugins: any): Array<ModernPluginModule<Event>>);
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
for (let i = 0; i < modernPlugins.length; i++) {
const plugin = modernPlugins[i];
plugin.extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
}
extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
dispatchEventsInBatch(dispatchQueue);
}

View File

@ -7,110 +7,11 @@
* @flow
*/
import type {DispatchConfig} from './ReactSyntheticEventType';
import type {
AnyNativeEvent,
LegacyPluginModule,
ModernPluginModule,
} from './PluginModuleType';
import ModernBeforeInputEventPlugin from '../events/plugins/ModernBeforeInputEventPlugin';
import ModernChangeEventPlugin from '../events/plugins/ModernChangeEventPlugin';
import ModernEnterLeaveEventPlugin from '../events/plugins/ModernEnterLeaveEventPlugin';
import ModernSelectEventPlugin from '../events/plugins/ModernSelectEventPlugin';
import ModernSimpleEventPlugin from '../events/plugins/ModernSimpleEventPlugin';
import type {TopLevelType} from './TopLevelEventTypes';
import type {EventTypes} from './PluginModuleType';
import invariant from 'shared/invariant';
/**
* Publishes an event so that it can be dispatched by the supplied plugin.
*
* @param {object} dispatchConfig Dispatch configuration for the event.
* @param {object} PluginModule Plugin publishing the event.
* @return {boolean} True if the event was successfully published.
* @private
*/
function publishEventForPlugin(
dispatchConfig: DispatchConfig,
pluginModule:
| LegacyPluginModule<AnyNativeEvent>
| ModernPluginModule<AnyNativeEvent>,
eventName: string,
): boolean {
invariant(
!eventNameDispatchConfigs.hasOwnProperty(eventName),
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'event name, `%s`.',
eventName,
);
eventNameDispatchConfigs[eventName] = dispatchConfig;
const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
if (phasedRegistrationNames) {
for (const phaseName in phasedRegistrationNames) {
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
const phasedRegistrationName = phasedRegistrationNames[phaseName];
publishRegistrationName(
phasedRegistrationName,
pluginModule,
eventName,
);
}
}
return true;
} else if (dispatchConfig.registrationName) {
publishRegistrationName(
dispatchConfig.registrationName,
pluginModule,
eventName,
);
return true;
}
return false;
}
/**
* Publishes a registration name that is used to identify dispatched events.
*
* @param {string} registrationName Registration name to add.
* @param {object} PluginModule Plugin publishing the event.
* @private
*/
function publishRegistrationName(
registrationName: string,
pluginModule:
| LegacyPluginModule<AnyNativeEvent>
| ModernPluginModule<AnyNativeEvent>,
eventName: string,
): void {
invariant(
!registrationNameModules[registrationName],
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName,
);
registrationNameModules[registrationName] = pluginModule;
registrationNameDependencies[registrationName] =
pluginModule.eventTypes[eventName].dependencies;
if (__DEV__) {
const lowerCasedName = registrationName.toLowerCase();
possibleRegistrationNames[lowerCasedName] = registrationName;
if (registrationName === 'onDoubleClick') {
possibleRegistrationNames.ondblclick = registrationName;
}
}
}
/**
* Registers plugins so that they can extract and dispatch events.
*/
/**
* Ordered list of injected plugins.
*/
export const plugins = [];
/**
* Mapping from event name to dispatch config
*/
@ -119,7 +20,7 @@ export const eventNameDispatchConfigs = {};
/**
* Mapping from registration name to plugin module
*/
export const registrationNameModules = {};
export const registrationNames = {};
/**
* Mapping from registration name to event name
@ -135,17 +36,66 @@ export const registrationNameDependencies = {};
export const possibleRegistrationNames = __DEV__ ? {} : (null: any);
// Trust the developer to only use possibleRegistrationNames in __DEV__
function injectEventPlugin(pluginModule: ModernPluginModule<any>): void {
plugins.push(pluginModule);
const publishedEvents = pluginModule.eventTypes;
for (const eventName in publishedEvents) {
publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName);
function publishEventForPlugin(
eventTypes: EventTypes,
eventName: string,
): boolean {
invariant(
!eventNameDispatchConfigs.hasOwnProperty(eventName),
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'event name, `%s`.',
eventName,
);
const dispatchConfig = eventTypes[eventName];
eventNameDispatchConfigs[eventName] = dispatchConfig;
const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
if (phasedRegistrationNames) {
for (const phaseName in phasedRegistrationNames) {
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
const phasedRegistrationName = phasedRegistrationNames[phaseName];
publishRegistrationName(
phasedRegistrationName,
eventTypes[eventName].dependencies,
);
}
}
return true;
} else if (dispatchConfig.registrationName) {
publishRegistrationName(
dispatchConfig.registrationName,
eventTypes[eventName].dependencies,
);
return true;
}
return false;
}
function publishRegistrationName(
registrationName: string,
dependencies: ?Array<TopLevelType>,
): void {
invariant(
!registrationNames[registrationName],
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName,
);
registrationNames[registrationName] = true;
registrationNameDependencies[registrationName] = dependencies;
if (__DEV__) {
const lowerCasedName = registrationName.toLowerCase();
possibleRegistrationNames[lowerCasedName] = registrationName;
if (registrationName === 'onDoubleClick') {
possibleRegistrationNames.ondblclick = registrationName;
}
}
}
// TODO: remove top-level side effect.
injectEventPlugin(ModernSimpleEventPlugin);
injectEventPlugin(ModernEnterLeaveEventPlugin);
injectEventPlugin(ModernChangeEventPlugin);
injectEventPlugin(ModernSelectEventPlugin);
injectEventPlugin(ModernBeforeInputEventPlugin);
export function injectEventPlugin(eventTypes: EventTypes): void {
for (const eventName in eventTypes) {
publishEventForPlugin(eventTypes, eventName);
}
}

View File

@ -12,7 +12,6 @@ import type {
DispatchConfig,
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {TopLevelType} from './TopLevelEventTypes';
export type EventTypes = {[key: string]: DispatchConfig, ...};
@ -22,19 +21,6 @@ export type PluginName = string;
export type EventSystemFlags = number;
export type LegacyPluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags?: number,
container?: null | EventTarget,
) => ?ReactSyntheticEvent,
tapMoveThreshold?: number,
};
export type DispatchQueueItemPhaseEntry = {|
instance: null | Fiber,
listener: Function,
@ -50,16 +36,3 @@ export type DispatchQueueItem = {|
|};
export type DispatchQueue = Array<DispatchQueueItem>;
export type ModernPluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: number,
container: null | EventTarget,
) => void,
};

View File

@ -58,7 +58,7 @@ const SPACEBAR_CODE = 32;
const SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
// Events and their corresponding property names.
const eventTypes = {
const eventTypes: EventTypes = {
beforeInput: {
phasedRegistrationNames: {
bubbled: 'onBeforeInput',
@ -457,33 +457,29 @@ function extractBeforeInputEvent(
* allowing us to share composition fallback code for both `beforeInput` and
* `composition` event types.
*/
const BeforeInputEventPlugin = {
eventTypes: eventTypes,
extractEvents: function(
function extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
) {
extractCompositionEvent(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
container,
) {
extractCompositionEvent(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
extractBeforeInputEvent(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
},
};
);
extractBeforeInputEvent(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
}
export default BeforeInputEventPlugin;
export {eventTypes, extractEvents};

View File

@ -6,7 +6,7 @@
*
* @flow
*/
import type {AnyNativeEvent, EventTypes} from '../PluginModuleType';
import type {TopLevelType} from '../TopLevelEventTypes';
import type {DispatchQueue} from '../PluginModuleType';
import type {EventSystemFlags} from '../EventSystemFlags';
@ -39,7 +39,7 @@ import {
accumulateTwoPhaseListeners,
} from '../DOMModernPluginEventSystem';
const eventTypes = {
const eventTypes: EventTypes = {
change: {
phasedRegistrationNames: {
bubbled: 'onChange',
@ -271,58 +271,52 @@ function handleControlledInputBlur(node: HTMLInputElement) {
* - textarea
* - select
*/
const ChangeEventPlugin = {
eventTypes: eventTypes,
function extractEvents(
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
_isInputEventSupported: isInputEventSupported,
extractEvents: function(
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: MouseEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
let getTargetInstFunc, handleEventFunc;
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(((targetNode: any): HTMLElement))) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
let getTargetInstFunc, handleEventFunc;
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(((targetNode: any): HTMLElement))) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
}
if (getTargetInstFunc) {
const inst = getTargetInstFunc(topLevelType, targetInst);
if (inst) {
createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
nativeEventTarget,
);
return;
}
if (getTargetInstFunc) {
const inst = getTargetInstFunc(topLevelType, targetInst);
if (inst) {
createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
nativeEventTarget,
);
return;
}
}
if (handleEventFunc) {
handleEventFunc(topLevelType, targetNode, targetInst);
}
if (handleEventFunc) {
handleEventFunc(topLevelType, targetNode, targetInst);
}
// When blurring, set the value attribute for number inputs
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(((targetNode: any): HTMLInputElement));
}
},
};
// When blurring, set the value attribute for number inputs
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(((targetNode: any): HTMLInputElement));
}
}
export default ChangeEventPlugin;
export {eventTypes, extractEvents};

View File

@ -23,7 +23,7 @@ import {accumulateEnterLeaveListeners} from '../DOMModernPluginEventSystem';
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
const eventTypes = {
const eventTypes: EventTypes = {
mouseEnter: {
registrationName: 'onMouseEnter',
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
@ -42,139 +42,135 @@ const eventTypes = {
},
};
const EnterLeaveEventPlugin = {
eventTypes: eventTypes,
/**
* For almost every interaction we care about, there will be both a top-level
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
* we do not extract duplicate events. However, moving the mouse into the
* browser from outside will not fire a `mouseout` event. In this case, we use
* the `mouseover` top-level event.
*/
function extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
) {
const isOverEvent =
topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER;
const isOutEvent =
topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT;
/**
* For almost every interaction we care about, there will be both a top-level
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
* we do not extract duplicate events. However, moving the mouse into the
* browser from outside will not fire a `mouseout` event. In this case, we use
* the `mouseover` top-level event.
*/
extractEvents: function(
dispatchQueue,
topLevelType,
targetInst,
if (isOverEvent && (eventSystemFlags & IS_REPLAYED) === 0) {
const related = nativeEvent.relatedTarget || nativeEvent.fromElement;
if (related) {
// Due to the fact we don't add listeners to the document with the
// modern event system and instead attach listeners to roots, we
// need to handle the over event case. To ensure this, we just need to
// make sure the node that we're coming from is managed by React.
const inst = getClosestInstanceFromNode(related);
if (inst !== null) {
return;
}
}
}
if (!isOutEvent && !isOverEvent) {
// Must not be a mouse or pointer in or out - ignoring.
return;
}
let win;
if (nativeEventTarget.window === nativeEventTarget) {
// `nativeEventTarget` is probably a window object.
win = nativeEventTarget;
} else {
// TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
const doc = nativeEventTarget.ownerDocument;
if (doc) {
win = doc.defaultView || doc.parentWindow;
} else {
win = window;
}
}
let from;
let to;
if (isOutEvent) {
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
from = targetInst;
to = related ? getClosestInstanceFromNode(related) : null;
if (to !== null) {
const nearestMounted = getNearestMountedFiber(to);
if (
to !== nearestMounted ||
(to.tag !== HostComponent && to.tag !== HostText)
) {
to = null;
}
}
} else {
// Moving to a node from outside the window.
from = null;
to = targetInst;
}
if (from === to) {
// Nothing pertains to our managed components.
return;
}
let eventInterface, leaveEventType, enterEventType, eventTypePrefix;
if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) {
eventInterface = SyntheticMouseEvent;
leaveEventType = eventTypes.mouseLeave;
enterEventType = eventTypes.mouseEnter;
eventTypePrefix = 'mouse';
} else if (
topLevelType === TOP_POINTER_OUT ||
topLevelType === TOP_POINTER_OVER
) {
eventInterface = SyntheticPointerEvent;
leaveEventType = eventTypes.pointerLeave;
enterEventType = eventTypes.pointerEnter;
eventTypePrefix = 'pointer';
}
const fromNode = from == null ? win : getNodeFromInstance(from);
const toNode = to == null ? win : getNodeFromInstance(to);
const leave = new eventInterface(
leaveEventType,
from,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
container,
) {
const isOverEvent =
topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER;
const isOutEvent =
topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT;
);
leave.type = eventTypePrefix + 'leave';
leave.target = fromNode;
leave.relatedTarget = toNode;
if (isOverEvent && (eventSystemFlags & IS_REPLAYED) === 0) {
const related = nativeEvent.relatedTarget || nativeEvent.fromElement;
if (related) {
// Due to the fact we don't add listeners to the document with the
// modern event system and instead attach listeners to roots, we
// need to handle the over event case. To ensure this, we just need to
// make sure the node that we're coming from is managed by React.
const inst = getClosestInstanceFromNode(related);
if (inst !== null) {
return;
}
}
}
let enter = new eventInterface(
enterEventType,
to,
nativeEvent,
nativeEventTarget,
);
enter.type = eventTypePrefix + 'enter';
enter.target = toNode;
enter.relatedTarget = fromNode;
if (!isOutEvent && !isOverEvent) {
// Must not be a mouse or pointer in or out - ignoring.
return;
}
// If we are not processing the first ancestor, then we
// should not process the same nativeEvent again, as we
// will have already processed it in the first ancestor.
const nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget);
if (nativeTargetInst !== targetInst) {
enter = null;
}
let win;
if (nativeEventTarget.window === nativeEventTarget) {
// `nativeEventTarget` is probably a window object.
win = nativeEventTarget;
} else {
// TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
const doc = nativeEventTarget.ownerDocument;
if (doc) {
win = doc.defaultView || doc.parentWindow;
} else {
win = window;
}
}
accumulateEnterLeaveListeners(dispatchQueue, leave, enter, from, to);
}
let from;
let to;
if (isOutEvent) {
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
from = targetInst;
to = related ? getClosestInstanceFromNode(related) : null;
if (to !== null) {
const nearestMounted = getNearestMountedFiber(to);
if (
to !== nearestMounted ||
(to.tag !== HostComponent && to.tag !== HostText)
) {
to = null;
}
}
} else {
// Moving to a node from outside the window.
from = null;
to = targetInst;
}
if (from === to) {
// Nothing pertains to our managed components.
return;
}
let eventInterface, leaveEventType, enterEventType, eventTypePrefix;
if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) {
eventInterface = SyntheticMouseEvent;
leaveEventType = eventTypes.mouseLeave;
enterEventType = eventTypes.mouseEnter;
eventTypePrefix = 'mouse';
} else if (
topLevelType === TOP_POINTER_OUT ||
topLevelType === TOP_POINTER_OVER
) {
eventInterface = SyntheticPointerEvent;
leaveEventType = eventTypes.pointerLeave;
enterEventType = eventTypes.pointerEnter;
eventTypePrefix = 'pointer';
}
const fromNode = from == null ? win : getNodeFromInstance(from);
const toNode = to == null ? win : getNodeFromInstance(to);
const leave = new eventInterface(
leaveEventType,
from,
nativeEvent,
nativeEventTarget,
);
leave.type = eventTypePrefix + 'leave';
leave.target = fromNode;
leave.relatedTarget = toNode;
let enter = new eventInterface(
enterEventType,
to,
nativeEvent,
nativeEventTarget,
);
enter.type = eventTypePrefix + 'enter';
enter.target = toNode;
enter.relatedTarget = fromNode;
// If we are not processing the first ancestor, then we
// should not process the same nativeEvent again, as we
// will have already processed it in the first ancestor.
const nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget);
if (nativeTargetInst !== targetInst) {
enter = null;
}
accumulateEnterLeaveListeners(dispatchQueue, leave, enter, from, to);
},
};
export default EnterLeaveEventPlugin;
export {eventTypes, extractEvents};

View File

@ -48,7 +48,7 @@ const rootTargetDependencies = [
TOP_MOUSE_UP,
];
const eventTypes = {
const eventTypes: EventTypes = {
select: {
phasedRegistrationNames: {
bubbled: 'onSelect',
@ -176,6 +176,84 @@ function isListeningToEvent(
return listenerMap.has(listenerMapKey);
}
function extractEvents(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
) {
const doc = getEventTargetDocument(nativeEventTarget);
// Track whether all listeners exists for this plugin. If none exist, we do
// not extract events. See #3639.
if (
// We only listen to TOP_SELECTION_CHANGE on the document, never the
// root.
!isListeningToEvent(TOP_SELECTION_CHANGE, doc) ||
// If we are handling TOP_SELECTION_CHANGE, then we don't need to
// check for the other dependencies, as TOP_SELECTION_CHANGE is only
// event attached from the onChange plugin and we don't expose an
// onSelectionChange event from React.
(topLevelType !== TOP_SELECTION_CHANGE &&
!isListeningToEvents(rootTargetDependencies, targetContainer))
) {
return;
}
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
switch (topLevelType) {
// Track the input node that has focus.
case TOP_FOCUS:
if (
isTextInputElement(targetNode) ||
targetNode.contentEditable === 'true'
) {
activeElement = targetNode;
activeElementInst = targetInst;
lastSelection = null;
}
break;
case TOP_BLUR:
activeElement = null;
activeElementInst = null;
lastSelection = null;
break;
// Don't fire the event while the user is dragging. This matches the
// semantics of the native select event.
case TOP_MOUSE_DOWN:
mouseDown = true;
break;
case TOP_CONTEXT_MENU:
case TOP_MOUSE_UP:
case TOP_DRAG_END:
mouseDown = false;
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
break;
// Chrome and IE fire non-standard event when selection is changed (and
// sometimes when it hasn't). IE's event fires out of order with respect
// to key and input events on deletion, so we discard it.
//
// Firefox doesn't support selectionchange, so check selection status
// after each key entry. The selection changes after keydown and before
// keyup, but we check on keydown as well in the case of holding down a
// key, when multiple keydown events are fired but only one keyup is.
// This is also our approach for IE handling, for the reason above.
case TOP_SELECTION_CHANGE:
if (skipSelectionChangeEvent) {
break;
}
// falls through
case TOP_KEY_DOWN:
case TOP_KEY_UP:
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
}
return;
}
/**
* This plugin creates an `onSelect` event that normalizes select events
* across form elements.
@ -190,86 +268,4 @@ function isListeningToEvent(
* - Fires for collapsed selection.
* - Fires after user input.
*/
const SelectEventPlugin = {
eventTypes: eventTypes,
extractEvents: function(
dispatchQueue,
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
container,
) {
const doc = getEventTargetDocument(nativeEventTarget);
// Track whether all listeners exists for this plugin. If none exist, we do
// not extract events. See #3639.
if (
// We only listen to TOP_SELECTION_CHANGE on the document, never the
// root.
!isListeningToEvent(TOP_SELECTION_CHANGE, doc) ||
// If we are handling TOP_SELECTION_CHANGE, then we don't need to
// check for the other dependencies, as TOP_SELECTION_CHANGE is only
// event attached from the onChange plugin and we don't expose an
// onSelectionChange event from React.
(topLevelType !== TOP_SELECTION_CHANGE &&
!isListeningToEvents(rootTargetDependencies, container))
) {
return;
}
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
switch (topLevelType) {
// Track the input node that has focus.
case TOP_FOCUS:
if (
isTextInputElement(targetNode) ||
targetNode.contentEditable === 'true'
) {
activeElement = targetNode;
activeElementInst = targetInst;
lastSelection = null;
}
break;
case TOP_BLUR:
activeElement = null;
activeElementInst = null;
lastSelection = null;
break;
// Don't fire the event while the user is dragging. This matches the
// semantics of the native select event.
case TOP_MOUSE_DOWN:
mouseDown = true;
break;
case TOP_CONTEXT_MENU:
case TOP_MOUSE_UP:
case TOP_DRAG_END:
mouseDown = false;
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
break;
// Chrome and IE fire non-standard event when selection is changed (and
// sometimes when it hasn't). IE's event fires out of order with respect
// to key and input events on deletion, so we discard it.
//
// Firefox doesn't support selectionchange, so check selection status
// after each key entry. The selection changes after keydown and before
// keyup, but we check on keydown as well in the case of holding down a
// key, when multiple keydown events are fired but only one keyup is.
// This is also our approach for IE handling, for the reason above.
case TOP_SELECTION_CHANGE:
if (skipSelectionChangeEvent) {
break;
}
// falls through
case TOP_KEY_DOWN:
case TOP_KEY_UP:
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
}
return;
},
};
export default SelectEventPlugin;
export {eventTypes, extractEvents};

View File

@ -13,7 +13,7 @@ import type {
} from '../../events/TopLevelEventTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
ModernPluginModule,
AnyNativeEvent,
DispatchQueue,
} from '../../events/PluginModuleType';
import type {EventSystemFlags} from '../EventSystemFlags';
@ -81,144 +81,144 @@ const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
DOMTopLevelEventTypes.TOP_WAITING,
];
const SimpleEventPlugin: ModernPluginModule<MouseEvent> = {
function extractEvents(
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
): void {
const dispatchConfig = topLevelEventsToDispatchConfig.get(topLevelType);
if (!dispatchConfig) {
return;
}
let EventConstructor;
switch (topLevelType) {
case DOMTopLevelEventTypes.TOP_KEY_PRESS:
// Firefox creates a keypress event for function keys too. This removes
// the unwanted keypress events. Enter is however both printable and
// non-printable. One would expect Tab to be as well (but it isn't).
if (getEventCharCode(nativeEvent) === 0) {
return;
}
/* falls through */
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
case DOMTopLevelEventTypes.TOP_KEY_UP:
EventConstructor = SyntheticKeyboardEvent;
break;
case DOMTopLevelEventTypes.TOP_BLUR:
case DOMTopLevelEventTypes.TOP_FOCUS:
case DOMTopLevelEventTypes.TOP_BEFORE_BLUR:
case DOMTopLevelEventTypes.TOP_AFTER_BLUR:
EventConstructor = SyntheticFocusEvent;
break;
case DOMTopLevelEventTypes.TOP_CLICK:
// Firefox creates a click event on right mouse clicks. This removes the
// unwanted click events.
if (nativeEvent.button === 2) {
return;
}
/* falls through */
case DOMTopLevelEventTypes.TOP_AUX_CLICK:
case DOMTopLevelEventTypes.TOP_DOUBLE_CLICK:
case DOMTopLevelEventTypes.TOP_MOUSE_DOWN:
case DOMTopLevelEventTypes.TOP_MOUSE_MOVE:
case DOMTopLevelEventTypes.TOP_MOUSE_UP:
// TODO: Disabled elements should not respond to mouse events
/* falls through */
case DOMTopLevelEventTypes.TOP_MOUSE_OUT:
case DOMTopLevelEventTypes.TOP_MOUSE_OVER:
case DOMTopLevelEventTypes.TOP_CONTEXT_MENU:
EventConstructor = SyntheticMouseEvent;
break;
case DOMTopLevelEventTypes.TOP_DRAG:
case DOMTopLevelEventTypes.TOP_DRAG_END:
case DOMTopLevelEventTypes.TOP_DRAG_ENTER:
case DOMTopLevelEventTypes.TOP_DRAG_EXIT:
case DOMTopLevelEventTypes.TOP_DRAG_LEAVE:
case DOMTopLevelEventTypes.TOP_DRAG_OVER:
case DOMTopLevelEventTypes.TOP_DRAG_START:
case DOMTopLevelEventTypes.TOP_DROP:
EventConstructor = SyntheticDragEvent;
break;
case DOMTopLevelEventTypes.TOP_TOUCH_CANCEL:
case DOMTopLevelEventTypes.TOP_TOUCH_END:
case DOMTopLevelEventTypes.TOP_TOUCH_MOVE:
case DOMTopLevelEventTypes.TOP_TOUCH_START:
EventConstructor = SyntheticTouchEvent;
break;
case DOMTopLevelEventTypes.TOP_ANIMATION_END:
case DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION:
case DOMTopLevelEventTypes.TOP_ANIMATION_START:
EventConstructor = SyntheticAnimationEvent;
break;
case DOMTopLevelEventTypes.TOP_TRANSITION_END:
EventConstructor = SyntheticTransitionEvent;
break;
case DOMTopLevelEventTypes.TOP_SCROLL:
EventConstructor = SyntheticUIEvent;
break;
case DOMTopLevelEventTypes.TOP_WHEEL:
EventConstructor = SyntheticWheelEvent;
break;
case DOMTopLevelEventTypes.TOP_COPY:
case DOMTopLevelEventTypes.TOP_CUT:
case DOMTopLevelEventTypes.TOP_PASTE:
EventConstructor = SyntheticClipboardEvent;
break;
case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE:
case DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE:
case DOMTopLevelEventTypes.TOP_POINTER_CANCEL:
case DOMTopLevelEventTypes.TOP_POINTER_DOWN:
case DOMTopLevelEventTypes.TOP_POINTER_MOVE:
case DOMTopLevelEventTypes.TOP_POINTER_OUT:
case DOMTopLevelEventTypes.TOP_POINTER_OVER:
case DOMTopLevelEventTypes.TOP_POINTER_UP:
EventConstructor = SyntheticPointerEvent;
break;
default:
if (__DEV__) {
if (
knownHTMLTopLevelTypes.indexOf(topLevelType) === -1 &&
dispatchConfig.customEvent !== true
) {
console.error(
'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' +
'is likely caused by a bug in React. Please file an issue.',
topLevelType,
);
}
}
// HTML Events
// @see http://www.w3.org/TR/html5/index.html#events-0
EventConstructor = SyntheticEvent;
break;
}
const event = new EventConstructor(
dispatchConfig,
null,
nativeEvent,
nativeEventTarget,
);
if (
enableCreateEventHandleAPI &&
eventSystemFlags !== undefined &&
eventSystemFlags & IS_TARGET_PHASE_ONLY &&
targetContainer != null
) {
accumulateEventTargetListeners(dispatchQueue, event, targetContainer);
} else {
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event, true);
}
return event;
}
export {
// simpleEventPluginEventTypes gets populated from
// the DOMEventProperties module.
eventTypes: simpleEventPluginEventTypes,
extractEvents: function(
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: MouseEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
): void {
const dispatchConfig = topLevelEventsToDispatchConfig.get(topLevelType);
if (!dispatchConfig) {
return;
}
let EventConstructor;
switch (topLevelType) {
case DOMTopLevelEventTypes.TOP_KEY_PRESS:
// Firefox creates a keypress event for function keys too. This removes
// the unwanted keypress events. Enter is however both printable and
// non-printable. One would expect Tab to be as well (but it isn't).
if (getEventCharCode(nativeEvent) === 0) {
return;
}
/* falls through */
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
case DOMTopLevelEventTypes.TOP_KEY_UP:
EventConstructor = SyntheticKeyboardEvent;
break;
case DOMTopLevelEventTypes.TOP_BLUR:
case DOMTopLevelEventTypes.TOP_FOCUS:
case DOMTopLevelEventTypes.TOP_BEFORE_BLUR:
case DOMTopLevelEventTypes.TOP_AFTER_BLUR:
EventConstructor = SyntheticFocusEvent;
break;
case DOMTopLevelEventTypes.TOP_CLICK:
// Firefox creates a click event on right mouse clicks. This removes the
// unwanted click events.
if (nativeEvent.button === 2) {
return;
}
/* falls through */
case DOMTopLevelEventTypes.TOP_AUX_CLICK:
case DOMTopLevelEventTypes.TOP_DOUBLE_CLICK:
case DOMTopLevelEventTypes.TOP_MOUSE_DOWN:
case DOMTopLevelEventTypes.TOP_MOUSE_MOVE:
case DOMTopLevelEventTypes.TOP_MOUSE_UP:
// TODO: Disabled elements should not respond to mouse events
/* falls through */
case DOMTopLevelEventTypes.TOP_MOUSE_OUT:
case DOMTopLevelEventTypes.TOP_MOUSE_OVER:
case DOMTopLevelEventTypes.TOP_CONTEXT_MENU:
EventConstructor = SyntheticMouseEvent;
break;
case DOMTopLevelEventTypes.TOP_DRAG:
case DOMTopLevelEventTypes.TOP_DRAG_END:
case DOMTopLevelEventTypes.TOP_DRAG_ENTER:
case DOMTopLevelEventTypes.TOP_DRAG_EXIT:
case DOMTopLevelEventTypes.TOP_DRAG_LEAVE:
case DOMTopLevelEventTypes.TOP_DRAG_OVER:
case DOMTopLevelEventTypes.TOP_DRAG_START:
case DOMTopLevelEventTypes.TOP_DROP:
EventConstructor = SyntheticDragEvent;
break;
case DOMTopLevelEventTypes.TOP_TOUCH_CANCEL:
case DOMTopLevelEventTypes.TOP_TOUCH_END:
case DOMTopLevelEventTypes.TOP_TOUCH_MOVE:
case DOMTopLevelEventTypes.TOP_TOUCH_START:
EventConstructor = SyntheticTouchEvent;
break;
case DOMTopLevelEventTypes.TOP_ANIMATION_END:
case DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION:
case DOMTopLevelEventTypes.TOP_ANIMATION_START:
EventConstructor = SyntheticAnimationEvent;
break;
case DOMTopLevelEventTypes.TOP_TRANSITION_END:
EventConstructor = SyntheticTransitionEvent;
break;
case DOMTopLevelEventTypes.TOP_SCROLL:
EventConstructor = SyntheticUIEvent;
break;
case DOMTopLevelEventTypes.TOP_WHEEL:
EventConstructor = SyntheticWheelEvent;
break;
case DOMTopLevelEventTypes.TOP_COPY:
case DOMTopLevelEventTypes.TOP_CUT:
case DOMTopLevelEventTypes.TOP_PASTE:
EventConstructor = SyntheticClipboardEvent;
break;
case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE:
case DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE:
case DOMTopLevelEventTypes.TOP_POINTER_CANCEL:
case DOMTopLevelEventTypes.TOP_POINTER_DOWN:
case DOMTopLevelEventTypes.TOP_POINTER_MOVE:
case DOMTopLevelEventTypes.TOP_POINTER_OUT:
case DOMTopLevelEventTypes.TOP_POINTER_OVER:
case DOMTopLevelEventTypes.TOP_POINTER_UP:
EventConstructor = SyntheticPointerEvent;
break;
default:
if (__DEV__) {
if (
knownHTMLTopLevelTypes.indexOf(topLevelType) === -1 &&
dispatchConfig.customEvent !== true
) {
console.error(
'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' +
'is likely caused by a bug in React. Please file an issue.',
topLevelType,
);
}
}
// HTML Events
// @see http://www.w3.org/TR/html5/index.html#events-0
EventConstructor = SyntheticEvent;
break;
}
const event = new EventConstructor(
dispatchConfig,
null,
nativeEvent,
nativeEventTarget,
);
if (
enableCreateEventHandleAPI &&
eventSystemFlags !== undefined &&
eventSystemFlags & IS_TARGET_PHASE_ONLY &&
targetContainer != null
) {
accumulateEventTargetListeners(dispatchQueue, event, targetContainer);
} else {
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event, true);
}
return event;
},
simpleEventPluginEventTypes as eventTypes,
extractEvents,
};
export default SimpleEventPlugin;

View File

@ -43,11 +43,8 @@ if (__DEV__) {
// We can't rely on the event system being injected on the server.
if (eventRegistry != null) {
const {
registrationNameModules,
possibleRegistrationNames,
} = eventRegistry;
if (registrationNameModules.hasOwnProperty(name)) {
const {registrationNames, possibleRegistrationNames} = eventRegistry;
if (registrationNames.hasOwnProperty(name)) {
return true;
}
const registrationName = possibleRegistrationNames.hasOwnProperty(