@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
74 lines • 2.77 kB
JavaScript
import { tabbable } from 'tabbable';
import { activeElement, contains, getDocument } from "./element.js";
export const getTabbableOptions = () => ({
getShadowRoot: true,
displayCheck:
// JSDOM does not support the `tabbable` library. To solve this we can
// check if `ResizeObserver` is a real function (not polyfilled), which
// determines if the current environment is JSDOM-like.
typeof ResizeObserver === 'function' && ResizeObserver.toString().includes('[native code]') ? 'full' : 'none'
});
function getTabbableIn(container, dir) {
const list = tabbable(container, getTabbableOptions());
const len = list.length;
if (len === 0) {
return undefined;
}
const active = activeElement(getDocument(container));
const index = list.indexOf(active);
// eslint-disable-next-line no-nested-ternary
const nextIndex = index === -1 ? dir === 1 ? 0 : len - 1 : index + dir;
return list[nextIndex];
}
export function getNextTabbable(referenceElement) {
return getTabbableIn(getDocument(referenceElement).body, 1) || referenceElement;
}
export function getPreviousTabbable(referenceElement) {
return getTabbableIn(getDocument(referenceElement).body, -1) || referenceElement;
}
function getTabbableNearElement(referenceElement, dir) {
if (!referenceElement) {
return null;
}
const list = tabbable(getDocument(referenceElement).body, getTabbableOptions());
const elementCount = list.length;
if (elementCount === 0) {
return null;
}
const index = list.indexOf(referenceElement);
if (index === -1) {
return null;
}
const nextIndex = (index + dir + elementCount) % elementCount;
return list[nextIndex];
}
export function getTabbableAfterElement(referenceElement) {
return getTabbableNearElement(referenceElement, 1);
}
export function getTabbableBeforeElement(referenceElement) {
return getTabbableNearElement(referenceElement, -1);
}
export function isOutsideEvent(event, container) {
const containerElement = container || event.currentTarget;
const relatedTarget = event.relatedTarget;
return !relatedTarget || !contains(containerElement, relatedTarget);
}
export function disableFocusInside(container) {
const tabbableElements = tabbable(container, getTabbableOptions());
tabbableElements.forEach(element => {
element.dataset.tabindex = element.getAttribute('tabindex') || '';
element.setAttribute('tabindex', '-1');
});
}
export function enableFocusInside(container) {
const elements = container.querySelectorAll('[data-tabindex]');
elements.forEach(element => {
const tabindex = element.dataset.tabindex;
delete element.dataset.tabindex;
if (tabindex) {
element.setAttribute('tabindex', tabindex);
} else {
element.removeAttribute('tabindex');
}
});
}