UNPKG

2.33 kBPlain TextView Raw
1import {DOMAttributes} from '@react-types/shared';
2import {isFocusVisible, useFocus, useFocusVisibleListener, useFocusWithin} from '@react-aria/interactions';
3import {useCallback, useState} from 'react';
4import {useRef} from 'react';
5
6export interface AriaFocusRingProps {
7 /**
8 * Whether to show the focus ring when something
9 * inside the container element has focus (true), or
10 * only if the container itself has focus (false).
11 * @default 'false'
12 */
13 within?: boolean,
14
15 /** Whether the element is a text input. */
16 isTextInput?: boolean,
17
18 /** Whether the element will be auto focused. */
19 autoFocus?: boolean
20}
21
22export interface FocusRingAria {
23 /** Whether the element is currently focused. */
24 isFocused: boolean,
25
26 /** Whether keyboard focus should be visible. */
27 isFocusVisible: boolean,
28
29 /** Props to apply to the container element with the focus ring. */
30 focusProps: DOMAttributes
31}
32
33/**
34 * Determines whether a focus ring should be shown to indicate keyboard focus.
35 * Focus rings are visible only when the user is interacting with a keyboard,
36 * not with a mouse, touch, or other input methods.
37 */
38export function useFocusRing(props: AriaFocusRingProps = {}): FocusRingAria {
39 let {
40 autoFocus = false,
41 isTextInput,
42 within
43 } = props;
44 let state = useRef({
45 isFocused: false,
46 isFocusVisible: autoFocus || isFocusVisible()
47 });
48 let [isFocused, setFocused] = useState(false);
49 let [isFocusVisibleState, setFocusVisible] = useState(() => state.current.isFocused && state.current.isFocusVisible);
50
51 let updateState = useCallback(() => setFocusVisible(state.current.isFocused && state.current.isFocusVisible), []);
52
53 let onFocusChange = useCallback(isFocused => {
54 state.current.isFocused = isFocused;
55 setFocused(isFocused);
56 updateState();
57 }, [updateState]);
58
59 useFocusVisibleListener((isFocusVisible) => {
60 state.current.isFocusVisible = isFocusVisible;
61 updateState();
62 }, [], {isTextInput});
63
64 let {focusProps} = useFocus({
65 isDisabled: within,
66 onFocusChange
67 });
68
69 let {focusWithinProps} = useFocusWithin({
70 isDisabled: !within,
71 onFocusWithinChange: onFocusChange
72 });
73
74 return {
75 isFocused,
76 isFocusVisible: state.current.isFocused && isFocusVisibleState,
77 focusProps: within ? focusWithinProps : focusProps
78 };
79}