@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
JavaScript
;
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