import { tabbable } from 'tabbable' const TAB_KEY = 9 function getNextActiveElement(node, startNode = null) { const tabbables = tabbable(node.parentNode) const nodeToStartAt = startNode || node const nextTabbable = tabbables[tabbables.indexOf(nodeToStartAt) + 1] if (!nextTabbable && node !== document.body) { return getNextActiveElement(node.parentNode, nodeToStartAt) } return nextTabbable } class RewireTabOrder { tabbables = [] firstFocusableElement: HTMLElement = null lastFocusableElement: HTMLElement = null originalActiveElement: HTMLElement = null nextActiveElement: HTMLElement = null constructor(node) { if (node) { this.tabbables = tabbable(node) if (this.tabbables.length > 0) { this.init() } } } init() { this.firstFocusableElement = this.tabbables[0] this.lastFocusableElement = this.tabbables[this.tabbables.length - 1] this.originalActiveElement = document.activeElement as HTMLElement this.nextActiveElement = getNextActiveElement(this.originalActiveElement) this.originalActiveElement.addEventListener( 'keydown', this.moveFocusToTargetFromTriggerElement ) this.firstFocusableElement.addEventListener( 'keydown', this.moveFocusFromTargetToTriggerElement ) if (this.nextActiveElement) { this.nextActiveElement.addEventListener( 'keydown', this.moveFocusToTargetFromNextElement ) this.lastFocusableElement.addEventListener( 'keydown', this.moveFocusFromTargetToNextElement ) } } destroy() { if (this.tabbables.length === 0) { return } this.originalActiveElement.removeEventListener( 'keydown', this.moveFocusToTargetFromTriggerElement ) this.firstFocusableElement.removeEventListener( 'keydown', this.moveFocusFromTargetToTriggerElement ) if (this.nextActiveElement) { this.nextActiveElement.removeEventListener( 'keydown', this.moveFocusToTargetFromNextElement ) this.lastFocusableElement.removeEventListener( 'keydown', this.moveFocusFromTargetToNextElement ) } } focusOriginalActiveElement = () => { this.originalActiveElement.focus() } focusNextActiveElement = () => { this.nextActiveElement.focus() } moveFocusToTargetFromTriggerElement = (event) => { if (!event.shiftKey && event.keyCode === TAB_KEY) { event.preventDefault() this.firstFocusableElement.focus() } } moveFocusFromTargetToTriggerElement = (event) => { if ( event.target === this.firstFocusableElement && event.shiftKey && event.keyCode === TAB_KEY ) { event.preventDefault() this.originalActiveElement.focus() } } moveFocusToTargetFromNextElement = (event) => { if (event.shiftKey && event.keyCode === TAB_KEY) { event.preventDefault() this.lastFocusableElement.focus() } } moveFocusFromTargetToNextElement = (event) => { if (!event.shiftKey && event.keyCode === TAB_KEY) { event.preventDefault() this.nextActiveElement.focus() } } } export default (node) => { const unwireTabOrder = new RewireTabOrder(node) return () => unwireTabOrder.destroy() }