UNPKG

@kubit-ui-web/react-components

Version:

Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience

174 lines 7.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.focusFirstDescendantV2 = exports.focusFirstDescendant = exports.focusLastDescendantOrElement = exports.focusElementOrFirstDescendant = exports.focusPreviousFocusableElement = exports.focusNextFocusableElement = exports.getFocusableDescendantsV2 = exports.getFocusableDescendants = void 0; const FOCUSABLE_QUERY_SELECTOR = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]):not([tabindex="-1"]), summary, [tabindex]:not([tabindex="-1"])'; const getFocusableDescendants = (element) => { const focusableNodes = Array.from(element.querySelectorAll(FOCUSABLE_QUERY_SELECTOR)).filter(node => !node.hasAttribute('aria-disabled:true') && !node.getAttribute('aria-hidden:true') && !node.hasAttribute('disabled')); return focusableNodes?.length ? focusableNodes : false; }; exports.getFocusableDescendants = getFocusableDescendants; /** * Retrieves an array of focusable descendants within a given element. * * @param element - The element to search for focusable descendants. * @param elementsToOmit - Optional array of elements to omit from the search. * @returns An array of HTMLElements representing the focusable descendants. */ const getFocusableDescendantsV2 = ({ element, elementsToOmit, }) => { const focusableNodes = Array.from(element.querySelectorAll(FOCUSABLE_QUERY_SELECTOR)).filter(node => { // It is not focusable if it is disabled if (node.getAttribute('aria-disabled') === 'true' || node.hasAttribute('disabled')) { return false; } // It is not focusable if it is aria-hidden if (node.getAttribute('aria-hidden') === 'true') { return false; } // It is not focusable if it is inside elements to omit if (elementsToOmit?.some(elementToOmit => elementToOmit.contains(node))) { return false; } // It is not focusable if it is not a summary element and it is inside details closed element if (node.tagName !== 'SUMMARY' && node.closest('details:not([open])')) { return false; } return true; }); return focusableNodes; }; exports.getFocusableDescendantsV2 = getFocusableDescendantsV2; /** * Find the next focusable element (without looking to children) * Return true when a focusable element has been found and focused * @param element * @returns boolean */ const focusNextFocusableElement = (element) => { let focusableElement = element; let foundFocusableElement = false; // Search next focusable element while (focusableElement) { while (focusableElement.nextElementSibling) { focusableElement = focusableElement.nextElementSibling; if (isElementOrSomeChildrenFocusable(focusableElement)) { foundFocusableElement = true; break; } } if (foundFocusableElement) { break; } focusableElement = focusableElement.parentNode; } // If next focusable element has been found if (foundFocusableElement) { (0, exports.focusElementOrFirstDescendant)(focusableElement); } return foundFocusableElement; }; exports.focusNextFocusableElement = focusNextFocusableElement; /** * Find the previous focusable element (without looking to children) * Return true when a focusable element has been found and focused * @param element * @returns boolean */ const focusPreviousFocusableElement = (element) => { let focusableElement = element; let foundFocusableElement = false; let focusableElementIsParent = false; while (focusableElement) { while (focusableElement.previousElementSibling) { focusableElement = focusableElement.previousElementSibling; if (isElementOrSomeChildrenFocusable(focusableElement)) { foundFocusableElement = true; break; } } if (foundFocusableElement) { break; } focusableElement = focusableElement.parentNode; // Father is the previous focusable element if (isElementFocusable(focusableElement)) { foundFocusableElement = true; focusableElementIsParent = true; break; } } if (foundFocusableElement) { if (focusableElementIsParent) { focusableElement.focus(); } else { (0, exports.focusLastDescendantOrElement)(focusableElement); } } return foundFocusableElement; }; exports.focusPreviousFocusableElement = focusPreviousFocusableElement; const isElementFocusable = (element) => { return (!element.hasAttribute('aria-disabled:true') && !element.getAttribute('aria-hidden:true') && element.matches(FOCUSABLE_QUERY_SELECTOR)); }; const isSomeChildrenFocusable = (element) => { const focusableElements = (0, exports.getFocusableDescendants)(element); return !!(typeof focusableElements !== 'boolean' && focusableElements?.length); }; const isElementOrSomeChildrenFocusable = (element) => { return isElementFocusable(element) || isSomeChildrenFocusable(element); }; const focusElementOrFirstDescendant = (element) => { if (isElementFocusable(element)) { element.focus(); return; } (0, exports.focusFirstDescendant)(element); }; exports.focusElementOrFirstDescendant = focusElementOrFirstDescendant; const focusLastDescendantOrElement = (element) => { const focusableElements = (0, exports.getFocusableDescendants)(element); if (typeof focusableElements !== 'boolean' && focusableElements?.length) { const focusableElement = focusableElements[focusableElements.length - 1]; focusableElement.focus(); return; } if (isElementFocusable(element)) { element.focus(); } }; exports.focusLastDescendantOrElement = focusLastDescendantOrElement; const focusFirstDescendant = (element) => { if (element?.childNodes?.length) { const focusableElements = (0, exports.getFocusableDescendants)(element); if (typeof focusableElements !== 'boolean' && focusableElements?.length) { focusableElements.some(node => { node.focus(); return document.activeElement === node; }); } } }; exports.focusFirstDescendant = focusFirstDescendant; /** * Sets focus on the first focusable descendant of the given element. * * @param {Object} options - The options for focusing the first descendant. * @param {HTMLElement} options.element - The element on which to set focus. * @param {HTMLElement[]} [options.elementsToOmit] - An optional array of elements to omit from focus. * * @returns {void} */ const focusFirstDescendantV2 = ({ element, elementsToOmit, }) => { if (element?.childNodes?.length) { const focusableElements = (0, exports.getFocusableDescendantsV2)({ element, elementsToOmit }); if (focusableElements.length) { focusableElements[0].focus(); } } }; exports.focusFirstDescendantV2 = focusFirstDescendantV2; //# sourceMappingURL=focusHandlers.js.map