UNPKG

react-use-focus-trap

Version:

A react hook to trap the focus within a reference

84 lines (77 loc) 4.82 kB
import {useRef as $5yN2A$useRef, useCallback as $5yN2A$useCallback, useEffect as $5yN2A$useEffect} from "react"; function $92986bfec7ee7470$export$94fec357614ce4e9(stringToConvert) { const parsed = parseInt(stringToConvert); return parsed ? parsed : 0; } function $92986bfec7ee7470$export$8f129d607746da1a(firstNode, secondNode) { const tabIndexes = [ firstNode, secondNode ].map((node)=>$92986bfec7ee7470$export$b1fd826f03019552(node)); return tabIndexes.map((tabIndexValue)=>$92986bfec7ee7470$var$sanitizeTabIndexInput(tabIndexValue, Math.max(...tabIndexes))).reduce((previousValue, currentValue)=>previousValue - currentValue); } /** * Prepares a tab-index to be further processed for the tab order of the focus trap. * It can't be less than 0, because negative values can not be part of the tab order at all. * In case it's exactly 0 it actually needs to be higher than any positive (> 0) value, since tab-index=0 means "follow the system default order". * The default tab order comes _after_ special tab indexes (>0). * @param {number} tabIndex The index to sanitize * @param {number} highestPositiveTabIndex The largest number among the tab indexes from the same context * @throws An error if the tabIndex is less than 0 * @returns Tha sanitized tab index * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex} for further information on the tabindex and its order */ function $92986bfec7ee7470$var$sanitizeTabIndexInput(tabIndex, highestPositiveTabIndex) { if (tabIndex < 0) throw new Error(`Unable to sort given input. A negative value is not part of the tab order: ${tabIndex}`); // 0 based tab indexes have a higher order than positive valued indicies, thus we add 1 to the max value return tabIndex === 0 ? highestPositiveTabIndex + 1 : tabIndex; } function $92986bfec7ee7470$export$b1fd826f03019552(targetNode) { return $92986bfec7ee7470$export$94fec357614ce4e9(targetNode.getAttribute("tabindex")); } const $24f8bdb3332f913b$var$focusableElementsSelector = "a[href], area[href], input:not([disabled]):not([type=hidden]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; const $24f8bdb3332f913b$var$TAB_KEY = 9; function $24f8bdb3332f913b$export$5562a1f223a0d314() { const trapRef = (0, $5yN2A$useRef)(null); const selectNextFocusableElem = (0, $5yN2A$useCallback)((sortedFocusableElems, currentIndex, shiftKeyPressed = false, skipCount = 0)=>{ if (skipCount > sortedFocusableElems.length) // this means that it ran through all of elements but non was properly focusable // hence we stop it to avoid running in an infinite loop return false; const backwards = !!shiftKeyPressed; const maxIndex = sortedFocusableElems.length - 1; if (!currentIndex) currentIndex = sortedFocusableElems.indexOf(document.activeElement) ?? 0; let nextIndex = backwards ? currentIndex - 1 : currentIndex + 1; if (nextIndex > maxIndex) nextIndex = 0; if (nextIndex < 0) nextIndex = maxIndex; const newFocusElem = sortedFocusableElems[nextIndex]; newFocusElem.focus(); if (document.activeElement !== newFocusElem) // run another round selectNextFocusableElem(sortedFocusableElems, nextIndex, shiftKeyPressed, skipCount + 1); }); // defining the trap function first const trapper = (0, $5yN2A$useCallback)((evt)=>{ const trapRefElem = trapRef.current; if (trapRefElem !== null) { if (evt.which === $24f8bdb3332f913b$var$TAB_KEY || evt.key === "Tab") { evt.preventDefault(); const shiftKeyPressed = !!evt.shiftKey; let focusableElems = Array.from(trapRefElem.querySelectorAll($24f8bdb3332f913b$var$focusableElementsSelector)).filter((focusableElement)=>(0, $92986bfec7ee7470$export$b1fd826f03019552)(focusableElement) >= 0); // caching this is NOT a good idea in dynamic applications - so don't! // now we need to sort it by tabIndex, highest first focusableElems = focusableElems.sort((0, $92986bfec7ee7470$export$8f129d607746da1a)); selectNextFocusableElem(focusableElems, undefined, shiftKeyPressed); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); (0, $5yN2A$useEffect)(()=>{ window.addEventListener("keydown", trapper); return ()=>{ window.removeEventListener("keydown", trapper); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return [ trapRef ]; } export {$24f8bdb3332f913b$export$5562a1f223a0d314 as useFocusTrap}; //# sourceMappingURL=useFocusTrap.js.map