react-use-focus-trap
Version:
A react hook to trap the focus within a reference
84 lines (77 loc) • 4.82 kB
JavaScript
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