@dvcol/common-utils
Version:
Typescript library for common utility functions and constants
62 lines (60 loc) • 2.86 kB
JavaScript
// lib/common/utils/element.utils.ts
var focusableElementSelectors = Object.freeze([
"button:not([disabled])",
"[href]",
// Ensures only real links are focusable
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])",
"[tabindex]"
// Includes manually focusable elements
]);
var tabFocusableElementSelectors = Object.freeze([
'button:not([disabled], [tabindex="-1"])',
'[href]:not([tabindex="-1"])',
'input:not([disabled], [tabindex="-1"])',
'select:not([disabled], [tabindex="-1"])',
'textarea:not([disabled], [tabindex="-1"])',
'[tabindex]:not([tabindex="-1"])'
]);
var getFocusableElement = (element, targets = tabFocusableElementSelectors) => element?.querySelector(targets.join(","));
var getFocusableElements = (element, targets = tabFocusableElementSelectors) => {
const elements = element?.querySelectorAll(targets.join(","));
if (!elements) return;
return Array.from(elements).sort((a, b) => (a.tabIndex || 0) - (b.tabIndex || 0));
};
var getLastFocusableElement = (element, targets = tabFocusableElementSelectors) => {
const elements = getFocusableElements(element, targets);
if (!elements || !elements.length) return;
return elements[elements.length - 1];
};
var clickableTags = /* @__PURE__ */ new Set(["A", "BUTTON", "INPUT", "SELECT", "TEXTAREA", "LABEL", "SUMMARY", "OPTION", "DETAILS", "VIDEO", "AUDIO"]);
var isClickable = (element) => {
if (element.hasAttribute("disabled") && ["", "true", null].includes(element.getAttribute("disabled"))) return false;
if (element.hasAttribute("readonly") && ["", "true", null].includes(element.getAttribute("readonly"))) return false;
if (element.tabIndex < 0) return false;
if (element.hasAttribute("role") && ["button", "link"].includes(element.getAttribute("role")?.toLowerCase())) return true;
return clickableTags.has(element.tagName) || element.hasAttribute("onclick") || element.hasAttribute("onkeydown") || element.isContentEditable || element.tabIndex >= 0;
};
var getClickableAncestor = (element, boundary, selector = isClickable) => {
if (!element) return;
if (typeof boundary === "function" && boundary() === element) return;
if (boundary === element) return;
if (selector(element)) return element;
if (!element.parentElement) return;
return getClickableAncestor(element.parentElement, boundary, selector);
};
var clickableSelector = "a, button, input, textarea, select, details, summary, [role='button'], [role='link'], [onclick], audio, video, [tabindex]:not([tabindex='-1'])";
var closestClickableElement = (element) => element.closest(clickableSelector);
export {
focusableElementSelectors,
tabFocusableElementSelectors,
getFocusableElement,
getFocusableElements,
getLastFocusableElement,
clickableTags,
isClickable,
getClickableAncestor,
clickableSelector,
closestClickableElement
};