UNPKG

@fremtind/jkl-react-hooks

Version:
146 lines (145 loc) 3.19 kB
import { useEffect } from "react"; function useListNavigation({ ref }) { useEffect(() => { let searchResetTimer; const search = { keys: "" }; const list = ref.current; const handler = (event) => { if (list) { handleListKeyNav({ list, event, search, searchResetTimer }); } }; if (list) { list.addEventListener("keydown", handler); } return () => { if (list) { list.removeEventListener("keydown", handler); } }; }, [ref]); } function handleMoveTo(direction, { event, list, currentFocus }) { event.preventDefault(); moveFocusTo(direction, list, currentFocus); } function handleListKeyNav({ list, event, search, searchResetTimer }) { const { key, target } = event; const currentFocus = target; const moveDetails = { event, list, currentFocus }; switch (key) { case "ArrowUp": case "PageUp": handleMoveTo("prev", moveDetails); break; case "ArrowDown": case "PageDown": handleMoveTo("next", moveDetails); break; case "Home": handleMoveTo("first", moveDetails); break; case "End": handleMoveTo("last", moveDetails); break; case "Tab": event.preventDefault(); break; case "Enter": case " ": break; default: if (search !== void 0) { const searchResult = findItem({ list, key, search, searchResetTimer }); if (searchResult) { searchResult.focus(); } } break; } } function moveFocusTo(direction, list, current) { const thisOption = current; switch (direction) { case "prev": const prevOption = thisOption && thisOption.previousElementSibling; if (prevOption) { prevOption.focus(); } break; case "next": const nextOption = thisOption && thisOption.nextElementSibling; if (nextOption) { nextOption.focus(); } break; case "first": const firstItem = list.querySelector('[role="option"]'); if (firstItem) { firstItem.focus(); } break; case "last": const listItems = list.querySelectorAll('[role="option"]'); if (listItems.length) { listItems[listItems.length - 1].focus(); } break; } } function findItem({ list, key, search, searchResetTimer }) { const listItems = list.querySelectorAll('[role="option"]'); if (!listItems.length) return null; if (search) { search.keys = search.keys.concat(key); resetWhenIdle(search, searchResetTimer); for (let n = 0; n < listItems.length; n++) { const label = listItems[n].innerText; if (label && label.toLowerCase().indexOf(search.keys) === 0) { return listItems[n]; } } } return null; } function resetWhenIdle(search, timer) { if (timer) { clearTimeout(timer); timer = void 0; } timer = setTimeout( () => { search ? search.keys = "" : search = { keys: "" }; timer = void 0; }, 500, search, timer ); } export { useListNavigation }; //# sourceMappingURL=useListNavigation.js.map