Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
1import { isFocusTarget, getActive } from './utils/focus';
2import { useLayoutEffect } from './utils/react';
3import { click } from './utils/click';
4import { contains, isInputElement } from './utils/element';
5import { makePriorityHook } from './usePriority';
6import { Ref } from './types';
7
8const usePriority = makePriorityHook();
9
10export interface OptionFocusOptions {
11 disabled?: boolean;
12}
13
14export function useOptionFocus<T extends HTMLElement>(
15 ref: Ref<T>,
16 options?: OptionFocusOptions
17) {
18 const disabled = !!(options && options.disabled);
19 const hasPriority = usePriority(ref, disabled);
20
21 useLayoutEffect(() => {
22 const { current: element } = ref;
23 // NOTE: This behaviour isn't necessary for input elements
24 if (!element || disabled || isInputElement(element)) return;
25
26 function onKey(event: KeyboardEvent) {
27 if (!element || event.defaultPrevented || event.isComposing) return;
28
29 const active = getActive();
30 if (!isFocusTarget(element) || !contains(active, element)) {
31 // Do nothing if the current item is not a target or not focused
32 return;
33 } else if (event.code === 'Space' || event.code === 'Enter') {
34 event.preventDefault();
35 click(element);
36 }
37 }
38
39 element.addEventListener('keydown', onKey);
40 return () => {
41 element.removeEventListener('keydown', onKey);
42 };
43 }, [ref.current, disabled, hasPriority]);
44}