mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
675 lines
21 KiB
JavaScript
675 lines
21 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 {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
|
||
import type {
|
||
ElementListenerMap,
|
||
ElementListenerMapEntry,
|
||
} from '../events/DOMEventListenerMap';
|
||
import type {EventSystemFlags} from './EventSystemFlags';
|
||
import type {EventPriority, ReactScopeMethods} from 'shared/ReactTypes';
|
||
import type {Fiber} from 'react-reconciler/src/ReactFiber.old';
|
||
import type {PluginModule} from 'legacy-events/PluginModuleType';
|
||
import type {
|
||
ReactSyntheticEvent,
|
||
CustomDispatchConfig,
|
||
} from 'legacy-events/ReactSyntheticEventType';
|
||
import type {ReactDOMListener} from '../shared/ReactDOMTypes';
|
||
|
||
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
|
||
import {plugins} from 'legacy-events/EventPluginRegistry';
|
||
import {
|
||
PLUGIN_EVENT_SYSTEM,
|
||
LEGACY_FB_SUPPORT,
|
||
IS_REPLAYED,
|
||
IS_TARGET_PHASE_ONLY,
|
||
USE_EVENT_SYSTEM,
|
||
} from './EventSystemFlags';
|
||
|
||
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
|
||
|
||
import {
|
||
addTrappedEventListener,
|
||
removeTrappedEventListener,
|
||
} from './ReactDOMEventListener';
|
||
import getEventTarget from './getEventTarget';
|
||
import {getListenerMapForElement} from './DOMEventListenerMap';
|
||
import {
|
||
TOP_FOCUS,
|
||
TOP_LOAD,
|
||
TOP_ABORT,
|
||
TOP_CANCEL,
|
||
TOP_INVALID,
|
||
TOP_BLUR,
|
||
TOP_SCROLL,
|
||
TOP_CLOSE,
|
||
TOP_RESET,
|
||
TOP_SUBMIT,
|
||
TOP_CAN_PLAY,
|
||
TOP_CAN_PLAY_THROUGH,
|
||
TOP_DURATION_CHANGE,
|
||
TOP_EMPTIED,
|
||
TOP_ENCRYPTED,
|
||
TOP_ENDED,
|
||
TOP_ERROR,
|
||
TOP_WAITING,
|
||
TOP_VOLUME_CHANGE,
|
||
TOP_TIME_UPDATE,
|
||
TOP_SUSPEND,
|
||
TOP_STALLED,
|
||
TOP_SEEKING,
|
||
TOP_SEEKED,
|
||
TOP_PLAY,
|
||
TOP_PAUSE,
|
||
TOP_LOAD_START,
|
||
TOP_LOADED_DATA,
|
||
TOP_LOADED_METADATA,
|
||
TOP_RATE_CHANGE,
|
||
TOP_PROGRESS,
|
||
TOP_PLAYING,
|
||
TOP_CLICK,
|
||
TOP_BEFORE_BLUR,
|
||
TOP_AFTER_BLUR,
|
||
} from './DOMTopLevelEventTypes';
|
||
import {
|
||
getClosestInstanceFromNode,
|
||
getListenersFromTarget,
|
||
initListenersSet,
|
||
} from '../client/ReactDOMComponentTree';
|
||
import {COMMENT_NODE} from '../shared/HTMLNodeType';
|
||
import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
|
||
import {batchedEventUpdates} from './ReactDOMUpdateBatching';
|
||
|
||
import {
|
||
enableLegacyFBSupport,
|
||
enableUseEventAPI,
|
||
} from 'shared/ReactFeatureFlags';
|
||
import {
|
||
invokeGuardedCallbackAndCatchFirstError,
|
||
rethrowCaughtError,
|
||
} from 'shared/ReactErrorUtils';
|
||
|
||
const capturePhaseEvents = new Set([
|
||
TOP_FOCUS,
|
||
TOP_BLUR,
|
||
TOP_SCROLL,
|
||
TOP_LOAD,
|
||
TOP_ABORT,
|
||
TOP_CANCEL,
|
||
TOP_CLOSE,
|
||
TOP_INVALID,
|
||
TOP_RESET,
|
||
TOP_SUBMIT,
|
||
TOP_ABORT,
|
||
TOP_CAN_PLAY,
|
||
TOP_CAN_PLAY_THROUGH,
|
||
TOP_DURATION_CHANGE,
|
||
TOP_EMPTIED,
|
||
TOP_ENCRYPTED,
|
||
TOP_ENDED,
|
||
TOP_ERROR,
|
||
TOP_LOADED_DATA,
|
||
TOP_LOADED_METADATA,
|
||
TOP_LOAD_START,
|
||
TOP_PAUSE,
|
||
TOP_PLAY,
|
||
TOP_PLAYING,
|
||
TOP_PROGRESS,
|
||
TOP_RATE_CHANGE,
|
||
TOP_SEEKED,
|
||
TOP_SEEKING,
|
||
TOP_STALLED,
|
||
TOP_SUSPEND,
|
||
TOP_TIME_UPDATE,
|
||
TOP_VOLUME_CHANGE,
|
||
TOP_WAITING,
|
||
]);
|
||
|
||
if (enableUseEventAPI) {
|
||
capturePhaseEvents.add(TOP_BEFORE_BLUR);
|
||
capturePhaseEvents.add(TOP_AFTER_BLUR);
|
||
}
|
||
|
||
const emptyDispatchConfigForCustomEvents: CustomDispatchConfig = {
|
||
customEvent: true,
|
||
phasedRegistrationNames: {
|
||
bubbled: null,
|
||
captured: null,
|
||
},
|
||
};
|
||
|
||
const isArray = Array.isArray;
|
||
|
||
// TODO: we should remove the FlowFixMes and the casting to figure out how to make
|
||
// these patterns work properly.
|
||
// $FlowFixMe: Flow struggles with this pattern, so we also have to cast it.
|
||
const PossiblyWeakMap = ((typeof WeakMap === 'function' ? WeakMap : Map): any);
|
||
|
||
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
|
||
export const eventTargetEventListenerStore: WeakMap<
|
||
EventTarget,
|
||
Map<
|
||
DOMTopLevelEventType,
|
||
{bubbled: Set<ReactDOMListener>, captured: Set<ReactDOMListener>},
|
||
>,
|
||
> = new PossiblyWeakMap();
|
||
|
||
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
|
||
export const reactScopeListenerStore: WeakMap<
|
||
ReactScopeMethods,
|
||
Map<
|
||
DOMTopLevelEventType,
|
||
{bubbled: Set<ReactDOMListener>, captured: Set<ReactDOMListener>},
|
||
>,
|
||
> = new PossiblyWeakMap();
|
||
|
||
function executeDispatch(
|
||
event: ReactSyntheticEvent,
|
||
listener: Function,
|
||
currentTarget: EventTarget,
|
||
): void {
|
||
const type = event.type || 'unknown-event';
|
||
event.currentTarget = currentTarget;
|
||
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
|
||
event.currentTarget = null;
|
||
}
|
||
|
||
function executeDispatchesInOrder(event: ReactSyntheticEvent): void {
|
||
// TODO we should remove _dispatchListeners and _dispatchInstances at some point.
|
||
const dispatchListeners = event._dispatchListeners;
|
||
const dispatchInstances = event._dispatchInstances;
|
||
const dispatchCurrentTargets = event._dispatchCurrentTargets;
|
||
let previousInstance;
|
||
|
||
if (
|
||
dispatchListeners !== null &&
|
||
dispatchInstances !== null &&
|
||
dispatchCurrentTargets !== null
|
||
) {
|
||
for (let i = 0; i < dispatchListeners.length; i++) {
|
||
const instance = dispatchInstances[i];
|
||
const listener = dispatchListeners[i];
|
||
const currentTarget = dispatchCurrentTargets[i];
|
||
|
||
// We check if the instance was the same as the last one,
|
||
// if it was, then we're still on the same instance thus
|
||
// propagation should not stop. If we add support for
|
||
// stopImmediatePropagation at some point, then we'll
|
||
// need to handle that case here differently.
|
||
if (instance !== previousInstance && event.isPropagationStopped()) {
|
||
break;
|
||
}
|
||
// Listeners and Instances are two parallel arrays that are always in sync.
|
||
executeDispatch(event, listener, currentTarget);
|
||
previousInstance = instance;
|
||
}
|
||
}
|
||
event._dispatchListeners = null;
|
||
event._dispatchInstances = null;
|
||
event._dispatchCurrentTargets = null;
|
||
}
|
||
|
||
export function dispatchEventsInBatch(
|
||
events: Array<ReactSyntheticEvent>,
|
||
): void {
|
||
for (let i = 0; i < events.length; i++) {
|
||
const syntheticEvent = events[i];
|
||
executeDispatchesInOrder(syntheticEvent);
|
||
// Release the event from the pool if needed
|
||
if (!syntheticEvent.isPersistent()) {
|
||
syntheticEvent.constructor.release(syntheticEvent);
|
||
}
|
||
}
|
||
// This would be a good time to rethrow if any of the event handlers threw.
|
||
rethrowCaughtError();
|
||
}
|
||
|
||
function dispatchEventsForPlugins(
|
||
topLevelType: DOMTopLevelEventType,
|
||
eventSystemFlags: EventSystemFlags,
|
||
nativeEvent: AnyNativeEvent,
|
||
targetInst: null | Fiber,
|
||
targetContainer: EventTarget,
|
||
): void {
|
||
const nativeEventTarget = getEventTarget(nativeEvent);
|
||
const syntheticEvents: Array<ReactSyntheticEvent> = [];
|
||
|
||
for (let i = 0; i < plugins.length; i++) {
|
||
const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
|
||
if (possiblePlugin !== undefined) {
|
||
const extractedEvents = possiblePlugin.extractEvents(
|
||
topLevelType,
|
||
targetInst,
|
||
nativeEvent,
|
||
nativeEventTarget,
|
||
eventSystemFlags,
|
||
targetContainer,
|
||
);
|
||
if (isArray(extractedEvents)) {
|
||
// Flow complains about @@iterator being missing in ReactSyntheticEvent,
|
||
// so we cast to avoid the Flow error.
|
||
const arrOfExtractedEvents = ((extractedEvents: any): Array<ReactSyntheticEvent>);
|
||
syntheticEvents.push(...arrOfExtractedEvents);
|
||
} else if (extractedEvents != null) {
|
||
syntheticEvents.push(extractedEvents);
|
||
}
|
||
}
|
||
}
|
||
dispatchEventsInBatch(syntheticEvents);
|
||
}
|
||
|
||
function shouldUpgradeListener(
|
||
listenerEntry: void | ElementListenerMapEntry,
|
||
passive: void | boolean,
|
||
): boolean {
|
||
if (listenerEntry === undefined) {
|
||
return false;
|
||
}
|
||
// Upgrade from passive to active.
|
||
if (passive !== true && listenerEntry.passive) {
|
||
return true;
|
||
}
|
||
// Upgrade from default-active (browser default) to active.
|
||
if (passive === false && listenerEntry.passive === undefined) {
|
||
return true;
|
||
}
|
||
// Otherwise, do not upgrade
|
||
return false;
|
||
}
|
||
|
||
export function listenToTopLevelEvent(
|
||
topLevelType: DOMTopLevelEventType,
|
||
targetContainer: EventTarget,
|
||
listenerMap: ElementListenerMap,
|
||
eventSystemFlags: EventSystemFlags,
|
||
passive?: boolean,
|
||
priority?: EventPriority,
|
||
capture?: boolean,
|
||
): void {
|
||
// If we explicitly define capture, then these are for EventTarget objects,
|
||
// rather than React managed DOM elements. So we need to ensure we separate
|
||
// capture and non-capture events. For React managed DOM nodes we only use
|
||
// one or the other, never both. Which one we use is determined by the the
|
||
// capturePhaseEvents Set (in this module) that defines if the event listener
|
||
// should use the capture phase – otherwise we always use the bubble phase.
|
||
// Finally, when we get to dispatching and accumulating event listeners, we
|
||
// check if the user wanted capture/bubble and emulate the behavior at that
|
||
// point (we call this accumulating two phase listeners).
|
||
const typeStr = ((topLevelType: any): string);
|
||
const listenerMapKey =
|
||
capture === undefined
|
||
? topLevelType
|
||
: `${typeStr}_${capture ? 'capture' : 'bubble'}`;
|
||
const listenerEntry = listenerMap.get(listenerMapKey);
|
||
const shouldUpgrade = shouldUpgradeListener(listenerEntry, passive);
|
||
if (listenerEntry === undefined || shouldUpgrade) {
|
||
const isCapturePhase =
|
||
capture === undefined ? capturePhaseEvents.has(topLevelType) : capture;
|
||
// If we should upgrade, then we need to remove the existing trapped
|
||
// event listener for the target container.
|
||
if (shouldUpgrade) {
|
||
removeTrappedEventListener(
|
||
targetContainer,
|
||
topLevelType,
|
||
isCapturePhase,
|
||
((listenerEntry: any): ElementListenerMapEntry).listener,
|
||
);
|
||
}
|
||
const listener = addTrappedEventListener(
|
||
targetContainer,
|
||
topLevelType,
|
||
eventSystemFlags,
|
||
isCapturePhase,
|
||
false,
|
||
passive,
|
||
priority,
|
||
);
|
||
listenerMap.set(listenerMapKey, {passive, listener});
|
||
}
|
||
}
|
||
|
||
export function listenToEvent(
|
||
registrationName: string,
|
||
rootContainerElement: Element,
|
||
): void {
|
||
const listenerMap = getListenerMapForElement(rootContainerElement);
|
||
const dependencies = registrationNameDependencies[registrationName];
|
||
|
||
for (let i = 0; i < dependencies.length; i++) {
|
||
const dependency = dependencies[i];
|
||
listenToTopLevelEvent(
|
||
dependency,
|
||
rootContainerElement,
|
||
listenerMap,
|
||
PLUGIN_EVENT_SYSTEM,
|
||
);
|
||
}
|
||
}
|
||
|
||
function willDeferLaterForLegacyFBSupport(
|
||
topLevelType: DOMTopLevelEventType,
|
||
targetContainer: EventTarget,
|
||
): boolean {
|
||
if (topLevelType !== TOP_CLICK) {
|
||
return false;
|
||
}
|
||
// We defer all click events with legacy FB support mode on.
|
||
// This means we add a one time event listener to trigger
|
||
// after the FB delegated listeners fire.
|
||
const isDeferredListenerForLegacyFBSupport = true;
|
||
addTrappedEventListener(
|
||
targetContainer,
|
||
topLevelType,
|
||
PLUGIN_EVENT_SYSTEM | LEGACY_FB_SUPPORT,
|
||
false,
|
||
isDeferredListenerForLegacyFBSupport,
|
||
);
|
||
return true;
|
||
}
|
||
|
||
function isMatchingRootContainer(
|
||
grandContainer: Element,
|
||
targetContainer: EventTarget,
|
||
): boolean {
|
||
return (
|
||
grandContainer === targetContainer ||
|
||
(grandContainer.nodeType === COMMENT_NODE &&
|
||
grandContainer.parentNode === targetContainer)
|
||
);
|
||
}
|
||
|
||
export function isManagedDOMElement(
|
||
target: EventTarget | ReactScopeMethods,
|
||
): boolean {
|
||
return getClosestInstanceFromNode(((target: any): Node)) !== null;
|
||
}
|
||
|
||
export function isValidEventTarget(
|
||
target: EventTarget | ReactScopeMethods,
|
||
): boolean {
|
||
return typeof (target: Object).addEventListener === 'function';
|
||
}
|
||
|
||
export function isReactScope(target: EventTarget | ReactScopeMethods): boolean {
|
||
return typeof (target: Object).getChildContextValues === 'function';
|
||
}
|
||
|
||
export function dispatchEventForPluginEventSystem(
|
||
topLevelType: DOMTopLevelEventType,
|
||
eventSystemFlags: EventSystemFlags,
|
||
nativeEvent: AnyNativeEvent,
|
||
targetInst: null | Fiber,
|
||
targetContainer: EventTarget,
|
||
): void {
|
||
let ancestorInst = targetInst;
|
||
if (eventSystemFlags & IS_TARGET_PHASE_ONLY) {
|
||
// For TargetEvent nodes (i.e. document, window)
|
||
ancestorInst = null;
|
||
} else {
|
||
const targetContainerNode = ((targetContainer: any): Node);
|
||
|
||
// If we are using the legacy FB support flag, we
|
||
// defer the event to the null with a one
|
||
// time event listener so we can defer the event.
|
||
if (
|
||
enableLegacyFBSupport &&
|
||
// We do not want to defer if the event system has already been
|
||
// set to LEGACY_FB_SUPPORT. LEGACY_FB_SUPPORT only gets set when
|
||
// we call willDeferLaterForLegacyFBSupport, thus not bailing out
|
||
// will result in endless cycles like an infinite loop.
|
||
(eventSystemFlags & LEGACY_FB_SUPPORT) === 0 &&
|
||
// We also don't want to defer during event replaying.
|
||
(eventSystemFlags & IS_REPLAYED) === 0 &&
|
||
// We don't want to apply the legacy FB support for the useEvent API.
|
||
(eventSystemFlags & USE_EVENT_SYSTEM) === 0 &&
|
||
willDeferLaterForLegacyFBSupport(topLevelType, targetContainer)
|
||
) {
|
||
return;
|
||
}
|
||
if (targetInst !== null) {
|
||
// The below logic attempts to work out if we need to change
|
||
// the target fiber to a different ancestor. We had similar logic
|
||
// in the legacy event system, except the big difference between
|
||
// systems is that the modern event system now has an event listener
|
||
// attached to each React Root and React Portal Root. Together,
|
||
// the DOM nodes representing these roots are the "rootContainer".
|
||
// To figure out which ancestor instance we should use, we traverse
|
||
// up the fiber tree from the target instance and attempt to find
|
||
// root boundaries that match that of our current "rootContainer".
|
||
// If we find that "rootContainer", we find the parent fiber
|
||
// sub-tree for that root and make that our ancestor instance.
|
||
let node = targetInst;
|
||
|
||
while (true) {
|
||
if (node === null) {
|
||
return;
|
||
}
|
||
if (node.tag === HostRoot || node.tag === HostPortal) {
|
||
const container = node.stateNode.containerInfo;
|
||
if (isMatchingRootContainer(container, targetContainerNode)) {
|
||
break;
|
||
}
|
||
if (node.tag === HostPortal) {
|
||
// The target is a portal, but it's not the rootContainer we're looking for.
|
||
// Normally portals handle their own events all the way down to the root.
|
||
// So we should be able to stop now. However, we don't know if this portal
|
||
// was part of *our* root.
|
||
let grandNode = node.return;
|
||
while (grandNode !== null) {
|
||
if (grandNode.tag === HostRoot || grandNode.tag === HostPortal) {
|
||
const grandContainer = grandNode.stateNode.containerInfo;
|
||
if (
|
||
isMatchingRootContainer(grandContainer, targetContainerNode)
|
||
) {
|
||
// This is the rootContainer we're looking for and we found it as
|
||
// a parent of the Portal. That means we can ignore it because the
|
||
// Portal will bubble through to us.
|
||
return;
|
||
}
|
||
}
|
||
grandNode = grandNode.return;
|
||
}
|
||
}
|
||
const parentSubtreeInst = getClosestInstanceFromNode(container);
|
||
if (parentSubtreeInst === null) {
|
||
return;
|
||
}
|
||
node = ancestorInst = parentSubtreeInst;
|
||
continue;
|
||
}
|
||
node = node.return;
|
||
}
|
||
}
|
||
}
|
||
|
||
batchedEventUpdates(() =>
|
||
dispatchEventsForPlugins(
|
||
topLevelType,
|
||
eventSystemFlags,
|
||
nativeEvent,
|
||
ancestorInst,
|
||
targetContainer,
|
||
),
|
||
);
|
||
}
|
||
|
||
function getNearestRootOrPortalContainer(instance: Element): Element {
|
||
let node = getClosestInstanceFromNode(instance);
|
||
while (node !== null) {
|
||
const tag = node.tag;
|
||
// Once we encounter a host container or root container
|
||
// we can return their DOM instance.
|
||
if (tag === HostRoot || tag === HostPortal) {
|
||
return node.stateNode.containerInfo;
|
||
}
|
||
node = node.return;
|
||
}
|
||
return instance;
|
||
}
|
||
|
||
function addEventTypeToDispatchConfig(type: DOMTopLevelEventType): void {
|
||
const dispatchConfig = topLevelEventsToDispatchConfig.get(type);
|
||
// If we don't have a dispatchConfig, then we're dealing with
|
||
// an event type that React does not know about (i.e. a custom event).
|
||
// We need to register an event config for this or the SimpleEventPlugin
|
||
// will not appropriately provide a SyntheticEvent, so we use out empty
|
||
// dispatch config for custom events.
|
||
if (dispatchConfig === undefined) {
|
||
topLevelEventsToDispatchConfig.set(
|
||
type,
|
||
emptyDispatchConfigForCustomEvents,
|
||
);
|
||
}
|
||
}
|
||
|
||
export function attachListenerToManagedDOMElement(
|
||
listener: ReactDOMListener,
|
||
): void {
|
||
const {event, target} = listener;
|
||
const {passive, priority, type} = event;
|
||
|
||
const managedTargetElement = ((target: any): Element);
|
||
const containerEventTarget = getNearestRootOrPortalContainer(
|
||
managedTargetElement,
|
||
);
|
||
const listenerMap = getListenerMapForElement(containerEventTarget);
|
||
// Add the event listener to the target container (falling back to
|
||
// the target if we didn't find one).
|
||
listenToTopLevelEvent(
|
||
type,
|
||
containerEventTarget,
|
||
listenerMap,
|
||
PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM,
|
||
passive,
|
||
priority,
|
||
);
|
||
// Get the internal listeners Set from the target instance.
|
||
let listeners = getListenersFromTarget(managedTargetElement);
|
||
// If we don't have any listeners, then we need to init them.
|
||
if (listeners === null) {
|
||
listeners = new Set();
|
||
initListenersSet(managedTargetElement, listeners);
|
||
}
|
||
// Add our listener to the listeners Set.
|
||
listeners.add(listener);
|
||
// Finally, add the event to our known event types list.
|
||
addEventTypeToDispatchConfig(type);
|
||
}
|
||
|
||
export function detachListenerFromManagedDOMElement(
|
||
listener: ReactDOMListener,
|
||
): void {
|
||
const {target} = listener;
|
||
const managedTargetElement = ((target: any): Element);
|
||
// Get the internal listeners Set from the target instance.
|
||
const listeners = getListenersFromTarget(managedTargetElement);
|
||
if (listeners !== null) {
|
||
// Remove out listener from the listeners Set.
|
||
listeners.delete(listener);
|
||
}
|
||
}
|
||
|
||
export function attachTargetEventListener(listener: ReactDOMListener): void {
|
||
const {event, target} = listener;
|
||
const {capture, passive, priority, type} = event;
|
||
const eventTarget = ((target: any): EventTarget);
|
||
const listenerMap = getListenerMapForElement(eventTarget);
|
||
// Add the event listener to the TargetEvent object.
|
||
listenToTopLevelEvent(
|
||
type,
|
||
eventTarget,
|
||
listenerMap,
|
||
PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
|
||
passive,
|
||
priority,
|
||
capture,
|
||
);
|
||
let eventTypeMap = eventTargetEventListenerStore.get(eventTarget);
|
||
if (eventTypeMap === undefined) {
|
||
eventTypeMap = new Map();
|
||
eventTargetEventListenerStore.set(eventTarget, eventTypeMap);
|
||
}
|
||
// Get the listeners by the event type
|
||
let listeners = eventTypeMap.get(type);
|
||
if (listeners === undefined) {
|
||
listeners = {captured: new Set(), bubbled: new Set()};
|
||
eventTypeMap.set(type, listeners);
|
||
}
|
||
// Add our listener to the listeners Set.
|
||
if (capture) {
|
||
listeners.captured.add(listener);
|
||
} else {
|
||
listeners.bubbled.add(listener);
|
||
}
|
||
// Finally, add the event to our known event types list.
|
||
addEventTypeToDispatchConfig(type);
|
||
}
|
||
|
||
export function detachTargetEventListener(listener: ReactDOMListener): void {
|
||
const {event, target} = listener;
|
||
const {capture, type} = event;
|
||
const validEventTarget = ((target: any): EventTarget);
|
||
const eventTypeMap = eventTargetEventListenerStore.get(validEventTarget);
|
||
if (eventTypeMap !== undefined) {
|
||
const listeners = eventTypeMap.get(type);
|
||
if (listeners !== undefined) {
|
||
// Remove out listener from the listeners Set.
|
||
if (capture) {
|
||
listeners.captured.delete(listener);
|
||
} else {
|
||
listeners.bubbled.delete(listener);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
export function attachListenerToReactScope(listener: ReactDOMListener): void {
|
||
const {event, target} = listener;
|
||
const {capture, type} = event;
|
||
const reactScope = ((target: any): ReactScopeMethods);
|
||
let eventTypeMap = reactScopeListenerStore.get(reactScope);
|
||
if (eventTypeMap === undefined) {
|
||
eventTypeMap = new Map();
|
||
reactScopeListenerStore.set(reactScope, eventTypeMap);
|
||
}
|
||
// Get the listeners by the event type
|
||
let listeners = eventTypeMap.get(type);
|
||
if (listeners === undefined) {
|
||
listeners = {captured: new Set(), bubbled: new Set()};
|
||
eventTypeMap.set(type, listeners);
|
||
}
|
||
// Add our listener to the listeners Set.
|
||
if (capture) {
|
||
listeners.captured.add(listener);
|
||
} else {
|
||
listeners.bubbled.add(listener);
|
||
}
|
||
// Finally, add the event to our known event types list.
|
||
addEventTypeToDispatchConfig(type);
|
||
}
|
||
|
||
export function detachListenerFromReactScope(listener: ReactDOMListener): void {
|
||
const {event, target} = listener;
|
||
const {capture, type} = event;
|
||
const reactScope = ((target: any): ReactScopeMethods);
|
||
const eventTypeMap = reactScopeListenerStore.get(reactScope);
|
||
if (eventTypeMap !== undefined) {
|
||
const listeners = eventTypeMap.get(type);
|
||
if (listeners !== undefined) {
|
||
// Remove out listener from the listeners Set.
|
||
if (capture) {
|
||
listeners.captured.delete(listener);
|
||
} else {
|
||
listeners.bubbled.delete(listener);
|
||
}
|
||
}
|
||
}
|
||
}
|