{"version":3,"file":"useListNavigation.cjs","sources":["../../../../src/hooks/useListNavigation/useListNavigation.ts"],"sourcesContent":["import { type RefObject, useCallback, useEffect, useRef } from \"react\";\n\n/**\n * Enten `ref` eller `element` må gis. `ref` brukes når listen alltid\n * er i DOM-en; `element` foretrekkes når elementet mountes/unmountes\n * dynamisk (f.eks. portalert via floating-ui), slik at listeneren\n * re-festes når elementet faktisk er tilgjengelig.\n */\ntype UseListNavigationProps<T> = {\n    disableTypeahead?: boolean;\n} & (\n    | {\n          ref: RefObject<T | null>;\n          element?: never;\n      }\n    | {\n          ref?: never;\n          element: T | null;\n      }\n);\n\nconst TYPEAHEAD_SEARCH_TIMEOUT = 500;\nconst MODIFIERS = [\"Control\", \"Alt\", \"Shift\", \"Meta\"];\nconst LIST_ITEM_SELECTOR =\n    \"[role='option'][class~='jkl-select__option'],[class~='jkl-combobox__option'],[class~='jkl-option'],[class~='jkl-search']\";\n\nexport function useListNavigation<T extends HTMLElement>({\n    ref,\n    element,\n    disableTypeahead = false,\n}: UseListNavigationProps<T>): void {\n    const searchResetTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n        undefined,\n    );\n    const searchTerm = useRef<string>(\"\");\n\n    const typeahead = useCallback(\n        (key: string): void => {\n            if (disableTypeahead || MODIFIERS.includes(key)) return;\n            const list = element ?? ref?.current ?? null;\n\n            searchTerm.current = searchTerm.current.concat(key);\n            clearTimeout(searchResetTimer.current);\n            searchResetTimer.current = setTimeout(() => {\n                searchTerm.current = \"\";\n                searchResetTimer.current = undefined;\n            }, TYPEAHEAD_SEARCH_TIMEOUT);\n\n            if (!searchTerm.current.length) return;\n\n            const listItems =\n                list?.querySelectorAll<HTMLElement>(LIST_ITEM_SELECTOR);\n            if (!listItems || !listItems.length) return;\n\n            for (const item of listItems) {\n                if (\n                    item.innerText\n                        ?.toLowerCase()\n                        .indexOf(searchTerm.current.toLowerCase()) === 0\n                ) {\n                    focusButtonOrInput(item);\n                    return;\n                }\n            }\n        },\n        [element, ref, disableTypeahead],\n    );\n\n    const handleKeyNavigation = useCallback(\n        (event: KeyboardEvent): void => {\n            const list = element ?? ref?.current ?? null;\n            if (!list) return;\n\n            const { key, target } = event;\n            const current = target as HTMLElement;\n            const allItems = Array.from(\n                list.querySelectorAll<HTMLElement>(LIST_ITEM_SELECTOR),\n            );\n\n            switch (key) {\n                case \"ArrowUp\": {\n                    event.preventDefault();\n                    const index = allItems.indexOf(\n                        current.closest(LIST_ITEM_SELECTOR) as HTMLElement,\n                    );\n                    focusButtonOrInput(allItems[index - 1]);\n                    break;\n                }\n                case \"ArrowDown\": {\n                    event.preventDefault();\n                    const index = allItems.indexOf(\n                        current.closest(LIST_ITEM_SELECTOR) as HTMLElement,\n                    );\n                    focusButtonOrInput(allItems[index + 1]);\n                    break;\n                }\n                case \"PageUp\":\n                case \"Home\": {\n                    event.preventDefault();\n                    focusButtonOrInput(allItems[0]);\n                    break;\n                }\n                case \"PageDown\":\n                case \"End\": {\n                    event.preventDefault();\n                    focusButtonOrInput(allItems[allItems.length - 1]);\n                    break;\n                }\n                // Vi gjør ingenting for modifiers eller mellomrom\n                case \"Shift\":\n                case \"Control\":\n                case \"Alt\":\n                case \"Meta\":\n                case \"Enter\":\n                case \"Escape\":\n                case \" \":\n                    break;\n\n                case \"Tab\":\n                    event.preventDefault();\n                    break;\n\n                // Alle andre taster trigger typeahead\n                default:\n                    typeahead(key);\n                    break;\n            }\n        },\n        [element, ref, typeahead],\n    );\n\n    useEffect(() => {\n        const list = element ?? ref?.current ?? null;\n        if (!list) return;\n\n        list.addEventListener(\"keydown\", handleKeyNavigation);\n\n        return () => {\n            list.removeEventListener(\"keydown\", handleKeyNavigation);\n            clearTimeout(searchResetTimer.current);\n        };\n    }, [element, ref, handleKeyNavigation]);\n}\n\nfunction focusButtonOrInput(element?: Element | null): void {\n    if (!element) return;\n\n    if (element.nodeName === \"BUTTON\" || element.nodeName === \"INPUT\") {\n        (element as HTMLElement).focus();\n    } else {\n        element\n            .querySelector<HTMLInputElement | HTMLButtonElement>(\"input,button\")\n            ?.focus();\n    }\n}\n"],"names":["MODIFIERS","LIST_ITEM_SELECTOR","focusButtonOrInput","element","nodeName","focus","querySelector","ref","disableTypeahead","searchResetTimer","useRef","searchTerm","typeahead","useCallback","key","includes","list","current","concat","clearTimeout","setTimeout","length","listItems","querySelectorAll","item","innerText","toLowerCase","indexOf","handleKeyNavigation","event","target","allItems","Array","from","preventDefault","index","closest","useEffect","addEventListener","removeEventListener"],"mappings":"yGAsBMA,EAAY,CAAC,UAAW,MAAO,QAAS,QACxCC,EACF,2HAwHJ,SAASC,EAAmBC,GACnBA,IAEoB,WAArBA,EAAQC,UAA8C,UAArBD,EAAQC,SACxCD,EAAwBE,QAEzBF,EACKG,cAAoD,iBACnDD,QAEd,2BAhIO,UACHE,IAAAA,EACAJ,QAAAA,EACAK,iBAAAA,GAAmB,IAEnB,MAAMC,EAAmBC,EAAAA,YACrB,GAEEC,EAAaD,EAAAA,OAAe,IAE5BE,EAAYC,EAAAA,YACbC,IACG,GAAIN,GAAoBR,EAAUe,SAASD,GAAM,OACjD,MAAME,EAAOb,GAAWI,GAAKU,SAAW,KASxC,GAPAN,EAAWM,QAAUN,EAAWM,QAAQC,OAAOJ,GAC/CK,aAAaV,EAAiBQ,SAC9BR,EAAiBQ,QAAUG,WAAW,KAClCT,EAAWM,QAAU,GACrBR,EAAiBQ,aAAU,GAxBV,MA2BhBN,EAAWM,QAAQI,OAAQ,OAEhC,MAAMC,EACFN,GAAMO,iBAA8BtB,GACxC,GAAKqB,GAAcA,EAAUD,OAE7B,IAAA,MAAWG,KAAQF,EACf,GAGuD,IAFnDE,EAAKC,WACCC,cACDC,QAAQhB,EAAWM,QAAQS,eAGhC,YADAxB,EAAmBsB,IAK/B,CAACrB,EAASI,EAAKC,IAGboB,EAAsBf,EAAAA,YACvBgB,IACG,MAAMb,EAAOb,GAAWI,GAAKU,SAAW,KACxC,IAAKD,EAAM,OAEX,MAAQF,IAAAA,EAAKgB,OAAAA,GAAWD,EAClBZ,EAAUa,EACVC,EAAWC,MAAMC,KACnBjB,EAAKO,iBAA8BtB,IAGvC,OAAQa,GACJ,IAAK,UAAW,CACZe,EAAMK,iBACN,MAAMC,EAAQJ,EAASJ,QACnBV,EAAQmB,QAAQnC,IAEpBC,EAAmB6B,EAASI,EAAQ,IACpC,KACJ,CACA,IAAK,YAAa,CACdN,EAAMK,iBACN,MAAMC,EAAQJ,EAASJ,QACnBV,EAAQmB,QAAQnC,IAEpBC,EAAmB6B,EAASI,EAAQ,IACpC,KACJ,CACA,IAAK,SACL,IAAK,OACDN,EAAMK,iBACNhC,EAAmB6B,EAAS,IAC5B,MAEJ,IAAK,WACL,IAAK,MACDF,EAAMK,iBACNhC,EAAmB6B,EAASA,EAASV,OAAS,IAC9C,MAGJ,IAAK,QACL,IAAK,UACL,IAAK,MACL,IAAK,OACL,IAAK,QACL,IAAK,SACL,IAAK,IACD,MAEJ,IAAK,MACDQ,EAAMK,iBACN,MAGJ,QACItB,EAAUE,KAItB,CAACX,EAASI,EAAKK,IAGnByB,EAAAA,UAAU,KACN,MAAMrB,EAAOb,GAAWI,GAAKU,SAAW,KACxC,GAAKD,EAEL,OAAAA,EAAKsB,iBAAiB,UAAWV,GAE1B,KACHZ,EAAKuB,oBAAoB,UAAWX,GACpCT,aAAaV,EAAiBQ,WAEnC,CAACd,EAASI,EAAKqB,GACtB"}