{"version":3,"file":"useArrowNavigation.js","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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"}