{"version":3,"file":"useArrowNavigation.cjs","sources":["../../src/shared/useArrowNavigation.ts"],"sourcesContent":["import type { Direction } from './types'\n\ntype ArrowKeyOptions = 'horizontal' | 'vertical' | 'both'\n\ninterface ArrowNavigationOptions {\n  /**\n   * The arrow key options to allow navigation\n   *\n   * @defaultValue \"both\"\n   */\n  arrowKeyOptions?: ArrowKeyOptions\n\n  /**\n   * The attribute name to find the collection items in the parent element.\n   *\n   * @defaultValue \"data-reka-collection-item\"\n   */\n  attributeName?: string\n\n  /**\n   * The parent element where contains all the collection items, this will collect every item to be used when nav\n   * It will be ignored if attributeName is provided\n   *\n   * @defaultValue []\n   */\n  itemsArray?: HTMLElement[]\n\n  /**\n   * Allow loop navigation. If false, it will stop at the first and last element\n   *\n   * @defaultValue true\n   */\n  loop?: boolean\n\n  /**\n   * The orientation of the collection\n   *\n   * @defaultValue \"ltr\"\n   */\n  dir?: Direction\n\n  /**\n   * Prevent the scroll when navigating. This happens when the direction of the\n   * key matches the scroll direction of any ancestor scrollable elements.\n   *\n   * @defaultValue true\n   */\n  preventScroll?: boolean\n\n  /**\n   * By default all currentElement would trigger navigation. If `true`, currentElement nodeName in the ignore list will return null\n   *\n   * @defaultValue false\n   */\n  enableIgnoredElement?: boolean\n\n  /**\n   * Focus the element after navigation\n   *\n   * @defaultValue false\n   */\n  focus?: boolean\n}\n\nconst ignoredElement = ['INPUT', 'TEXTAREA']\n\n/**\n * Allow arrow navigation for every html element with data-reka-collection-item tag\n *\n * @param e               Keyboard event\n * @param currentElement  Event initiator element or any element that wants to handle the navigation\n * @param parentElement   Parent element where contains all the collection items, this will collect every item to be used when nav\n * @param options         further options\n * @returns               the navigated html element or null if none\n */\nexport function useArrowNavigation(\n  e: KeyboardEvent,\n  currentElement: HTMLElement,\n  parentElement: HTMLElement | undefined,\n  options: ArrowNavigationOptions = {},\n): HTMLElement | null {\n  if (!currentElement || (options.enableIgnoredElement && ignoredElement.includes(currentElement.nodeName)))\n    return null\n\n  const {\n    arrowKeyOptions = 'both',\n    attributeName = '[data-reka-collection-item]',\n    itemsArray = [],\n    loop = true,\n    dir = 'ltr',\n    preventScroll = true,\n    focus = false,\n  } = options\n\n  const [right, left, up, down, home, end] = [\n    e.key === 'ArrowRight',\n    e.key === 'ArrowLeft',\n    e.key === 'ArrowUp',\n    e.key === 'ArrowDown',\n    e.key === 'Home',\n    e.key === 'End',\n  ]\n  const goingVertical = up || down\n  const goingHorizontal = right || left\n  if (\n    !home\n    && !end\n    && ((!goingVertical && !goingHorizontal)\n      || (arrowKeyOptions === 'vertical' && goingHorizontal)\n      || (arrowKeyOptions === 'horizontal' && goingVertical))\n  ) {\n    return null\n  }\n\n  const allCollectionItems: HTMLElement[] = parentElement\n    ? Array.from(parentElement.querySelectorAll(attributeName))\n    : itemsArray\n\n  if (!allCollectionItems.length)\n    return null\n\n  if (preventScroll)\n    e.preventDefault()\n\n  let item: HTMLElement | null = null\n\n  if (goingHorizontal || goingVertical) {\n    const goForward = goingVertical ? down : dir === 'ltr' ? right : left\n    item = findNextFocusableElement(allCollectionItems, currentElement, {\n      goForward,\n      loop,\n    })\n  }\n  else if (home) {\n    item = allCollectionItems.at(0) || null\n  }\n  else if (end) {\n    item = allCollectionItems.at(-1) || null\n  }\n\n  if (focus)\n    item?.focus()\n\n  return item\n}\n\ninterface FindNextFocusableElementOptions {\n  /**\n   * Whether to search forwards or backwards.\n   */\n  goForward: boolean\n  /**\n   * Whether to allow looping the search. If false, it will stop at the first/last element.\n   *\n   * @default true\n   */\n  loop?: boolean\n}\n\n/**\n * Recursive function to find the next focusable element to avoid disabled elements\n *\n * @param elements Elements to navigate\n * @param currentElement Current active element\n * @param options\n * @returns next focusable element\n */\nfunction findNextFocusableElement(\n  elements: HTMLElement[],\n  currentElement: HTMLElement,\n  options: FindNextFocusableElementOptions,\n  iterations = elements.length,\n): HTMLElement | null {\n  if (--iterations === 0)\n    return null\n\n  const index = elements.indexOf(currentElement)\n  const newIndex = options.goForward ? index + 1 : index - 1\n\n  if (!options.loop && (newIndex < 0 || newIndex >= elements.length))\n    return null\n\n  const adjustedNewIndex = (newIndex + elements.length) % elements.length\n  const candidate = elements[adjustedNewIndex]\n  if (!candidate)\n    return null\n\n  const isDisabled\n    = candidate.hasAttribute('disabled')\n    && candidate.getAttribute('disabled') !== 'false'\n  if (isDisabled) {\n    return findNextFocusableElement(\n      elements,\n      candidate,\n      options,\n      iterations,\n    )\n  }\n  return candidate\n}\n"],"names":[],"mappings":"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"}