mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
80f8b0d512
* Add part of the event responder system
171 lines
4.7 KiB
JavaScript
171 lines
4.7 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';
|
|
|
|
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,
|
|
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,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
);
|
|
if (extractedEvents) {
|
|
events = accumulateInto(events, extractedEvents);
|
|
}
|
|
}
|
|
}
|
|
return events;
|
|
}
|
|
|
|
export function runExtractedPluginEventsInBatch(
|
|
topLevelType: TopLevelType,
|
|
targetInst: null | Fiber,
|
|
nativeEvent: AnyNativeEvent,
|
|
nativeEventTarget: EventTarget,
|
|
) {
|
|
const events = extractPluginEvents(
|
|
topLevelType,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
);
|
|
runEventsInBatch(events);
|
|
}
|