-
Notifications
You must be signed in to change notification settings - Fork 45.6k
/
FocusContain.js
89 lines (82 loc) · 2.23 KB
/
FocusContain.js
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
87
88
89
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {KeyboardEvent} from 'react-interactions/events/keyboard';
import React from 'react';
import {useFocusWithin} from 'react-interactions/events/focus';
import {useKeyboard} from 'react-interactions/events/keyboard';
import {
focusPrevious,
focusNext,
} from 'react-interactions/accessibility/focus-manager';
type FocusContainProps = {|
children: React.Node,
disabled?: boolean,
scopeQuery: (type: string | Object, props: Object) => boolean,
|};
const {useLayoutEffect, useRef} = React;
const FocusContainScope = React.unstable_createScope();
export default function FocusContain({
children,
disabled,
scopeQuery,
}: FocusContainProps): React.Node {
const scopeRef = useRef(null);
// This ensures tabbing works through the React tree (including Portals and Suspense nodes)
const keyboard = useKeyboard({
onKeyDown(event: KeyboardEvent): void {
if (disabled === true || event.key !== 'Tab') {
event.continuePropagation();
return;
}
const scope = scopeRef.current;
if (scope !== null) {
if (event.shiftKey) {
focusPrevious(scopeQuery, scope, event, true);
} else {
focusNext(scopeQuery, scope, event, true);
}
}
},
});
const focusWithin = useFocusWithin({
onBlurWithin: function(event) {
if (disabled === true) {
event.continuePropagation();
return;
}
const lastNode = event.target;
if (lastNode) {
requestAnimationFrame(() => {
(lastNode: any).focus();
});
}
},
});
useLayoutEffect(
() => {
const scope = scopeRef.current;
if (
scope !== null &&
disabled !== true &&
!scope.containsNode(document.activeElement)
) {
const fistElem = scope.queryFirstNode(scopeQuery);
if (fistElem !== null) {
fistElem.focus();
}
}
},
[disabled],
);
return (
<FocusContainScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
{children}
</FocusContainScope>
);
}