export const getKeyboardFocusableElements = (DOMElement: Document | HTMLElement | null = document) => { if (DOMElement === null) return [] as (HTMLElement | SVGElement)[] const focusableElements = Array.from( (DOMElement as Document | HTMLElement).querySelectorAll( 'a, button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])', ), ) if (!focusableElements.length) { return DOMElement instanceof HTMLElement ? [DOMElement] : [] } return focusableElements.filter((element) => { const isElementDisabled = element.hasAttribute('disabled') const isElementVisible = (element as HTMLElement).offsetHeight !== 0 && (element as HTMLElement).offsetWidth !== 0 const isInputElement = element instanceof HTMLInputElement const isElementRadioNotChecked = isInputElement && element.type === 'radio' && element.checked === false return !isElementDisabled && isElementVisible && !isElementRadioNotChecked }) } export const handleTrapFocus = (event: KeyboardEvent, DOMElement?: HTMLElement | null) => { if (!DOMElement) return const childFocusableElements = getKeyboardFocusableElements(DOMElement) const firstElement = childFocusableElements[0] const lastElement = childFocusableElements[childFocusableElements.length - 1] const tabForwards = !event.shiftKey && document.activeElement === lastElement const tabBackwards = event.shiftKey && document.activeElement === firstElement if (tabForwards) { // only TAB is pressed, not SHIFT simultaneously // Prevent default behavior of keydown on TAB (i.e. focus next element) event.preventDefault() firstElement?.focus() } else if (tabBackwards) { // TAB and SHIFT are pressed simultaneously event.preventDefault() lastElement?.focus() } } export const focusOnDOMElement = (DOMElement: Document | HTMLElement | null, inputTag = 'input'): void => { if (!DOMElement) return if (DOMElement instanceof HTMLInputElement) { DOMElement.focus() return } const inputElement = DOMElement.querySelector(inputTag) as HTMLInputElement | null if (!inputElement) return inputElement.focus() }