1 |
|
2 | import * as React from 'react';
|
3 | import * as ReactDOM from 'react-dom';
|
4 | import PropTypes from 'prop-types';
|
5 | import ownerDocument from '../utils/ownerDocument';
|
6 | import useForkRef from '../utils/useForkRef';
|
7 | import { exactProp } from '@material-ui/utils';
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | function Unstable_TrapFocus(props) {
|
13 | const {
|
14 | children,
|
15 | disableAutoFocus = false,
|
16 | disableEnforceFocus = false,
|
17 | disableRestoreFocus = false,
|
18 | getDoc,
|
19 | isEnabled,
|
20 | open
|
21 | } = props;
|
22 | const ignoreNextEnforceFocus = React.useRef();
|
23 | const sentinelStart = React.useRef(null);
|
24 | const sentinelEnd = React.useRef(null);
|
25 | const nodeToRestore = React.useRef();
|
26 | const rootRef = React.useRef(null);
|
27 |
|
28 | const handleOwnRef = React.useCallback(instance => {
|
29 |
|
30 | rootRef.current = ReactDOM.findDOMNode(instance);
|
31 | }, []);
|
32 | const handleRef = useForkRef(children.ref, handleOwnRef);
|
33 | const prevOpenRef = React.useRef();
|
34 | React.useEffect(() => {
|
35 | prevOpenRef.current = open;
|
36 | }, [open]);
|
37 |
|
38 | if (!prevOpenRef.current && open && typeof window !== 'undefined') {
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | nodeToRestore.current = getDoc().activeElement;
|
48 | }
|
49 |
|
50 | React.useEffect(() => {
|
51 | if (!open) {
|
52 | return;
|
53 | }
|
54 |
|
55 | const doc = ownerDocument(rootRef.current);
|
56 |
|
57 | if (!disableAutoFocus && rootRef.current && !rootRef.current.contains(doc.activeElement)) {
|
58 | if (!rootRef.current.hasAttribute('tabIndex')) {
|
59 | if (process.env.NODE_ENV !== 'production') {
|
60 | console.error(['Material-UI: The modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n'));
|
61 | }
|
62 |
|
63 | rootRef.current.setAttribute('tabIndex', -1);
|
64 | }
|
65 |
|
66 | rootRef.current.focus();
|
67 | }
|
68 |
|
69 | const contain = () => {
|
70 | const {
|
71 | current: rootElement
|
72 | } = rootRef;
|
73 |
|
74 |
|
75 | if (rootElement === null) {
|
76 | return;
|
77 | }
|
78 |
|
79 | if (!doc.hasFocus() || disableEnforceFocus || !isEnabled() || ignoreNextEnforceFocus.current) {
|
80 | ignoreNextEnforceFocus.current = false;
|
81 | return;
|
82 | }
|
83 |
|
84 | if (rootRef.current && !rootRef.current.contains(doc.activeElement)) {
|
85 | rootRef.current.focus();
|
86 | }
|
87 | };
|
88 |
|
89 | const loopFocus = event => {
|
90 |
|
91 | if (disableEnforceFocus || !isEnabled() || event.keyCode !== 9) {
|
92 | return;
|
93 | }
|
94 |
|
95 |
|
96 | if (doc.activeElement === rootRef.current) {
|
97 |
|
98 |
|
99 | ignoreNextEnforceFocus.current = true;
|
100 |
|
101 | if (event.shiftKey) {
|
102 | sentinelEnd.current.focus();
|
103 | } else {
|
104 | sentinelStart.current.focus();
|
105 | }
|
106 | }
|
107 | };
|
108 |
|
109 | doc.addEventListener('focus', contain, true);
|
110 | doc.addEventListener('keydown', loopFocus, true);
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | const interval = setInterval(() => {
|
117 | contain();
|
118 | }, 50);
|
119 | return () => {
|
120 | clearInterval(interval);
|
121 | doc.removeEventListener('focus', contain, true);
|
122 | doc.removeEventListener('keydown', loopFocus, true);
|
123 |
|
124 | if (!disableRestoreFocus) {
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | if (nodeToRestore.current && nodeToRestore.current.focus) {
|
130 | nodeToRestore.current.focus();
|
131 | }
|
132 |
|
133 | nodeToRestore.current = null;
|
134 | }
|
135 | };
|
136 | }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open]);
|
137 | return React.createElement(React.Fragment, null, React.createElement("div", {
|
138 | tabIndex: 0,
|
139 | ref: sentinelStart,
|
140 | "data-test": "sentinelStart"
|
141 | }), React.cloneElement(children, {
|
142 | ref: handleRef
|
143 | }), React.createElement("div", {
|
144 | tabIndex: 0,
|
145 | ref: sentinelEnd,
|
146 | "data-test": "sentinelEnd"
|
147 | }));
|
148 | }
|
149 |
|
150 | process.env.NODE_ENV !== "production" ? Unstable_TrapFocus.propTypes = {
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | |
157 |
|
158 |
|
159 | children: PropTypes.node,
|
160 |
|
161 | |
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | disableAutoFocus: PropTypes.bool,
|
170 |
|
171 | |
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | disableEnforceFocus: PropTypes.bool,
|
178 |
|
179 | |
180 |
|
181 |
|
182 |
|
183 | disableRestoreFocus: PropTypes.bool,
|
184 |
|
185 | |
186 |
|
187 |
|
188 |
|
189 | getDoc: PropTypes.func.isRequired,
|
190 |
|
191 | |
192 |
|
193 |
|
194 |
|
195 | isEnabled: PropTypes.func.isRequired,
|
196 |
|
197 | |
198 |
|
199 |
|
200 | open: PropTypes.bool.isRequired
|
201 | } : void 0;
|
202 |
|
203 | if (process.env.NODE_ENV !== 'production') {
|
204 |
|
205 | Unstable_TrapFocus['propTypes' + ''] = exactProp(Unstable_TrapFocus.propTypes);
|
206 | }
|
207 |
|
208 | export default Unstable_TrapFocus; |
\ | No newline at end of file |