UNPKG

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