const ION_FOCUSED = 'ion-focused'; const ION_FOCUSABLE = 'ion-focusable'; const FOCUS_KEYS = [ 'Tab', 'ArrowDown', 'Space', 'Escape', ' ', 'Shift', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Home', 'End', ]; export interface FocusVisibleUtility { destroy: () => void; setFocus: (elements: Element[]) => void; } export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => { let currentFocus: Element[] = []; let keyboardMode = true; const ref = rootEl ? rootEl.shadowRoot! : document; const root = rootEl ? rootEl : document.body; const setFocus = (elements: Element[]) => { currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED)); elements.forEach((el) => el.classList.add(ION_FOCUSED)); currentFocus = elements; }; const pointerDown = () => { keyboardMode = false; setFocus([]); }; const onKeydown = (ev: Event) => { keyboardMode = FOCUS_KEYS.includes((ev as KeyboardEvent).key); if (!keyboardMode) { setFocus([]); } }; const onFocusin = (ev: Event) => { if (keyboardMode && ev.composedPath !== undefined) { const toFocus = ev.composedPath().filter((el: any) => { // TODO(FW-2832): type if (el.classList) { return el.classList.contains(ION_FOCUSABLE); } return false; }) as Element[]; setFocus(toFocus); } }; const onFocusout = () => { if (ref.activeElement === root) { setFocus([]); } }; ref.addEventListener('keydown', onKeydown); ref.addEventListener('focusin', onFocusin); ref.addEventListener('focusout', onFocusout); ref.addEventListener('touchstart', pointerDown, { passive: true }); ref.addEventListener('mousedown', pointerDown); const destroy = () => { ref.removeEventListener('keydown', onKeydown); ref.removeEventListener('focusin', onFocusin); ref.removeEventListener('focusout', onFocusout); ref.removeEventListener('touchstart', pointerDown); ref.removeEventListener('mousedown', pointerDown); }; return { destroy, setFocus, }; };