All files / react-native-web/src/modules/useResponderEvents utils.js

97.14% Statements 68/70
87.76% Branches 43/49
100% Functions 9/9
97.06% Lines 66/68

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171                      17x           250x 3x 3x     247x     247x         3x 3x 8x 8x   3x             1660x 1660x                 180x 180x                   250x 250x 250x 250x 1660x 1660x 1660x 367x 367x     250x             32x 32x 32x               5x     27x 27x 27x 27x     27x 4x 4x 4x       27x 3x 3x 3x       27x 27x 31x 27x   4x 4x                   41x 37x   4x 4x 4x 4x 3x       1x               5x 3x   2x             93x 93x 93x 93x 93x   93x 84x   9x    
/**
 * Copyright (c) Nicolas Gallagher
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */
 
import isSelectionValid from '../../modules/isSelectionValid';
 
const keyName = '__reactResponderId';
 
function getEventPath(domEvent: any): Array<any> {
  // The 'selectionchange' event always has the 'document' as the target.
  // Use the anchor node as the initial target to reconstruct a path.
  // (We actually only need the first "responder" node in practice.)
  if (domEvent.type === 'selectionchange') {
    const target = window.getSelection().anchorNode;
    return composedPathFallback(target);
  } else {
    const path =
      domEvent.composedPath != null
        ? domEvent.composedPath()
        : composedPathFallback(domEvent.target);
    return path;
  }
}
 
function composedPathFallback(target: any): Array<any> {
  const path = [];
  while (target != null && target !== document.body) {
    path.push(target);
    target = target.parentNode;
  }
  return path;
}
 
/**
 * Retrieve the responderId from a host node
 */
function getResponderId(node: any): ?number {
  Eif (node != null) {
    return node[keyName];
  }
  return null;
}
 
/**
 * Store the responderId on a host node
 */
export function setResponderId(node: any, id: number) {
  Eif (node != null) {
    node[keyName] = id;
  }
}
 
/**
 * Filter the event path to contain only the nodes attached to the responder system
 */
export function getResponderPaths(
  domEvent: any
): {| idPath: Array<number>, nodePath: Array<any> |} {
  const idPath = [];
  const nodePath = [];
  const eventPath = getEventPath(domEvent);
  for (let i = 0; i < eventPath.length; i++) {
    const node = eventPath[i];
    const id = getResponderId(node);
    if (id != null) {
      idPath.push(id);
      nodePath.push(node);
    }
  }
  return { idPath, nodePath };
}
 
/**
 * Walk the paths and find the first common ancestor
 */
export function getLowestCommonAncestor(pathA: Array<any>, pathB: Array<any>): any {
  let pathALength = pathA.length;
  let pathBLength = pathB.length;
  if (
    // If either path is empty
    pathALength === 0 ||
    pathBLength === 0 ||
    // If the last elements aren't the same there can't be a common ancestor
    // that is connected to the responder system
    pathA[pathALength - 1] !== pathB[pathBLength - 1]
  ) {
    return null;
  }
 
  let itemA = pathA[0];
  let indexA = 0;
  let itemB = pathB[0];
  let indexB = 0;
 
  // If A is deeper, skip indices that can't match.
  if (pathALength - pathBLength > 0) {
    indexA = pathALength - pathBLength;
    itemA = pathA[indexA];
    pathALength = pathBLength;
  }
 
  // If B is deeper, skip indices that can't match
  if (pathBLength - pathALength > 0) {
    indexB = pathBLength - pathALength;
    itemB = pathB[indexB];
    pathBLength = pathALength;
  }
 
  // Walk in lockstep until a match is found
  let depth = pathALength;
  while (depth--) {
    if (itemA === itemB) {
      return itemA;
    }
    itemA = pathA[indexA++];
    itemB = pathB[indexB++];
  }
  return null;
}
 
/**
 * Determine whether any of the active touches are within the current responder.
 * This cannot rely on W3C `targetTouches`, as neither IE11 nor Safari implement it.
 */
export function hasTargetTouches(target: any, touches: any): boolean {
  if (!touches || touches.length === 0) {
    return false;
  }
  for (let i = 0; i < touches.length; i++) {
    const node = touches[i].target;
    Eif (node != null) {
      if (target.contains(node)) {
        return true;
      }
    }
  }
  return false;
}
 
/**
 * Ignore 'selectionchange' events that don't correspond with a person's intent to
 * select text.
 */
export function hasValidSelection(domEvent: any): boolean {
  if (domEvent.type === 'selectionchange') {
    return isSelectionValid();
  }
  return domEvent.type === 'select';
}
 
/**
 * Events are only valid if the primary button was used without specific modifier keys.
 */
export function isPrimaryPointerDown(domEvent: any): boolean {
  const { altKey, button, buttons, ctrlKey, type } = domEvent;
  const isTouch = type === 'touchstart' || type === 'touchmove';
  const isPrimaryMouseDown = type === 'mousedown' && (button === 0 || buttons === 1);
  const isPrimaryMouseMove = type === 'mousemove' && buttons === 1;
  const noModifiers = altKey === false && ctrlKey === false;
 
  if (isTouch || (isPrimaryMouseDown && noModifiers) || (isPrimaryMouseMove && noModifiers)) {
    return true;
  }
  return false;
}