-
-
Notifications
You must be signed in to change notification settings - Fork 300
/
useFocusMovement.ts
86 lines (76 loc) · 2.4 KB
/
useFocusMovement.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useEffect, useState } from "react";
import {
BaseKeyboardMovementOptions,
KeyboardMovementProviders,
useKeyboardMovement,
} from "./useKeyboardMovement";
interface KeyboardFocusOptions<
D = unknown,
CE extends HTMLElement = HTMLElement,
IE extends HTMLElement = HTMLElement
> extends BaseKeyboardMovementOptions<D, CE, IE> {
/**
* The index that should be focused by default.
*/
defaultFocusedIndex?: number;
}
/**
* This hook allows for custom keyboard focus movement using DOM focus behavior
* by actually focusing each DOM node.
*
* To use this hook, you'll want to update the container element of all the
* items to have a correct `role` applied as well as the `onKeyDown` event
* handler provided by this hook. Then, you'll want to applied a
* `ref={itemRefs[i]}` for each item within the items list so that the DOM nodes
* can be focused as needed. Unfortunately, this means that all the child items
* **must** either be an HTMLElement or the ref is forwarded down to the
* HTMLElement.
*
* @typeParam D - The type of each data item within the items list.
* @typeParam CE - The HTMLElement type of the container element that handles
* the custom keyboard movement.
* @typeParam IE - The HTMLElement type of each item within the container
* element that can be focusable.
*/
export function useFocusMovement<
D = unknown,
CE extends HTMLElement = HTMLElement,
IE extends HTMLElement = HTMLElement
>({
defaultFocusedIndex = -1,
onChange,
...options
}: KeyboardFocusOptions<D, CE, IE>): KeyboardMovementProviders<CE, IE> {
const [focusedIndex, setFocusedIndex] = useState(defaultFocusedIndex);
const [itemRefs, handleKeyDown] = useKeyboardMovement<D, CE, IE>({
...options,
focusedIndex,
onChange(data, itemRefs) {
if (onChange) {
onChange(data, itemRefs);
}
const { index } = data;
if (index === -1) {
return;
}
const item = itemRefs[index] && itemRefs[index].current;
if (item) {
item.focus();
}
setFocusedIndex(index);
},
});
useEffect(() => {
if (defaultFocusedIndex === -1) {
return;
}
const item =
itemRefs[defaultFocusedIndex] && itemRefs[defaultFocusedIndex].current;
if (item) {
item.focus();
}
// only want to trigger on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [itemRefs, handleKeyDown];
}