import { focusable, isFocusable, isTabbable, tabbable } from 'tabbable' import { getDocument, isHTMLElement } from './core' import { findVisible } from './focus' function getTabbableOptions() { return { getShadowRoot: true, displayCheck: typeof ResizeObserver === 'function' && ResizeObserver.toString().includes('[native code]') ? 'full' : 'none' } as const } function toHtmlElements(nodes: ReadonlyArray): HTMLElement[] { return nodes.filter(isHTMLElement) } export function getTabbableCandidates(container: HTMLElement): HTMLElement[] { return toHtmlElements(tabbable(container, getTabbableOptions())) } export function getTabbableEdges( container: HTMLElement ): readonly [HTMLElement | undefined, HTMLElement | undefined] { const candidates = getTabbableCandidates(container) const first = findVisible(candidates, container) const last = findVisible([...candidates].reverse(), container) return [first, last] as const } export function getTabbableFrom(currentNode: HTMLElement, direction: 'next' | 'prev'): HTMLElement { if (!isTabbable(currentNode, getTabbableOptions())) { return getTabbableFromFocusable(currentNode, direction) } const doc = getDocument(currentNode) const allTabbable = toHtmlElements(tabbable(doc.body, getTabbableOptions())) if (direction === 'prev') allTabbable.reverse() const activeIndex = allTabbable.indexOf(currentNode) if (activeIndex === -1) return doc.body return allTabbable.slice(activeIndex + 1)[0] ?? doc.body } export function getTabbableFromFocusable( currentNode: HTMLElement, direction: 'next' | 'prev' ): HTMLElement { const doc = getDocument(currentNode) if (!isFocusable(currentNode, getTabbableOptions())) return doc.body const allFocusable = toHtmlElements(focusable(doc.body, getTabbableOptions())) if (direction === 'prev') allFocusable.reverse() const activeIndex = allFocusable.indexOf(currentNode) if (activeIndex === -1) return doc.body return ( allFocusable.slice(activeIndex + 1).find((node) => isTabbable(node, getTabbableOptions())) ?? doc.body ) }