// packages/block-editor/src/components/writing-flow/use-arrow-nav.js
import {
  computeCaretRect,
  focus,
  isHorizontalEdge,
  isVerticalEdge,
  placeCaretAtHorizontalEdge,
  placeCaretAtVerticalEdge,
  isRTL
} from "@wordpress/dom";
import { UP, DOWN, LEFT, RIGHT } from "@wordpress/keycodes";
import { useDispatch, useSelect } from "@wordpress/data";
import { useRefEffect } from "@wordpress/compose";
import { getBlockClientId, isInSameBlock } from "../../utils/dom.mjs";
import { store as blockEditorStore } from "../../store/index.mjs";
function isNavigationCandidate(element, keyCode, hasModifier) {
  const isVertical = keyCode === UP || keyCode === DOWN;
  const { tagName } = element;
  const elementType = element.getAttribute("type");
  if (isVertical && !hasModifier) {
    if (tagName === "INPUT") {
      const verticalInputTypes = [
        "date",
        "datetime-local",
        "month",
        "number",
        "range",
        "time",
        "week"
      ];
      return !verticalInputTypes.includes(elementType);
    }
    return true;
  }
  if (tagName === "INPUT") {
    const simpleInputTypes = [
      "button",
      "checkbox",
      "number",
      "color",
      "file",
      "image",
      "radio",
      "reset",
      "submit"
    ];
    return simpleInputTypes.includes(elementType);
  }
  return tagName !== "TEXTAREA";
}
function getClosestTabbable(target, isReverse, containerElement, onlyVertical) {
  let focusableNodes = focus.focusable.find(containerElement);
  if (isReverse) {
    focusableNodes.reverse();
  }
  focusableNodes = focusableNodes.slice(
    focusableNodes.indexOf(target) + 1
  );
  let targetRect;
  if (onlyVertical) {
    targetRect = target.getBoundingClientRect();
  }
  function isTabCandidate(node) {
    if (node.children.length === 1 && isInSameBlock(node, node.firstElementChild) && node.firstElementChild.getAttribute("contenteditable") === "true") {
      return;
    }
    if (!focus.tabbable.isTabbableIndex(node)) {
      return false;
    }
    if (node.isContentEditable && node.contentEditable !== "true") {
      return false;
    }
    if (onlyVertical) {
      const nodeRect = node.getBoundingClientRect();
      if (nodeRect.left >= targetRect.right || nodeRect.right <= targetRect.left) {
        return false;
      }
    }
    return true;
  }
  return focusableNodes.find(isTabCandidate);
}
function useArrowNav() {
  const {
    getMultiSelectedBlocksStartClientId,
    getMultiSelectedBlocksEndClientId,
    getSettings,
    hasMultiSelection,
    __unstableIsFullySelected
  } = useSelect(blockEditorStore);
  const { selectBlock } = useDispatch(blockEditorStore);
  return useRefEffect((node) => {
    let verticalRect;
    function onMouseDown() {
      verticalRect = null;
    }
    function isClosestTabbableABlock(target, isReverse) {
      const closestTabbable = getClosestTabbable(
        target,
        isReverse,
        node
      );
      return closestTabbable && getBlockClientId(closestTabbable);
    }
    function onKeyDown(event) {
      if (event.defaultPrevented) {
        return;
      }
      const { keyCode, target, shiftKey, ctrlKey, altKey, metaKey } = event;
      const isUp = keyCode === UP;
      const isDown = keyCode === DOWN;
      const isLeft = keyCode === LEFT;
      const isRight = keyCode === RIGHT;
      const isReverse = isUp || isLeft;
      const isHorizontal = isLeft || isRight;
      const isVertical = isUp || isDown;
      const isNav = isHorizontal || isVertical;
      const hasModifier = shiftKey || ctrlKey || altKey || metaKey;
      const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge;
      const { ownerDocument } = node;
      const { defaultView } = ownerDocument;
      if (!isNav) {
        return;
      }
      if (getSettings().isPreviewMode) {
        return;
      }
      if (hasMultiSelection()) {
        if (shiftKey) {
          return;
        }
        if (!__unstableIsFullySelected()) {
          return;
        }
        event.preventDefault();
        if (isReverse) {
          selectBlock(getMultiSelectedBlocksStartClientId());
        } else {
          selectBlock(getMultiSelectedBlocksEndClientId(), -1);
        }
        return;
      }
      if (!isNavigationCandidate(target, keyCode, hasModifier)) {
        return;
      }
      if (!isVertical) {
        verticalRect = null;
      } else if (!verticalRect) {
        verticalRect = computeCaretRect(defaultView);
      }
      const isReverseDir = isRTL(target) ? !isReverse : isReverse;
      const { keepCaretInsideBlock } = getSettings();
      if (shiftKey) {
        if (isClosestTabbableABlock(target, isReverse) && isNavEdge(target, isReverse)) {
          node.contentEditable = true;
          node.focus();
        }
      } else if (isVertical && isVerticalEdge(target, isReverse) && // When Alt is pressed, only intercept if the caret is also at
      // the horizontal edge.
      (altKey ? isHorizontalEdge(target, isReverseDir) : true) && !keepCaretInsideBlock) {
        const closestTabbable = getClosestTabbable(
          target,
          isReverse,
          node,
          true
        );
        if (closestTabbable) {
          placeCaretAtVerticalEdge(
            closestTabbable,
            // When Alt is pressed, place the caret at the furthest
            // horizontal edge and the furthest vertical edge.
            altKey ? !isReverse : isReverse,
            altKey ? void 0 : verticalRect
          );
          event.preventDefault();
        }
      } else if (isHorizontal && defaultView.getSelection().isCollapsed && isHorizontalEdge(target, isReverseDir) && !keepCaretInsideBlock) {
        const closestTabbable = getClosestTabbable(
          target,
          isReverseDir,
          node
        );
        placeCaretAtHorizontalEdge(closestTabbable, isReverse);
        event.preventDefault();
      }
    }
    node.addEventListener("mousedown", onMouseDown);
    node.addEventListener("keydown", onKeyDown);
    return () => {
      node.removeEventListener("mousedown", onMouseDown);
      node.removeEventListener("keydown", onKeyDown);
    };
  }, []);
}
export {
  useArrowNav as default,
  getClosestTabbable,
  isNavigationCandidate
};
//# sourceMappingURL=use-arrow-nav.mjs.map
