mirror of
https://github.com/facebook/react.git
synced 2026-02-24 04:33:04 +00:00
176 lines
5.2 KiB
JavaScript
176 lines
5.2 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 type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
|
|
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
|
import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
|
|
import SyntheticEvent from 'legacy-events/SyntheticEvent';
|
|
import invariant from 'shared/invariant';
|
|
|
|
// Module provided by RN:
|
|
import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
|
import accumulateInto from 'legacy-events/accumulateInto';
|
|
import getListener from 'legacy-events/getListener';
|
|
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.
|
|
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
|
const registrationName =
|
|
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
|
|
return getListener(inst, registrationName);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
const path = [];
|
|
while (inst) {
|
|
path.push(inst);
|
|
inst = getParent(inst);
|
|
}
|
|
let i;
|
|
for (i = path.length; i-- > 0; ) {
|
|
fn(path[i], 'captured', arg);
|
|
}
|
|
for (i = 0; i < path.length; i++) {
|
|
fn(path[i], 'bubbled', arg);
|
|
}
|
|
}
|
|
|
|
function accumulateTwoPhaseDispatchesSingle(event) {
|
|
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
|
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
|
|
}
|
|
}
|
|
|
|
function accumulateTwoPhaseDispatches(events) {
|
|
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
|
|
}
|
|
|
|
/**
|
|
* 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> | Object)) {
|
|
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
|
}
|
|
|
|
// End of inline
|
|
type PropagationPhases = 'bubbled' | 'captured';
|
|
|
|
const ReactNativeBridgeEventPlugin = {
|
|
eventTypes: {},
|
|
|
|
extractEvents: function(
|
|
topLevelType: TopLevelType,
|
|
targetInst: null | Object,
|
|
nativeEvent: AnyNativeEvent,
|
|
nativeEventTarget: null | Object,
|
|
eventSystemFlags: EventSystemFlags,
|
|
): ?Object {
|
|
if (targetInst == null) {
|
|
// Probably a node belonging to another renderer's tree.
|
|
return null;
|
|
}
|
|
const bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
|
|
const directDispatchConfig = customDirectEventTypes[topLevelType];
|
|
invariant(
|
|
bubbleDispatchConfig || directDispatchConfig,
|
|
'Unsupported top level event type "%s" dispatched',
|
|
topLevelType,
|
|
);
|
|
const event = SyntheticEvent.getPooled(
|
|
bubbleDispatchConfig || directDispatchConfig,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
);
|
|
if (bubbleDispatchConfig) {
|
|
accumulateTwoPhaseDispatches(event);
|
|
} else if (directDispatchConfig) {
|
|
accumulateDirectDispatches(event);
|
|
} else {
|
|
return null;
|
|
}
|
|
return event;
|
|
},
|
|
};
|
|
|
|
export default ReactNativeBridgeEventPlugin;
|