/** * 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 | 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 = 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); }