@react-aria/focus
Version:
Spectrum UI components in React
79 lines (65 loc) • 2.28 kB
text/typescript
import {DOMAttributes} from '@react-types/shared';
import {isFocusVisible, useFocus, useFocusVisibleListener, useFocusWithin} from '@react-aria/interactions';
import {useCallback, useRef, useState} from 'react';
export interface AriaFocusRingProps {
/**
* Whether to show the focus ring when something
* inside the container element has focus (true), or
* only if the container itself has focus (false).
* @default 'false'
*/
within?: boolean,
/** Whether the element is a text input. */
isTextInput?: boolean,
/** Whether the element will be auto focused. */
autoFocus?: boolean
}
export interface FocusRingAria {
/** Whether the element is currently focused. */
isFocused: boolean,
/** Whether keyboard focus should be visible. */
isFocusVisible: boolean,
/** Props to apply to the container element with the focus ring. */
focusProps: DOMAttributes
}
/**
* Determines whether a focus ring should be shown to indicate keyboard focus.
* Focus rings are visible only when the user is interacting with a keyboard,
* not with a mouse, touch, or other input methods.
*/
export function useFocusRing(props: AriaFocusRingProps = {}): FocusRingAria {
let {
autoFocus = false,
isTextInput,
within
} = props;
let state = useRef({
isFocused: false,
isFocusVisible: autoFocus || isFocusVisible()
});
let [isFocused, setFocused] = useState(false);
let [isFocusVisibleState, setFocusVisible] = useState(() => state.current.isFocused && state.current.isFocusVisible);
let updateState = useCallback(() => setFocusVisible(state.current.isFocused && state.current.isFocusVisible), []);
let onFocusChange = useCallback(isFocused => {
state.current.isFocused = isFocused;
setFocused(isFocused);
updateState();
}, [updateState]);
useFocusVisibleListener((isFocusVisible) => {
state.current.isFocusVisible = isFocusVisible;
updateState();
}, [], {isTextInput});
let {focusProps} = useFocus({
isDisabled: within,
onFocusChange
});
let {focusWithinProps} = useFocusWithin({
isDisabled: !within,
onFocusWithinChange: onFocusChange
});
return {
isFocused,
isFocusVisible: isFocusVisibleState,
focusProps: within ? focusWithinProps : focusProps
};
}