UNPKG

3.25 kBTypeScriptView Raw
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13import {DOMAttributes, FocusableDOMProps, FocusableElement, FocusableProps, RefObject} from '@react-types/shared';
14import {focusSafely} from './';
15import {mergeProps, useObjectRef, useSyncRef} from '@react-aria/utils';
16import React, {ForwardedRef, MutableRefObject, ReactNode, useContext, useEffect, useRef} from 'react';
17import {useFocus, useKeyboard} from '@react-aria/interactions';
18
19export interface FocusableOptions extends FocusableProps, FocusableDOMProps {
20 /** Whether focus should be disabled. */
21 isDisabled?: boolean
22}
23
24export interface FocusableProviderProps extends DOMAttributes {
25 /** The child element to provide DOM props to. */
26 children?: ReactNode
27}
28
29interface FocusableContextValue extends FocusableProviderProps {
30 ref?: MutableRefObject<FocusableElement | null>
31}
32
33let FocusableContext = React.createContext<FocusableContextValue | null>(null);
34
35function useFocusableContext(ref: RefObject<FocusableElement | null>): FocusableContextValue {
36 let context = useContext(FocusableContext) || {};
37 useSyncRef(context, ref);
38
39 // eslint-disable-next-line
40 let {ref: _, ...otherProps} = context;
41 return otherProps;
42}
43
44/**
45 * Provides DOM props to the nearest focusable child.
46 */
47function 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
62let _FocusableProvider = React.forwardRef(FocusableProvider);
63export {_FocusableProvider as FocusableProvider};
64
65export interface FocusableAria {
66 /** Props for the focusable element. */
67 focusableProps: DOMAttributes
68}
69
70/**
71 * Used to make an element focusable and capable of auto focus.
72 */
73export 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}