"use client"; import * as React from "react"; /** * Represents the focus handlers returned by {@link useFocusVisible}. * * @remarks * Spread these handlers onto the interactive element that should respond to keyboard * focus visibility. They coordinate transient focus styling without owning rendering. */ interface FocusVisibleProps { /** * Called when the target receives focus. */ onFocus: (event: React.FocusEvent) => void; /** * Called when the target loses focus. */ onBlur: () => void; } /** * Detects whether the current focus originated from keyboard navigation. * * @remarks * Tracks global keyboard and pointer input so consumers can render focus rings only when * users are tabbing through the interface. This mirrors the intent of `:focus-visible` * while remaining available to JavaScript-driven components and custom states. * * @returns An object containing the current focus-visible state and focus handlers to spread on the target element. * * @example * ```tsx * const {isFocusVisible, focusProps} = useFocusVisible(); * * return ( * * ); * ``` * * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible | MDN :focus-visible} */ export function useFocusVisible(): { isFocusVisible: boolean; focusProps: FocusVisibleProps; } { const [isFocusVisible, setIsFocusVisible] = React.useState(false); const isKeyboardRef = React.useRef(false); React.useEffect(() => { const onKeyDown = () => { isKeyboardRef.current = true; }; const onPointerDown = () => { isKeyboardRef.current = false; }; globalThis.document.addEventListener("keydown", onKeyDown, {capture: true}); globalThis.document.addEventListener("pointerdown", onPointerDown, {capture: true}); return () => { globalThis.document.removeEventListener("keydown", onKeyDown, {capture: true}); globalThis.document.removeEventListener("pointerdown", onPointerDown, {capture: true}); }; }, []); const focusProps = React.useMemo( () => ({ onFocus: (_event: React.FocusEvent) => { if (isKeyboardRef.current) { setIsFocusVisible(true); } }, onBlur: () => { setIsFocusVisible(false); }, }), [], ); return {isFocusVisible, focusProps}; }