UNPKG

@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
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'); } }); }