1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | import {DOMAttributes, FocusableDOMProps, FocusableElement, FocusableProps, RefObject} from '@react-types/shared';
|
14 | import {focusSafely} from './';
|
15 | import {mergeProps, useObjectRef, useSyncRef} from '@react-aria/utils';
|
16 | import React, {ForwardedRef, MutableRefObject, ReactNode, useContext, useEffect, useRef} from 'react';
|
17 | import {useFocus, useKeyboard} from '@react-aria/interactions';
|
18 |
|
19 | export interface FocusableOptions extends FocusableProps, FocusableDOMProps {
|
20 |
|
21 | isDisabled?: boolean
|
22 | }
|
23 |
|
24 | export interface FocusableProviderProps extends DOMAttributes {
|
25 |
|
26 | children?: ReactNode
|
27 | }
|
28 |
|
29 | interface FocusableContextValue extends FocusableProviderProps {
|
30 | ref?: MutableRefObject<FocusableElement | null>
|
31 | }
|
32 |
|
33 | let FocusableContext = React.createContext<FocusableContextValue | null>(null);
|
34 |
|
35 | function useFocusableContext(ref: RefObject<FocusableElement | null>): FocusableContextValue {
|
36 | let context = useContext(FocusableContext) || {};
|
37 | useSyncRef(context, ref);
|
38 |
|
39 |
|
40 | let {ref: _, ...otherProps} = context;
|
41 | return otherProps;
|
42 | }
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | function FocusableProvider(props: FocusableProviderProps, ref: ForwardedRef<FocusableElement>) {
|
48 | let {children, ...otherProps} = props;
|
49 | let objRef = useObjectRef(ref);
|
50 | let context = {
|
51 | ...otherProps,
|
52 | ref: objRef
|
53 | };
|
54 |
|
55 | return (
|
56 | <FocusableContext.Provider value={context}>
|
57 | {children}
|
58 | </FocusableContext.Provider>
|
59 | );
|
60 | }
|
61 |
|
62 | let _FocusableProvider = React.forwardRef(FocusableProvider);
|
63 | export {_FocusableProvider as FocusableProvider};
|
64 |
|
65 | export interface FocusableAria {
|
66 |
|
67 | focusableProps: DOMAttributes
|
68 | }
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | export function useFocusable(props: FocusableOptions, domRef: RefObject<FocusableElement | null>): FocusableAria {
|
74 | let {focusProps} = useFocus(props);
|
75 | let {keyboardProps} = useKeyboard(props);
|
76 | let interactions = mergeProps(focusProps, keyboardProps);
|
77 | let domProps = useFocusableContext(domRef);
|
78 | let interactionProps = props.isDisabled ? {} : domProps;
|
79 | let autoFocusRef = useRef(props.autoFocus);
|
80 |
|
81 | useEffect(() => {
|
82 | if (autoFocusRef.current && domRef.current) {
|
83 | focusSafely(domRef.current);
|
84 | }
|
85 | autoFocusRef.current = false;
|
86 | }, [domRef]);
|
87 |
|
88 | return {
|
89 | focusableProps: mergeProps(
|
90 | {
|
91 | ...interactions,
|
92 | tabIndex: props.excludeFromTabOrder && !props.isDisabled ? -1 : undefined
|
93 | },
|
94 | interactionProps
|
95 | )
|
96 | };
|
97 | }
|