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 {AnyNativeEvent} from '../PluginModuleType';
    
  11. import type {DOMEventName} from '../DOMEventNames';
    
  12. import type {DispatchQueue} from '../DOMPluginEventSystem';
    
  13. import type {EventSystemFlags} from '../EventSystemFlags';
    
  14. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  15. import type {KnownReactSyntheticEvent} from '../ReactSyntheticEventType';
    
  16. 
    
  17. import {registerDirectEvent} from '../EventRegistry';
    
  18. import {isReplayingEvent} from '../CurrentReplayingEvent';
    
  19. import {SyntheticMouseEvent, SyntheticPointerEvent} from '../SyntheticEvent';
    
  20. import {
    
  21.   getClosestInstanceFromNode,
    
  22.   getNodeFromInstance,
    
  23.   isContainerMarkedAsRoot,
    
  24. } from '../../client/ReactDOMComponentTree';
    
  25. import {accumulateEnterLeaveTwoPhaseListeners} from '../DOMPluginEventSystem';
    
  26. 
    
  27. import {enableHostSingletons} from 'shared/ReactFeatureFlags';
    
  28. import {
    
  29.   HostComponent,
    
  30.   HostSingleton,
    
  31.   HostText,
    
  32. } from 'react-reconciler/src/ReactWorkTags';
    
  33. import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
    
  34. 
    
  35. function registerEvents() {
    
  36.   registerDirectEvent('onMouseEnter', ['mouseout', 'mouseover']);
    
  37.   registerDirectEvent('onMouseLeave', ['mouseout', 'mouseover']);
    
  38.   registerDirectEvent('onPointerEnter', ['pointerout', 'pointerover']);
    
  39.   registerDirectEvent('onPointerLeave', ['pointerout', 'pointerover']);
    
  40. }
    
  41. 
    
  42. /**
    
  43.  * For almost every interaction we care about, there will be both a top-level
    
  44.  * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
    
  45.  * we do not extract duplicate events. However, moving the mouse into the
    
  46.  * browser from outside will not fire a `mouseout` event. In this case, we use
    
  47.  * the `mouseover` top-level event.
    
  48.  */
    
  49. function extractEvents(
    
  50.   dispatchQueue: DispatchQueue,
    
  51.   domEventName: DOMEventName,
    
  52.   targetInst: null | Fiber,
    
  53.   nativeEvent: AnyNativeEvent,
    
  54.   nativeEventTarget: null | EventTarget,
    
  55.   eventSystemFlags: EventSystemFlags,
    
  56.   targetContainer: EventTarget,
    
  57. ) {
    
  58.   const isOverEvent =
    
  59.     domEventName === 'mouseover' || domEventName === 'pointerover';
    
  60.   const isOutEvent =
    
  61.     domEventName === 'mouseout' || domEventName === 'pointerout';
    
  62. 
    
  63.   if (isOverEvent && !isReplayingEvent(nativeEvent)) {
    
  64.     // If this is an over event with a target, we might have already dispatched
    
  65.     // the event in the out event of the other target. If this is replayed,
    
  66.     // then it's because we couldn't dispatch against this target previously
    
  67.     // so we have to do it now instead.
    
  68.     const related =
    
  69.       (nativeEvent: any).relatedTarget || (nativeEvent: any).fromElement;
    
  70.     if (related) {
    
  71.       // If the related node is managed by React, we can assume that we have
    
  72.       // already dispatched the corresponding events during its mouseout.
    
  73.       if (
    
  74.         getClosestInstanceFromNode(related) ||
    
  75.         isContainerMarkedAsRoot(related)
    
  76.       ) {
    
  77.         return;
    
  78.       }
    
  79.     }
    
  80.   }
    
  81. 
    
  82.   if (!isOutEvent && !isOverEvent) {
    
  83.     // Must not be a mouse or pointer in or out - ignoring.
    
  84.     return;
    
  85.   }
    
  86. 
    
  87.   let win;
    
  88.   // TODO: why is this nullable in the types but we read from it?
    
  89.   if ((nativeEventTarget: any).window === nativeEventTarget) {
    
  90.     // `nativeEventTarget` is probably a window object.
    
  91.     win = nativeEventTarget;
    
  92.   } else {
    
  93.     // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
    
  94.     const doc = (nativeEventTarget: any).ownerDocument;
    
  95.     if (doc) {
    
  96.       win = doc.defaultView || doc.parentWindow;
    
  97.     } else {
    
  98.       win = window;
    
  99.     }
    
  100.   }
    
  101. 
    
  102.   let from;
    
  103.   let to;
    
  104.   if (isOutEvent) {
    
  105.     const related = nativeEvent.relatedTarget || (nativeEvent: any).toElement;
    
  106.     from = targetInst;
    
  107.     to = related ? getClosestInstanceFromNode((related: any)) : null;
    
  108.     if (to !== null) {
    
  109.       const nearestMounted = getNearestMountedFiber(to);
    
  110.       const tag = to.tag;
    
  111.       if (
    
  112.         to !== nearestMounted ||
    
  113.         (tag !== HostComponent &&
    
  114.           (!enableHostSingletons ? true : tag !== HostSingleton) &&
    
  115.           tag !== HostText)
    
  116.       ) {
    
  117.         to = null;
    
  118.       }
    
  119.     }
    
  120.   } else {
    
  121.     // Moving to a node from outside the window.
    
  122.     from = null;
    
  123.     to = targetInst;
    
  124.   }
    
  125. 
    
  126.   if (from === to) {
    
  127.     // Nothing pertains to our managed components.
    
  128.     return;
    
  129.   }
    
  130. 
    
  131.   let SyntheticEventCtor = SyntheticMouseEvent;
    
  132.   let leaveEventType = 'onMouseLeave';
    
  133.   let enterEventType = 'onMouseEnter';
    
  134.   let eventTypePrefix = 'mouse';
    
  135.   if (domEventName === 'pointerout' || domEventName === 'pointerover') {
    
  136.     SyntheticEventCtor = SyntheticPointerEvent;
    
  137.     leaveEventType = 'onPointerLeave';
    
  138.     enterEventType = 'onPointerEnter';
    
  139.     eventTypePrefix = 'pointer';
    
  140.   }
    
  141. 
    
  142.   const fromNode = from == null ? win : getNodeFromInstance(from);
    
  143.   const toNode = to == null ? win : getNodeFromInstance(to);
    
  144. 
    
  145.   const leave: KnownReactSyntheticEvent = new SyntheticEventCtor(
    
  146.     leaveEventType,
    
  147.     eventTypePrefix + 'leave',
    
  148.     from,
    
  149.     nativeEvent,
    
  150.     nativeEventTarget,
    
  151.   );
    
  152.   leave.target = fromNode;
    
  153.   leave.relatedTarget = toNode;
    
  154. 
    
  155.   let enter: KnownReactSyntheticEvent | null = null;
    
  156. 
    
  157.   // We should only process this nativeEvent if we are processing
    
  158.   // the first ancestor. Next time, we will ignore the event.
    
  159.   const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any));
    
  160.   if (nativeTargetInst === targetInst) {
    
  161.     const enterEvent: KnownReactSyntheticEvent = new SyntheticEventCtor(
    
  162.       enterEventType,
    
  163.       eventTypePrefix + 'enter',
    
  164.       to,
    
  165.       nativeEvent,
    
  166.       nativeEventTarget,
    
  167.     );
    
  168.     enterEvent.target = toNode;
    
  169.     enterEvent.relatedTarget = fromNode;
    
  170.     enter = enterEvent;
    
  171.   }
    
  172. 
    
  173.   accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
    
  174. }
    
  175. 
    
  176. export {registerEvents, extractEvents};