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.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. import {
    
  13.   buttonType,
    
  14.   buttonsType,
    
  15.   defaultPointerId,
    
  16.   defaultPointerSize,
    
  17.   defaultBrowserChromeSize,
    
  18. } from './constants';
    
  19. import * as domEvents from './domEvents';
    
  20. import {hasPointerEvent, platform} from './domEnvironment';
    
  21. import * as touchStore from './touchStore';
    
  22. 
    
  23. /**
    
  24.  * Converts a PointerEvent payload to a Touch
    
  25.  */
    
  26. function createTouch(target, payload) {
    
  27.   const {
    
  28.     height = defaultPointerSize,
    
  29.     pageX,
    
  30.     pageY,
    
  31.     pointerId,
    
  32.     pressure = 1,
    
  33.     twist = 0,
    
  34.     width = defaultPointerSize,
    
  35.     x = 0,
    
  36.     y = 0,
    
  37.   } = payload;
    
  38. 
    
  39.   return {
    
  40.     clientX: x,
    
  41.     clientY: y,
    
  42.     force: pressure,
    
  43.     identifier: pointerId,
    
  44.     pageX: pageX || x,
    
  45.     pageY: pageY || y,
    
  46.     radiusX: width / 2,
    
  47.     radiusY: height / 2,
    
  48.     rotationAngle: twist,
    
  49.     target,
    
  50.     screenX: x,
    
  51.     screenY: y + defaultBrowserChromeSize,
    
  52.   };
    
  53. }
    
  54. 
    
  55. /**
    
  56.  * Converts a PointerEvent to a TouchEvent
    
  57.  */
    
  58. function createTouchEventPayload(target, touch, payload) {
    
  59.   const {
    
  60.     altKey = false,
    
  61.     ctrlKey = false,
    
  62.     metaKey = false,
    
  63.     preventDefault,
    
  64.     shiftKey = false,
    
  65.     timeStamp,
    
  66.   } = payload;
    
  67. 
    
  68.   return {
    
  69.     altKey,
    
  70.     changedTouches: [touch],
    
  71.     ctrlKey,
    
  72.     metaKey,
    
  73.     preventDefault,
    
  74.     shiftKey,
    
  75.     targetTouches: touchStore.getTargetTouches(target),
    
  76.     timeStamp,
    
  77.     touches: touchStore.getTouches(),
    
  78.   };
    
  79. }
    
  80. 
    
  81. function getPointerType(payload) {
    
  82.   let pointerType = 'mouse';
    
  83.   if (payload != null && payload.pointerType != null) {
    
  84.     pointerType = payload.pointerType;
    
  85.   }
    
  86.   return pointerType;
    
  87. }
    
  88. 
    
  89. /**
    
  90.  * Pointer events sequences.
    
  91.  *
    
  92.  * Creates representative browser event sequences for high-level gestures based on pointers.
    
  93.  * This allows unit tests to be written in terms of simple pointer interactions while testing
    
  94.  * that the responses to those interactions account for the complex sequence of events that
    
  95.  * browsers produce as a result.
    
  96.  *
    
  97.  * Every time a new pointer touches the surface a 'touchstart' event should be dispatched.
    
  98.  * - 'changedTouches' contains the new touch.
    
  99.  * - 'targetTouches' contains all the active pointers for the target.
    
  100.  * - 'touches' contains all the active pointers on the surface.
    
  101.  *
    
  102.  * Every time an existing pointer moves a 'touchmove' event should be dispatched.
    
  103.  * - 'changedTouches' contains the updated touch.
    
  104.  *
    
  105.  * Every time an existing pointer leaves the surface a 'touchend' event should be dispatched.
    
  106.  * - 'changedTouches' contains the released touch.
    
  107.  * - 'targetTouches' contains any of the remaining active pointers for the target.
    
  108.  */
    
  109. 
    
  110. export function contextmenu(
    
  111.   target,
    
  112.   defaultPayload,
    
  113.   {pointerType = 'mouse', modified} = {},
    
  114. ) {
    
  115.   const dispatch = arg => target.dispatchEvent(arg);
    
  116. 
    
  117.   const payload = {
    
  118.     pointerId: defaultPointerId,
    
  119.     pointerType,
    
  120.     ...defaultPayload,
    
  121.   };
    
  122. 
    
  123.   const preventDefault = payload.preventDefault;
    
  124. 
    
  125.   if (pointerType === 'touch') {
    
  126.     if (hasPointerEvent()) {
    
  127.       dispatch(
    
  128.         domEvents.pointerdown({
    
  129.           ...payload,
    
  130.           button: buttonType.primary,
    
  131.           buttons: buttonsType.primary,
    
  132.         }),
    
  133.       );
    
  134.     }
    
  135.     const touch = createTouch(target, payload);
    
  136.     touchStore.addTouch(touch);
    
  137.     const touchEventPayload = createTouchEventPayload(target, touch, payload);
    
  138.     dispatch(domEvents.touchstart(touchEventPayload));
    
  139.     dispatch(
    
  140.       domEvents.contextmenu({
    
  141.         button: buttonType.primary,
    
  142.         buttons: buttonsType.none,
    
  143.         preventDefault,
    
  144.       }),
    
  145.     );
    
  146.     touchStore.removeTouch(touch);
    
  147.   } else if (pointerType === 'mouse') {
    
  148.     if (modified === true) {
    
  149.       const button = buttonType.primary;
    
  150.       const buttons = buttonsType.primary;
    
  151.       const ctrlKey = true;
    
  152.       if (hasPointerEvent()) {
    
  153.         dispatch(
    
  154.           domEvents.pointerdown({button, buttons, ctrlKey, pointerType}),
    
  155.         );
    
  156.       }
    
  157.       dispatch(domEvents.mousedown({button, buttons, ctrlKey}));
    
  158.       if (platform.get() === 'mac') {
    
  159.         dispatch(
    
  160.           domEvents.contextmenu({button, buttons, ctrlKey, preventDefault}),
    
  161.         );
    
  162.       }
    
  163.     } else {
    
  164.       const button = buttonType.secondary;
    
  165.       const buttons = buttonsType.secondary;
    
  166.       if (hasPointerEvent()) {
    
  167.         dispatch(domEvents.pointerdown({button, buttons, pointerType}));
    
  168.       }
    
  169.       dispatch(domEvents.mousedown({button, buttons}));
    
  170.       dispatch(domEvents.contextmenu({button, buttons, preventDefault}));
    
  171.     }
    
  172.   }
    
  173. }
    
  174. 
    
  175. export function pointercancel(target, defaultPayload) {
    
  176.   const dispatchEvent = arg => target.dispatchEvent(arg);
    
  177.   const pointerType = getPointerType(defaultPayload);
    
  178. 
    
  179.   const payload = {
    
  180.     pointerId: defaultPointerId,
    
  181.     pointerType,
    
  182.     ...defaultPayload,
    
  183.   };
    
  184. 
    
  185.   if (hasPointerEvent()) {
    
  186.     dispatchEvent(domEvents.pointercancel(payload));
    
  187.   } else {
    
  188.     if (pointerType === 'mouse') {
    
  189.       dispatchEvent(domEvents.dragstart(payload));
    
  190.     } else {
    
  191.       const touch = createTouch(target, payload);
    
  192.       touchStore.removeTouch(touch);
    
  193.       const touchEventPayload = createTouchEventPayload(target, touch, payload);
    
  194.       dispatchEvent(domEvents.touchcancel(touchEventPayload));
    
  195.     }
    
  196.   }
    
  197. }
    
  198. 
    
  199. export function pointerdown(target, defaultPayload) {
    
  200.   const dispatch = arg => target.dispatchEvent(arg);
    
  201.   const pointerType = getPointerType(defaultPayload);
    
  202. 
    
  203.   const payload = {
    
  204.     button: buttonType.primary,
    
  205.     buttons: buttonsType.primary,
    
  206.     pointerId: defaultPointerId,
    
  207.     pointerType,
    
  208.     ...defaultPayload,
    
  209.   };
    
  210. 
    
  211.   if (pointerType === 'mouse') {
    
  212.     if (hasPointerEvent()) {
    
  213.       dispatch(domEvents.pointerover(payload));
    
  214.       dispatch(domEvents.pointerenter(payload));
    
  215.     }
    
  216.     dispatch(domEvents.mouseover(payload));
    
  217.     dispatch(domEvents.mouseenter(payload));
    
  218.     if (hasPointerEvent()) {
    
  219.       dispatch(domEvents.pointerdown(payload));
    
  220.     }
    
  221.     dispatch(domEvents.mousedown(payload));
    
  222.     if (document.activeElement !== target) {
    
  223.       dispatch(domEvents.focus());
    
  224.     }
    
  225.   } else {
    
  226.     if (hasPointerEvent()) {
    
  227.       dispatch(domEvents.pointerover(payload));
    
  228.       dispatch(domEvents.pointerenter(payload));
    
  229.       dispatch(domEvents.pointerdown(payload));
    
  230.     }
    
  231.     const touch = createTouch(target, payload);
    
  232.     touchStore.addTouch(touch);
    
  233.     const touchEventPayload = createTouchEventPayload(target, touch, payload);
    
  234.     dispatch(domEvents.touchstart(touchEventPayload));
    
  235.     if (hasPointerEvent()) {
    
  236.       dispatch(domEvents.gotpointercapture(payload));
    
  237.     }
    
  238.   }
    
  239. }
    
  240. 
    
  241. export function pointerenter(target, defaultPayload) {
    
  242.   const dispatch = arg => target.dispatchEvent(arg);
    
  243. 
    
  244.   const payload = {
    
  245.     pointerId: defaultPointerId,
    
  246.     ...defaultPayload,
    
  247.   };
    
  248. 
    
  249.   if (hasPointerEvent()) {
    
  250.     dispatch(domEvents.pointerover(payload));
    
  251.     dispatch(domEvents.pointerenter(payload));
    
  252.   }
    
  253.   dispatch(domEvents.mouseover(payload));
    
  254.   dispatch(domEvents.mouseenter(payload));
    
  255. }
    
  256. 
    
  257. export function pointerexit(target, defaultPayload) {
    
  258.   const dispatch = arg => target.dispatchEvent(arg);
    
  259. 
    
  260.   const payload = {
    
  261.     pointerId: defaultPointerId,
    
  262.     ...defaultPayload,
    
  263.   };
    
  264. 
    
  265.   if (hasPointerEvent()) {
    
  266.     dispatch(domEvents.pointerout(payload));
    
  267.     dispatch(domEvents.pointerleave(payload));
    
  268.   }
    
  269.   dispatch(domEvents.mouseout(payload));
    
  270.   dispatch(domEvents.mouseleave(payload));
    
  271. }
    
  272. 
    
  273. export function pointerhover(target, defaultPayload) {
    
  274.   const dispatch = arg => target.dispatchEvent(arg);
    
  275. 
    
  276.   const payload = {
    
  277.     pointerId: defaultPointerId,
    
  278.     ...defaultPayload,
    
  279.   };
    
  280. 
    
  281.   if (hasPointerEvent()) {
    
  282.     dispatch(domEvents.pointermove(payload));
    
  283.   }
    
  284.   dispatch(domEvents.mousemove(payload));
    
  285. }
    
  286. 
    
  287. export function pointermove(target, defaultPayload) {
    
  288.   const dispatch = arg => target.dispatchEvent(arg);
    
  289.   const pointerType = getPointerType(defaultPayload);
    
  290. 
    
  291.   const payload = {
    
  292.     pointerId: defaultPointerId,
    
  293.     pointerType,
    
  294.     ...defaultPayload,
    
  295.   };
    
  296. 
    
  297.   if (hasPointerEvent()) {
    
  298.     dispatch(
    
  299.       domEvents.pointermove({
    
  300.         pressure: pointerType === 'touch' ? 1 : 0.5,
    
  301.         ...payload,
    
  302.       }),
    
  303.     );
    
  304.   } else {
    
  305.     if (pointerType === 'mouse') {
    
  306.       dispatch(domEvents.mousemove(payload));
    
  307.     } else {
    
  308.       const touch = createTouch(target, payload);
    
  309.       touchStore.updateTouch(touch);
    
  310.       const touchEventPayload = createTouchEventPayload(target, touch, payload);
    
  311.       dispatch(domEvents.touchmove(touchEventPayload));
    
  312.     }
    
  313.   }
    
  314. }
    
  315. 
    
  316. export function pointerup(target, defaultPayload) {
    
  317.   const dispatch = arg => target.dispatchEvent(arg);
    
  318.   const pointerType = getPointerType(defaultPayload);
    
  319. 
    
  320.   const payload = {
    
  321.     pointerId: defaultPointerId,
    
  322.     pointerType,
    
  323.     ...defaultPayload,
    
  324.   };
    
  325. 
    
  326.   if (pointerType === 'mouse') {
    
  327.     if (hasPointerEvent()) {
    
  328.       dispatch(domEvents.pointerup(payload));
    
  329.     }
    
  330.     dispatch(domEvents.mouseup(payload));
    
  331.     dispatch(domEvents.click(payload));
    
  332.   } else {
    
  333.     if (hasPointerEvent()) {
    
  334.       dispatch(domEvents.pointerup(payload));
    
  335.       dispatch(domEvents.lostpointercapture(payload));
    
  336.       dispatch(domEvents.pointerout(payload));
    
  337.       dispatch(domEvents.pointerleave(payload));
    
  338.     }
    
  339.     const touch = createTouch(target, payload);
    
  340.     touchStore.removeTouch(touch);
    
  341.     const touchEventPayload = createTouchEventPayload(target, touch, payload);
    
  342.     dispatch(domEvents.touchend(touchEventPayload));
    
  343.     dispatch(domEvents.mouseover(payload));
    
  344.     dispatch(domEvents.mousemove(payload));
    
  345.     dispatch(domEvents.mousedown(payload));
    
  346.     if (document.activeElement !== target) {
    
  347.       dispatch(domEvents.focus());
    
  348.     }
    
  349.     dispatch(domEvents.mouseup(payload));
    
  350.     dispatch(domEvents.click(payload));
    
  351.   }
    
  352. }
    
  353. 
    
  354. /**
    
  355.  * This function should be called after each test to ensure the touchStore is cleared
    
  356.  * in cases where the mock pointers weren't released before the test completed
    
  357.  * (e.g., a test failed or ran a partial gesture).
    
  358.  */
    
  359. export function resetActivePointers() {
    
  360.   touchStore.clear();
    
  361. }