import { isHTMLElement } from '@fluentui/react-utilities'; import { KEYBORG_FOCUSIN, createKeyborg, disposeKeyborg } from 'keyborg'; import { FOCUS_VISIBLE_ATTR } from './constants'; /** * @internal * @param scope - Applies the ponyfill to all DOM children * @param targetWindow - window */ export function applyFocusVisiblePolyfill(scope, targetWindow) { if (alreadyInScope(scope)) { // Focus visible polyfill already applied at this scope return ()=>undefined; } const state = { current: undefined }; const keyborg = createKeyborg(targetWindow); function registerElementIfNavigating(el) { if (keyborg.isNavigatingWithKeyboard() && isHTMLElement(el)) { state.current = el; el.setAttribute(FOCUS_VISIBLE_ATTR, ''); } } function disposeCurrentElement() { if (state.current) { state.current.removeAttribute(FOCUS_VISIBLE_ATTR); state.current = undefined; } } // When navigation mode changes remove the focus-visible selector keyborg.subscribe((isNavigatingWithKeyboard)=>{ if (!isNavigatingWithKeyboard) { disposeCurrentElement(); } }); // Keyborg's focusin event is delegated so it's only registered once on the window // and contains metadata about the focus event const keyborgListener = (e)=>{ disposeCurrentElement(); const target = e.composedPath()[0]; registerElementIfNavigating(target); }; // Make sure that when focus leaves the scope, the focus visible class is removed const blurListener = (e)=>{ if (!e.relatedTarget || isHTMLElement(e.relatedTarget) && !scope.contains(e.relatedTarget)) { disposeCurrentElement(); } }; scope.addEventListener(KEYBORG_FOCUSIN, keyborgListener); scope.addEventListener('focusout', blurListener); scope.focusVisible = true; if (scope.contains(targetWindow.document.activeElement)) { registerElementIfNavigating(targetWindow.document.activeElement); } // Return disposer return ()=>{ disposeCurrentElement(); scope.removeEventListener(KEYBORG_FOCUSIN, keyborgListener); scope.removeEventListener('focusout', blurListener); delete scope.focusVisible; disposeKeyborg(keyborg); }; } function alreadyInScope(el) { if (!el) { return false; } if (el.focusVisible) { return true; } return alreadyInScope(el === null || el === void 0 ? void 0 : el.parentElement); }