1 | import {DOMAttributes} from '@react-types/shared';
|
2 | import {isFocusVisible, useFocus, useFocusVisibleListener, useFocusWithin} from '@react-aria/interactions';
|
3 | import {useCallback, useRef, useState} from 'react';
|
4 |
|
5 | export interface AriaFocusRingProps {
|
6 | |
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | within?: boolean,
|
13 |
|
14 |
|
15 | isTextInput?: boolean,
|
16 |
|
17 |
|
18 | autoFocus?: boolean
|
19 | }
|
20 |
|
21 | export interface FocusRingAria {
|
22 |
|
23 | isFocused: boolean,
|
24 |
|
25 |
|
26 | isFocusVisible: boolean,
|
27 |
|
28 |
|
29 | focusProps: DOMAttributes
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export function useFocusRing(props: AriaFocusRingProps = {}): FocusRingAria {
|
38 | let {
|
39 | autoFocus = false,
|
40 | isTextInput,
|
41 | within
|
42 | } = props;
|
43 | let state = useRef({
|
44 | isFocused: false,
|
45 | isFocusVisible: autoFocus || isFocusVisible()
|
46 | });
|
47 | let [isFocused, setFocused] = useState(false);
|
48 | let [isFocusVisibleState, setFocusVisible] = useState(() => state.current.isFocused && state.current.isFocusVisible);
|
49 |
|
50 | let updateState = useCallback(() => setFocusVisible(state.current.isFocused && state.current.isFocusVisible), []);
|
51 |
|
52 | let onFocusChange = useCallback(isFocused => {
|
53 | state.current.isFocused = isFocused;
|
54 | setFocused(isFocused);
|
55 | updateState();
|
56 | }, [updateState]);
|
57 |
|
58 | useFocusVisibleListener((isFocusVisible) => {
|
59 | state.current.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: isFocusVisibleState,
|
76 | focusProps: within ? focusWithinProps : focusProps
|
77 | };
|
78 | }
|