1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {DOMEventName} from '../events/DOMEventNames';
    
  11. import type {ReactScopeInstance} from 'shared/ReactTypes';
    
  12. import type {
    
  13.   ReactDOMEventHandle,
    
  14.   ReactDOMEventHandleListener,
    
  15. } from './ReactDOMEventHandleTypes';
    
  16. 
    
  17. import {allNativeEvents} from '../events/EventRegistry';
    
  18. import {
    
  19.   getEventHandlerListeners,
    
  20.   setEventHandlerListeners,
    
  21.   doesTargetHaveEventHandle,
    
  22.   addEventHandleToTarget,
    
  23. } from './ReactDOMComponentTree';
    
  24. import {ELEMENT_NODE} from './HTMLNodeType';
    
  25. import {listenToNativeEventForNonManagedEventTarget} from '../events/DOMPluginEventSystem';
    
  26. 
    
  27. import {
    
  28.   enableScopeAPI,
    
  29.   enableCreateEventHandleAPI,
    
  30. } from 'shared/ReactFeatureFlags';
    
  31. 
    
  32. type EventHandleOptions = {
    
  33.   capture?: boolean,
    
  34. };
    
  35. 
    
  36. function isValidEventTarget(target: EventTarget | ReactScopeInstance): boolean {
    
  37.   return typeof (target: Object).addEventListener === 'function';
    
  38. }
    
  39. 
    
  40. function isReactScope(target: EventTarget | ReactScopeInstance): boolean {
    
  41.   return typeof (target: Object).getChildContextValues === 'function';
    
  42. }
    
  43. 
    
  44. function createEventHandleListener(
    
  45.   type: DOMEventName,
    
  46.   isCapturePhaseListener: boolean,
    
  47.   callback: (SyntheticEvent<EventTarget>) => void,
    
  48. ): ReactDOMEventHandleListener {
    
  49.   return {
    
  50.     callback,
    
  51.     capture: isCapturePhaseListener,
    
  52.     type,
    
  53.   };
    
  54. }
    
  55. 
    
  56. function registerReactDOMEvent(
    
  57.   target: EventTarget | ReactScopeInstance,
    
  58.   domEventName: DOMEventName,
    
  59.   isCapturePhaseListener: boolean,
    
  60. ): void {
    
  61.   if ((target: any).nodeType === ELEMENT_NODE) {
    
  62.     // Do nothing. We already attached all root listeners.
    
  63.   } else if (enableScopeAPI && isReactScope(target)) {
    
  64.     // Do nothing. We already attached all root listeners.
    
  65.   } else if (isValidEventTarget(target)) {
    
  66.     const eventTarget = ((target: any): EventTarget);
    
  67.     // These are valid event targets, but they are also
    
  68.     // non-managed React nodes.
    
  69.     listenToNativeEventForNonManagedEventTarget(
    
  70.       domEventName,
    
  71.       isCapturePhaseListener,
    
  72.       eventTarget,
    
  73.     );
    
  74.   } else {
    
  75.     throw new Error(
    
  76.       'ReactDOM.createEventHandle: setter called on an invalid ' +
    
  77.         'target. Provide a valid EventTarget or an element managed by React.',
    
  78.     );
    
  79.   }
    
  80. }
    
  81. 
    
  82. export function createEventHandle(
    
  83.   type: string,
    
  84.   options?: EventHandleOptions,
    
  85. ): ReactDOMEventHandle {
    
  86.   if (enableCreateEventHandleAPI) {
    
  87.     const domEventName = ((type: any): DOMEventName);
    
  88. 
    
  89.     // We cannot support arbitrary native events with eager root listeners
    
  90.     // because the eager strategy relies on knowing the whole list ahead of time.
    
  91.     // If we wanted to support this, we'd have to add code to keep track
    
  92.     // (or search) for all portal and root containers, and lazily add listeners
    
  93.     // to them whenever we see a previously unknown event. This seems like a lot
    
  94.     // of complexity for something we don't even have a particular use case for.
    
  95.     // Unfortunately, the downside of this invariant is that *removing* a native
    
  96.     // event from the list of known events has now become a breaking change for
    
  97.     // any code relying on the createEventHandle API.
    
  98.     if (!allNativeEvents.has(domEventName)) {
    
  99.       throw new Error(
    
  100.         `Cannot call unstable_createEventHandle with "${domEventName}", as it is not an event known to React.`,
    
  101.       );
    
  102.     }
    
  103. 
    
  104.     let isCapturePhaseListener = false;
    
  105.     if (options != null) {
    
  106.       const optionsCapture = options.capture;
    
  107.       if (typeof optionsCapture === 'boolean') {
    
  108.         isCapturePhaseListener = optionsCapture;
    
  109.       }
    
  110.     }
    
  111. 
    
  112.     const eventHandle: ReactDOMEventHandle = (
    
  113.       target: EventTarget | ReactScopeInstance,
    
  114.       callback: (SyntheticEvent<EventTarget>) => void,
    
  115.     ) => {
    
  116.       if (typeof callback !== 'function') {
    
  117.         throw new Error(
    
  118.           'ReactDOM.createEventHandle: setter called with an invalid ' +
    
  119.             'callback. The callback must be a function.',
    
  120.         );
    
  121.       }
    
  122. 
    
  123.       if (!doesTargetHaveEventHandle(target, eventHandle)) {
    
  124.         addEventHandleToTarget(target, eventHandle);
    
  125.         registerReactDOMEvent(target, domEventName, isCapturePhaseListener);
    
  126.       }
    
  127.       const listener = createEventHandleListener(
    
  128.         domEventName,
    
  129.         isCapturePhaseListener,
    
  130.         callback,
    
  131.       );
    
  132.       let targetListeners = getEventHandlerListeners(target);
    
  133.       if (targetListeners === null) {
    
  134.         targetListeners = new Set();
    
  135.         setEventHandlerListeners(target, targetListeners);
    
  136.       }
    
  137.       targetListeners.add(listener);
    
  138.       return () => {
    
  139.         ((targetListeners: any): Set<ReactDOMEventHandleListener>).delete(
    
  140.           listener,
    
  141.         );
    
  142.       };
    
  143.     };
    
  144. 
    
  145.     return eventHandle;
    
  146.   }
    
  147.   return (null: any);
    
  148. }