@ariakit/core
Version:
Ariakit core
240 lines (238 loc) • 8.2 kB
JavaScript
"use client";
import {
contains,
getActiveElement,
isFrame,
isVisible
} from "../__chunks/DTR5TSDJ.js";
import {
__spreadValues
} from "../__chunks/3YLGPPWQ.js";
// src/utils/focus.ts
var selector = "input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], button:not([disabled]), [tabindex], summary, iframe, object, embed, area[href], audio[controls], video[controls], [contenteditable]:not([contenteditable='false'])";
function hasNegativeTabIndex(element) {
const tabIndex = Number.parseInt(element.getAttribute("tabindex") || "0", 10);
return tabIndex < 0;
}
function isFocusable(element) {
if (!element.matches(selector)) return false;
if (!isVisible(element)) return false;
if (element.closest("[inert]")) return false;
return true;
}
function isTabbable(element) {
if (!isFocusable(element)) return false;
if (hasNegativeTabIndex(element)) return false;
if (!("form" in element)) return true;
if (!element.form) return true;
if (element.checked) return true;
if (element.type !== "radio") return true;
const radioGroup = element.form.elements.namedItem(element.name);
if (!radioGroup) return true;
if (!("length" in radioGroup)) return true;
const activeElement = getActiveElement(element);
if (!activeElement) return true;
if (activeElement === element) return true;
if (!("form" in activeElement)) return true;
if (activeElement.form !== element.form) return true;
if (activeElement.name !== element.name) return true;
return false;
}
function getAllFocusableIn(container, includeContainer) {
const elements = Array.from(
container.querySelectorAll(selector)
);
if (includeContainer) {
elements.unshift(container);
}
const focusableElements = elements.filter(isFocusable);
focusableElements.forEach((element, i) => {
if (isFrame(element) && element.contentDocument) {
const frameBody = element.contentDocument.body;
focusableElements.splice(i, 1, ...getAllFocusableIn(frameBody));
}
});
return focusableElements;
}
function getAllFocusable(includeBody) {
return getAllFocusableIn(document.body, includeBody);
}
function getFirstFocusableIn(container, includeContainer) {
const [first] = getAllFocusableIn(container, includeContainer);
return first || null;
}
function getFirstFocusable(includeBody) {
return getFirstFocusableIn(document.body, includeBody);
}
function getAllTabbableIn(container, includeContainer, fallbackToFocusable) {
const elements = Array.from(
container.querySelectorAll(selector)
);
const tabbableElements = elements.filter(isTabbable);
if (includeContainer && isTabbable(container)) {
tabbableElements.unshift(container);
}
tabbableElements.forEach((element, i) => {
if (isFrame(element) && element.contentDocument) {
const frameBody = element.contentDocument.body;
const allFrameTabbable = getAllTabbableIn(
frameBody,
false,
fallbackToFocusable
);
tabbableElements.splice(i, 1, ...allFrameTabbable);
}
});
if (!tabbableElements.length && fallbackToFocusable) {
return elements;
}
return tabbableElements;
}
function getAllTabbable(fallbackToFocusable) {
return getAllTabbableIn(document.body, false, fallbackToFocusable);
}
function getFirstTabbableIn(container, includeContainer, fallbackToFocusable) {
const [first] = getAllTabbableIn(
container,
includeContainer,
fallbackToFocusable
);
return first || null;
}
function getFirstTabbable(fallbackToFocusable) {
return getFirstTabbableIn(document.body, false, fallbackToFocusable);
}
function getLastTabbableIn(container, includeContainer, fallbackToFocusable) {
const allTabbable = getAllTabbableIn(
container,
includeContainer,
fallbackToFocusable
);
return allTabbable[allTabbable.length - 1] || null;
}
function getLastTabbable(fallbackToFocusable) {
return getLastTabbableIn(document.body, false, fallbackToFocusable);
}
function getNextTabbableIn(container, includeContainer, fallbackToFirst, fallbackToFocusable) {
const activeElement = getActiveElement(container);
const allFocusable = getAllFocusableIn(container, includeContainer);
const activeIndex = allFocusable.indexOf(activeElement);
const nextFocusableElements = allFocusable.slice(activeIndex + 1);
return nextFocusableElements.find(isTabbable) || (fallbackToFirst ? allFocusable.find(isTabbable) : null) || (fallbackToFocusable ? nextFocusableElements[0] : null) || null;
}
function getNextTabbable(fallbackToFirst, fallbackToFocusable) {
return getNextTabbableIn(
document.body,
false,
fallbackToFirst,
fallbackToFocusable
);
}
function getPreviousTabbableIn(container, includeContainer, fallbackToLast, fallbackToFocusable) {
const activeElement = getActiveElement(container);
const allFocusable = getAllFocusableIn(container, includeContainer).reverse();
const activeIndex = allFocusable.indexOf(activeElement);
const previousFocusableElements = allFocusable.slice(activeIndex + 1);
return previousFocusableElements.find(isTabbable) || (fallbackToLast ? allFocusable.find(isTabbable) : null) || (fallbackToFocusable ? previousFocusableElements[0] : null) || null;
}
function getPreviousTabbable(fallbackToFirst, fallbackToFocusable) {
return getPreviousTabbableIn(
document.body,
false,
fallbackToFirst,
fallbackToFocusable
);
}
function getClosestFocusable(element) {
while (element && !isFocusable(element)) {
element = element.closest(selector);
}
return element || null;
}
function hasFocus(element) {
const activeElement = getActiveElement(element);
if (!activeElement) return false;
if (activeElement === element) return true;
const activeDescendant = activeElement.getAttribute("aria-activedescendant");
if (!activeDescendant) return false;
return activeDescendant === element.id;
}
function hasFocusWithin(element) {
const activeElement = getActiveElement(element);
if (!activeElement) return false;
if (contains(element, activeElement)) return true;
const activeDescendant = activeElement.getAttribute("aria-activedescendant");
if (!activeDescendant) return false;
if (!("id" in element)) return false;
if (activeDescendant === element.id) return true;
return !!element.querySelector(`#${CSS.escape(activeDescendant)}`);
}
function focusIfNeeded(element) {
if (!hasFocusWithin(element) && isFocusable(element)) {
element.focus();
}
}
function disableFocus(element) {
var _a;
const currentTabindex = (_a = element.getAttribute("tabindex")) != null ? _a : "";
element.setAttribute("data-tabindex", currentTabindex);
element.setAttribute("tabindex", "-1");
}
function disableFocusIn(container, includeContainer) {
const tabbableElements = getAllTabbableIn(container, includeContainer);
for (const element of tabbableElements) {
disableFocus(element);
}
}
function restoreFocusIn(container) {
const elements = container.querySelectorAll("[data-tabindex]");
const restoreTabIndex = (element) => {
const tabindex = element.getAttribute("data-tabindex");
element.removeAttribute("data-tabindex");
if (tabindex) {
element.setAttribute("tabindex", tabindex);
} else {
element.removeAttribute("tabindex");
}
};
if (container.hasAttribute("data-tabindex")) {
restoreTabIndex(container);
}
for (const element of elements) {
restoreTabIndex(element);
}
}
function focusIntoView(element, options) {
if (!("scrollIntoView" in element)) {
element.focus();
} else {
element.focus({ preventScroll: true });
element.scrollIntoView(__spreadValues({ block: "nearest", inline: "nearest" }, options));
}
}
export {
disableFocus,
disableFocusIn,
focusIfNeeded,
focusIntoView,
getAllFocusable,
getAllFocusableIn,
getAllTabbable,
getAllTabbableIn,
getClosestFocusable,
getFirstFocusable,
getFirstFocusableIn,
getFirstTabbable,
getFirstTabbableIn,
getLastTabbable,
getLastTabbableIn,
getNextTabbable,
getNextTabbableIn,
getPreviousTabbable,
getPreviousTabbableIn,
hasFocus,
hasFocusWithin,
isFocusable,
isTabbable,
restoreFocusIn
};