/
FocusTrap.tsx
67 lines (62 loc) · 1.97 KB
/
FocusTrap.tsx
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
import { useEffect, useRef } from 'react';
import { isBrowser } from './utils';
import {
tabTrappingKey,
candidateSelectors,
getAllTabbingElements,
} from './lib/focusTrapJs';
interface FocusTrapProps {
container?: React.RefObject<HTMLElement> | null;
initialFocusRef?: React.RefObject<HTMLElement>;
}
export const FocusTrap = ({ container, initialFocusRef }: FocusTrapProps) => {
const refLastFocus = useRef<HTMLElement | null>();
/**
* Handle focus lock on the modal
*/
useEffect(() => {
const handleKeyEvent = (event: KeyboardEvent) => {
if (container?.current) {
tabTrappingKey(event, container.current);
}
};
if (isBrowser) {
document.addEventListener('keydown', handleKeyEvent);
}
// On mount we focus on the first focusable element in the modal if there is one
if (isBrowser && container?.current) {
const savePreviousFocus = () => {
// First we save the last focused element
// only if it's a focusable element
if (
candidateSelectors.findIndex((selector) =>
document.activeElement?.matches(selector)
) !== -1
) {
refLastFocus.current = document.activeElement as HTMLElement;
}
};
if (initialFocusRef) {
savePreviousFocus();
// We need to schedule focusing on a next frame - this allows to focus on the modal root
requestAnimationFrame(() => {
initialFocusRef.current?.focus();
});
} else {
const allTabbingElements = getAllTabbingElements(container.current);
if (allTabbingElements[0]) {
savePreviousFocus();
allTabbingElements[0].focus();
}
}
}
return () => {
if (isBrowser) {
document.removeEventListener('keydown', handleKeyEvent);
// On unmount we restore the focus to the last focused element
refLastFocus.current?.focus();
}
};
}, [container, initialFocusRef]);
return null;
};