UNPKG

@dvcol/common-utils

Version:

Typescript library for common utility functions and constants

62 lines (60 loc) 2.86 kB
// 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 };