You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
65 lines
2.0 KiB
65 lines
2.0 KiB
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 | SVGElement>): 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
|
|
)
|
|
}
|