mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
249 lines
7.4 KiB
JavaScript
249 lines
7.4 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 {DOMTopLevelEventType} from '../events/TopLevelEventTypes';
|
|
import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
|
|
import type {
|
|
ReactDOMEventHandle,
|
|
ReactDOMEventHandleListener,
|
|
} from '../shared/ReactDOMTypes';
|
|
|
|
import {getEventPriorityForListenerSystem} from '../events/DOMEventProperties';
|
|
import {
|
|
getClosestInstanceFromNode,
|
|
getEventHandlerListeners,
|
|
setEventHandlerListeners,
|
|
getEventListenerMap,
|
|
getFiberFromScopeInstance,
|
|
} from './ReactDOMComponentTree';
|
|
import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
|
|
import {
|
|
listenToNativeEvent,
|
|
addEventTypeToDispatchConfig,
|
|
} from '../events/DOMModernPluginEventSystem';
|
|
|
|
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
|
|
import {
|
|
PLUGIN_EVENT_SYSTEM,
|
|
IS_EVENT_HANDLE_NON_MANAGED_NODE,
|
|
} from '../events/EventSystemFlags';
|
|
|
|
import {
|
|
enableScopeAPI,
|
|
enableCreateEventHandleAPI,
|
|
} from 'shared/ReactFeatureFlags';
|
|
import invariant from 'shared/invariant';
|
|
|
|
type EventHandleOptions = {|
|
|
capture?: boolean,
|
|
passive?: boolean,
|
|
priority?: EventPriority,
|
|
|};
|
|
|
|
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
|
|
|
|
function getNearestRootOrPortalContainer(node: Fiber): null | Element {
|
|
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 null;
|
|
}
|
|
|
|
function isValidEventTarget(target: EventTarget | ReactScopeInstance): boolean {
|
|
return typeof (target: Object).addEventListener === 'function';
|
|
}
|
|
|
|
function isReactScope(target: EventTarget | ReactScopeInstance): boolean {
|
|
return typeof (target: Object).getChildContextValues === 'function';
|
|
}
|
|
|
|
function createEventHandleListener(
|
|
type: DOMTopLevelEventType,
|
|
isCapturePhaseListener: boolean,
|
|
callback: (SyntheticEvent<EventTarget>) => void,
|
|
): ReactDOMEventHandleListener {
|
|
return {
|
|
callback,
|
|
capture: isCapturePhaseListener,
|
|
type,
|
|
};
|
|
}
|
|
|
|
function registerEventOnNearestTargetContainer(
|
|
targetFiber: Fiber,
|
|
topLevelType: DOMTopLevelEventType,
|
|
isPassiveListener: boolean | void,
|
|
listenerPriority: EventPriority | void,
|
|
isCapturePhaseListener: boolean,
|
|
): void {
|
|
// If it is, find the nearest root or portal and make it
|
|
// our event handle target container.
|
|
let targetContainer = getNearestRootOrPortalContainer(targetFiber);
|
|
if (targetContainer === null) {
|
|
invariant(
|
|
false,
|
|
'ReactDOM.createEventHandle: setListener called on an target ' +
|
|
'that did not have a corresponding root. This is likely a bug in React.',
|
|
);
|
|
}
|
|
if (targetContainer.nodeType === COMMENT_NODE) {
|
|
targetContainer = ((targetContainer.parentNode: any): Element);
|
|
}
|
|
const listenerMap = getEventListenerMap(targetContainer);
|
|
listenToNativeEvent(
|
|
topLevelType,
|
|
targetContainer,
|
|
listenerMap,
|
|
PLUGIN_EVENT_SYSTEM,
|
|
isCapturePhaseListener,
|
|
isPassiveListener,
|
|
listenerPriority,
|
|
);
|
|
}
|
|
|
|
function registerReactDOMEvent(
|
|
target: EventTarget | ReactScopeInstance,
|
|
topLevelType: DOMTopLevelEventType,
|
|
isPassiveListener: boolean | void,
|
|
isCapturePhaseListener: boolean,
|
|
listenerPriority: EventPriority | void,
|
|
): void {
|
|
// Check if the target is a DOM element.
|
|
if ((target: any).nodeType === ELEMENT_NODE) {
|
|
const targetElement = ((target: any): Element);
|
|
// Check if the DOM element is managed by React.
|
|
const targetFiber = getClosestInstanceFromNode(targetElement);
|
|
if (targetFiber === null) {
|
|
invariant(
|
|
false,
|
|
'ReactDOM.createEventHandle: setListener called on an element ' +
|
|
'target that is not managed by React. Ensure React rendered the DOM element.',
|
|
);
|
|
}
|
|
registerEventOnNearestTargetContainer(
|
|
targetFiber,
|
|
topLevelType,
|
|
isPassiveListener,
|
|
listenerPriority,
|
|
isCapturePhaseListener,
|
|
);
|
|
} else if (enableScopeAPI && isReactScope(target)) {
|
|
const scopeTarget = ((target: any): ReactScopeInstance);
|
|
const targetFiber = getFiberFromScopeInstance(scopeTarget);
|
|
if (targetFiber === null) {
|
|
// Scope is unmounted, do not proceed.
|
|
return;
|
|
}
|
|
registerEventOnNearestTargetContainer(
|
|
targetFiber,
|
|
topLevelType,
|
|
isPassiveListener,
|
|
listenerPriority,
|
|
isCapturePhaseListener,
|
|
);
|
|
} else if (isValidEventTarget(target)) {
|
|
const eventTarget = ((target: any): EventTarget);
|
|
const listenerMap = getEventListenerMap(eventTarget);
|
|
listenToNativeEvent(
|
|
topLevelType,
|
|
eventTarget,
|
|
listenerMap,
|
|
PLUGIN_EVENT_SYSTEM | IS_EVENT_HANDLE_NON_MANAGED_NODE,
|
|
isCapturePhaseListener,
|
|
isPassiveListener,
|
|
listenerPriority,
|
|
);
|
|
} else {
|
|
invariant(
|
|
false,
|
|
'ReactDOM.createEventHandle: setter called on an invalid ' +
|
|
'target. Provide a valid EventTarget or an element managed by React.',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function createEventHandle(
|
|
type: string,
|
|
options?: EventHandleOptions,
|
|
): ReactDOMEventHandle {
|
|
if (enableCreateEventHandleAPI) {
|
|
const topLevelType = ((type: any): DOMTopLevelEventType);
|
|
let isCapturePhaseListener = false;
|
|
let isPassiveListener = undefined; // Undefined means to use the browser default
|
|
let listenerPriority;
|
|
|
|
if (options != null) {
|
|
const optionsCapture = options.capture;
|
|
const optionsPassive = options.passive;
|
|
const optionsPriority = options.priority;
|
|
|
|
if (typeof optionsCapture === 'boolean') {
|
|
isCapturePhaseListener = optionsCapture;
|
|
}
|
|
if (typeof optionsPassive === 'boolean') {
|
|
isPassiveListener = optionsPassive;
|
|
}
|
|
if (typeof optionsPriority === 'number') {
|
|
listenerPriority = optionsPriority;
|
|
}
|
|
}
|
|
if (listenerPriority === undefined) {
|
|
listenerPriority = getEventPriorityForListenerSystem(topLevelType);
|
|
}
|
|
|
|
const registeredReactDOMEvents = new PossiblyWeakSet();
|
|
|
|
return (
|
|
target: EventTarget | ReactScopeInstance,
|
|
callback: (SyntheticEvent<EventTarget>) => void,
|
|
) => {
|
|
invariant(
|
|
typeof callback === 'function',
|
|
'ReactDOM.createEventHandle: setter called with an invalid ' +
|
|
'callback. The callback must be a function.',
|
|
);
|
|
if (!registeredReactDOMEvents.has(target)) {
|
|
registeredReactDOMEvents.add(target);
|
|
registerReactDOMEvent(
|
|
target,
|
|
topLevelType,
|
|
isPassiveListener,
|
|
isCapturePhaseListener,
|
|
listenerPriority,
|
|
);
|
|
// Add the event to our known event types list.
|
|
addEventTypeToDispatchConfig(topLevelType);
|
|
}
|
|
const listener = createEventHandleListener(
|
|
topLevelType,
|
|
isCapturePhaseListener,
|
|
callback,
|
|
);
|
|
let targetListeners = getEventHandlerListeners(target);
|
|
if (targetListeners === null) {
|
|
targetListeners = new Set();
|
|
setEventHandlerListeners(target, targetListeners);
|
|
}
|
|
targetListeners.add(listener);
|
|
return () => {
|
|
((targetListeners: any): Set<ReactDOMEventHandleListener>).delete(
|
|
listener,
|
|
);
|
|
};
|
|
};
|
|
}
|
|
return (null: any);
|
|
}
|