132 lines
3.7 KiB
JavaScript
132 lines
3.7 KiB
JavaScript
|
// src/internal/tabbable.ts
|
||
|
var computedStyleMap = /* @__PURE__ */ new WeakMap();
|
||
|
function getCachedComputedStyle(el) {
|
||
|
let computedStyle = computedStyleMap.get(el);
|
||
|
if (!computedStyle) {
|
||
|
computedStyle = window.getComputedStyle(el, null);
|
||
|
computedStyleMap.set(el, computedStyle);
|
||
|
}
|
||
|
return computedStyle;
|
||
|
}
|
||
|
function isVisible(el) {
|
||
|
if (typeof el.checkVisibility === "function") {
|
||
|
return el.checkVisibility({ checkOpacity: false, checkVisibilityCSS: true });
|
||
|
}
|
||
|
const computedStyle = getCachedComputedStyle(el);
|
||
|
return computedStyle.visibility !== "hidden" && computedStyle.display !== "none";
|
||
|
}
|
||
|
function isOverflowingAndTabbable(el) {
|
||
|
const computedStyle = getCachedComputedStyle(el);
|
||
|
const { overflowY, overflowX } = computedStyle;
|
||
|
if (overflowY === "scroll" || overflowX === "scroll") {
|
||
|
return true;
|
||
|
}
|
||
|
if (overflowY !== "auto" || overflowX !== "auto") {
|
||
|
return false;
|
||
|
}
|
||
|
const isOverflowingY = el.scrollHeight > el.clientHeight;
|
||
|
if (isOverflowingY && overflowY === "auto") {
|
||
|
return true;
|
||
|
}
|
||
|
const isOverflowingX = el.scrollWidth > el.clientWidth;
|
||
|
if (isOverflowingX && overflowX === "auto") {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
function isTabbable(el) {
|
||
|
const tag = el.tagName.toLowerCase();
|
||
|
const tabindex = Number(el.getAttribute("tabindex"));
|
||
|
const hasTabindex = el.hasAttribute("tabindex");
|
||
|
if (hasTabindex && (isNaN(tabindex) || tabindex <= -1)) {
|
||
|
return false;
|
||
|
}
|
||
|
if (el.hasAttribute("disabled")) {
|
||
|
return false;
|
||
|
}
|
||
|
if (el.closest("[inert]")) {
|
||
|
return false;
|
||
|
}
|
||
|
if (tag === "input" && el.getAttribute("type") === "radio" && !el.hasAttribute("checked")) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!isVisible(el)) {
|
||
|
return false;
|
||
|
}
|
||
|
if ((tag === "audio" || tag === "video") && el.hasAttribute("controls")) {
|
||
|
return true;
|
||
|
}
|
||
|
if (el.hasAttribute("tabindex")) {
|
||
|
return true;
|
||
|
}
|
||
|
if (el.hasAttribute("contenteditable") && el.getAttribute("contenteditable") !== "false") {
|
||
|
return true;
|
||
|
}
|
||
|
const isNativelyTabbable = [
|
||
|
"button",
|
||
|
"input",
|
||
|
"select",
|
||
|
"textarea",
|
||
|
"a",
|
||
|
"audio",
|
||
|
"video",
|
||
|
"summary",
|
||
|
"iframe"
|
||
|
].includes(tag);
|
||
|
if (isNativelyTabbable) {
|
||
|
return true;
|
||
|
}
|
||
|
return isOverflowingAndTabbable(el);
|
||
|
}
|
||
|
function getTabbableBoundary(root) {
|
||
|
var _a, _b;
|
||
|
const tabbableElements = getTabbableElements(root);
|
||
|
const start = (_a = tabbableElements[0]) != null ? _a : null;
|
||
|
const end = (_b = tabbableElements[tabbableElements.length - 1]) != null ? _b : null;
|
||
|
return { start, end };
|
||
|
}
|
||
|
function getSlottedChildrenOutsideRootElement(slotElement, root) {
|
||
|
var _a;
|
||
|
return ((_a = slotElement.getRootNode({ composed: true })) == null ? void 0 : _a.host) !== root;
|
||
|
}
|
||
|
function getTabbableElements(root) {
|
||
|
const walkedEls = /* @__PURE__ */ new WeakMap();
|
||
|
const tabbableElements = [];
|
||
|
function walk(el) {
|
||
|
if (el instanceof Element) {
|
||
|
if (el.hasAttribute("inert") || el.closest("[inert]")) {
|
||
|
return;
|
||
|
}
|
||
|
if (walkedEls.has(el)) {
|
||
|
return;
|
||
|
}
|
||
|
walkedEls.set(el, true);
|
||
|
if (!tabbableElements.includes(el) && isTabbable(el)) {
|
||
|
tabbableElements.push(el);
|
||
|
}
|
||
|
if (el instanceof HTMLSlotElement && getSlottedChildrenOutsideRootElement(el, root)) {
|
||
|
el.assignedElements({ flatten: true }).forEach((assignedEl) => {
|
||
|
walk(assignedEl);
|
||
|
});
|
||
|
}
|
||
|
if (el.shadowRoot !== null && el.shadowRoot.mode === "open") {
|
||
|
walk(el.shadowRoot);
|
||
|
}
|
||
|
}
|
||
|
for (const e of el.children) {
|
||
|
walk(e);
|
||
|
}
|
||
|
}
|
||
|
walk(root);
|
||
|
return tabbableElements.sort((a, b) => {
|
||
|
const aTabindex = Number(a.getAttribute("tabindex")) || 0;
|
||
|
const bTabindex = Number(b.getAttribute("tabindex")) || 0;
|
||
|
return bTabindex - aTabindex;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
export {
|
||
|
getTabbableBoundary,
|
||
|
getTabbableElements
|
||
|
};
|