/** * Copyright (c) Meta Platforms, Inc. and 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 type { AnyNativeEvent, EventTypes, } from './legacy-events/PluginModuleType'; import type {TopLevelType} from './legacy-events/TopLevelEventTypes'; import SyntheticEvent from './legacy-events/SyntheticEvent'; // Module provided by RN: import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; import accumulateInto from './legacy-events/accumulateInto'; import getListener from './ReactNativeGetListener'; import forEachAccumulated from './legacy-events/forEachAccumulated'; import {HostComponent} from 'react-reconciler/src/ReactWorkTags'; const {customBubblingEventTypes, customDirectEventTypes} = ReactNativeViewConfigRegistry; // Start of inline: the below functions were inlined from // EventPropagator.js, as they deviated from ReactDOM's newer // implementations. // $FlowFixMe[missing-local-annot] function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) { const registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase]; return getListener(inst, registrationName); } // $FlowFixMe[missing-local-annot] function accumulateDirectionalDispatches(inst, phase, event) { if (__DEV__) { if (!inst) { console.error('Dispatching inst must not be null'); } } const listener = listenerAtPhase(inst, event, phase); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener, ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } // $FlowFixMe[missing-local-annot] function getParent(inst) { do { inst = inst.return; // TODO: If this is a HostRoot we might want to bail out. // That is depending on if we want nested subtrees (layers) to bubble // events to their parent. We could also go through parentNode on the // host node but that wouldn't work for React Native and doesn't let us // do the portal feature. } while (inst && inst.tag !== HostComponent); if (inst) { return inst; } return null; } /** * Simulates the traversal of a two-phase, capture/bubble event dispatch. */ export function traverseTwoPhase( inst: Object, fn: Function, arg: Function, skipBubbling: boolean, ) { const path = []; while (inst) { path.push(inst); inst = getParent(inst); } let i; for (i = path.length; i-- > 0; ) { fn(path[i], 'captured', arg); } if (skipBubbling) { // Dispatch on target only fn(path[0], 'bubbled', arg); } else { for (i = 0; i < path.length; i++) { fn(path[i], 'bubbled', arg); } } } // $FlowFixMe[missing-local-annot] function accumulateTwoPhaseDispatchesSingle(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { traverseTwoPhase( event._targetInst, accumulateDirectionalDispatches, event, false, ); } } // $FlowFixMe[missing-local-annot] function accumulateTwoPhaseDispatches(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle); } // $FlowFixMe[missing-local-annot] function accumulateCapturePhaseDispatches(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { traverseTwoPhase( event._targetInst, accumulateDirectionalDispatches, event, true, ); } } /** * Accumulates without regard to direction, does not look for phased * registration names. Same as `accumulateDirectDispatchesSingle` but without * requiring that the `dispatchMarker` be the same as the dispatched ID. */ function accumulateDispatches( inst: Object, ignoredDirection: ?boolean, event: Object, ): void { if (inst && event && event.dispatchConfig.registrationName) { const registrationName = event.dispatchConfig.registrationName; const listener = getListener(inst, registrationName); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener, ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } } /** * Accumulates dispatches on an `SyntheticEvent`, but only for the * `dispatchMarker`. * @param {SyntheticEvent} event */ function accumulateDirectDispatchesSingle(event: Object) { if (event && event.dispatchConfig.registrationName) { accumulateDispatches(event._targetInst, null, event); } } function accumulateDirectDispatches(events: ?(Array | Object)) { forEachAccumulated(events, accumulateDirectDispatchesSingle); } // End of inline type PropagationPhases = 'bubbled' | 'captured'; const ReactNativeBridgeEventPlugin = { eventTypes: ({}: EventTypes), extractEvents: function ( topLevelType: TopLevelType, targetInst: null | Object, nativeEvent: AnyNativeEvent, nativeEventTarget: null | Object, ): ?Object { if (targetInst == null) { // Probably a node belonging to another renderer's tree. return null; } const bubbleDispatchConfig = customBubblingEventTypes[topLevelType]; const directDispatchConfig = customDirectEventTypes[topLevelType]; if (!bubbleDispatchConfig && !directDispatchConfig) { throw new Error( // $FlowFixMe[incompatible-type] - Flow doesn't like this string coercion because DOMTopLevelEventType is opaque `Unsupported top level event type "${topLevelType}" dispatched`, ); } const event = SyntheticEvent.getPooled( bubbleDispatchConfig || directDispatchConfig, targetInst, nativeEvent, nativeEventTarget, ); if (bubbleDispatchConfig) { const skipBubbling = event != null && event.dispatchConfig.phasedRegistrationNames != null && event.dispatchConfig.phasedRegistrationNames.skipBubbling; if (skipBubbling) { accumulateCapturePhaseDispatches(event); } else { accumulateTwoPhaseDispatches(event); } } else if (directDispatchConfig) { accumulateDirectDispatches(event); } else { return null; } return event; }, }; export default ReactNativeBridgeEventPlugin;