in packages/component/src/hooks/internal/useObserveFocusVisible.ts [74:206]
function useObserveFocusVisibleForLegacyBrowsers(
targetRef: RefObject<HTMLElement>,
onFocusVisibleRef: MutableRefObject<() => void>
) {
// This polyfill algorithm is adopted from https://github.com/WICG/focus-visible.
const blurSinceRef = useRef(0);
const hadKeyboardEventRef = useRef(true);
const hasFocusVisibleRef = useRef(false);
const eventSubscription = useMemo(
() =>
createEventSubscription(
document,
[
'mousemove',
'mousedown',
'mouseup',
'pointermove',
'pointerdown',
'pointerup',
'touchmove',
'touchstart',
'touchend'
],
event => {
if ((event.target as HTMLElement).nodeName?.toLowerCase() !== 'html') {
hadKeyboardEventRef.current = false;
eventSubscription.pause();
}
}
),
[hadKeyboardEventRef]
);
const setHasFocusVisible = useCallback(
nextHasFocusVisible => {
if (hasFocusVisibleRef.current !== nextHasFocusVisible) {
hasFocusVisibleRef.current = nextHasFocusVisible;
nextHasFocusVisible && onFocusVisibleRef?.current();
}
},
[hasFocusVisibleRef, onFocusVisibleRef]
);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.altKey || event.ctrlKey || event.metaKey) {
return;
}
if (event.target === targetRef.current) {
setHasFocusVisible(true);
}
hadKeyboardEventRef.current = true;
},
[hadKeyboardEventRef, setHasFocusVisible, targetRef]
);
const handlePointerDown = useCallback(() => {
hadKeyboardEventRef.current = false;
}, [hadKeyboardEventRef]);
const handleFocus = useCallback(
({ target }: Event) => {
target === targetRef.current &&
(hadKeyboardEventRef.current || focusTriggersKeyboardModality(target as HTMLInputElement)) &&
setHasFocusVisible(true);
},
[hadKeyboardEventRef, setHasFocusVisible, targetRef]
);
const handleBlur = useCallback(
(event: Event) => {
if (event.target === targetRef.current && hasFocusVisibleRef.current) {
blurSinceRef.current = Date.now();
setHasFocusVisible(false);
}
},
[blurSinceRef, hasFocusVisibleRef, setHasFocusVisible, targetRef]
);
const handleVisibilityChange = useCallback(() => {
if (document.visibilityState === 'hidden') {
// The element is blurred due to "visibilityState" set to "hidden".
// 100ms is referenced from the WICG polyfill.
// eslint-disable-next-line no-magic-numbers
if (Date.now() - blurSinceRef.current < 100) {
hadKeyboardEventRef.current = true;
}
eventSubscription.resume();
}
}, [blurSinceRef, eventSubscription, hadKeyboardEventRef]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown, true);
document.addEventListener('mousedown', handlePointerDown, true);
document.addEventListener('pointerdown', handlePointerDown, true);
document.addEventListener('touchstart', handlePointerDown, true);
document.addEventListener('visibilitychange', handleVisibilityChange, true);
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('mousedown', handlePointerDown);
document.removeEventListener('pointerdown', handlePointerDown);
document.removeEventListener('touchstart', handlePointerDown);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [handleKeyDown, handlePointerDown, handleVisibilityChange]);
useEffect(() => {
const { current: target } = targetRef;
target.addEventListener('blur', handleBlur, true);
target.addEventListener('focus', handleFocus, true);
return () => {
target.removeEventListener('blur', handleBlur);
target.removeEventListener('focus', handleFocus);
};
// We specifically add "targetRef.current" here.
// If the target element changed, we should reattach our event listeners.
}, [handleBlur, handleFocus, targetRef]);
useEffect(() => {
eventSubscription.resume();
return () => eventSubscription.pause();
}, [eventSubscription]);
}