1 |
|
2 | import * as React from 'react';
|
3 | import PropTypes from 'prop-types';
|
4 | import { exactProp, elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument } from '@mui/utils';
|
5 | import { jsx as _jsx } from "react/jsx-runtime";
|
6 | import { jsxs as _jsxs } from "react/jsx-runtime";
|
7 |
|
8 | var candidatesSelector = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])'].join(',');
|
9 | function getTabIndex(node) {
|
10 | var tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10);
|
11 | if (!Number.isNaN(tabindexAttr)) {
|
12 | return tabindexAttr;
|
13 | }
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | if (node.contentEditable === 'true' || (node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) {
|
24 | return 0;
|
25 | }
|
26 | return node.tabIndex;
|
27 | }
|
28 | function isNonTabbableRadio(node) {
|
29 | if (node.tagName !== 'INPUT' || node.type !== 'radio') {
|
30 | return false;
|
31 | }
|
32 | if (!node.name) {
|
33 | return false;
|
34 | }
|
35 | var getRadio = function getRadio(selector) {
|
36 | return node.ownerDocument.querySelector("input[type=\"radio\"]".concat(selector));
|
37 | };
|
38 | var roving = getRadio("[name=\"".concat(node.name, "\"]:checked"));
|
39 | if (!roving) {
|
40 | roving = getRadio("[name=\"".concat(node.name, "\"]"));
|
41 | }
|
42 | return roving !== node;
|
43 | }
|
44 | function isNodeMatchingSelectorFocusable(node) {
|
45 | if (node.disabled || node.tagName === 'INPUT' && node.type === 'hidden' || isNonTabbableRadio(node)) {
|
46 | return false;
|
47 | }
|
48 | return true;
|
49 | }
|
50 | function defaultGetTabbable(root) {
|
51 | var regularTabNodes = [];
|
52 | var orderedTabNodes = [];
|
53 | Array.from(root.querySelectorAll(candidatesSelector)).forEach(function (node, i) {
|
54 | var nodeTabIndex = getTabIndex(node);
|
55 | if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node)) {
|
56 | return;
|
57 | }
|
58 | if (nodeTabIndex === 0) {
|
59 | regularTabNodes.push(node);
|
60 | } else {
|
61 | orderedTabNodes.push({
|
62 | documentOrder: i,
|
63 | tabIndex: nodeTabIndex,
|
64 | node: node
|
65 | });
|
66 | }
|
67 | });
|
68 | return orderedTabNodes.sort(function (a, b) {
|
69 | return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
|
70 | }).map(function (a) {
|
71 | return a.node;
|
72 | }).concat(regularTabNodes);
|
73 | }
|
74 | function defaultIsEnabled() {
|
75 | return true;
|
76 | }
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | function FocusTrap(props) {
|
90 | var children = props.children,
|
91 | _props$disableAutoFoc = props.disableAutoFocus,
|
92 | disableAutoFocus = _props$disableAutoFoc === void 0 ? false : _props$disableAutoFoc,
|
93 | _props$disableEnforce = props.disableEnforceFocus,
|
94 | disableEnforceFocus = _props$disableEnforce === void 0 ? false : _props$disableEnforce,
|
95 | _props$disableRestore = props.disableRestoreFocus,
|
96 | disableRestoreFocus = _props$disableRestore === void 0 ? false : _props$disableRestore,
|
97 | _props$getTabbable = props.getTabbable,
|
98 | getTabbable = _props$getTabbable === void 0 ? defaultGetTabbable : _props$getTabbable,
|
99 | _props$isEnabled = props.isEnabled,
|
100 | isEnabled = _props$isEnabled === void 0 ? defaultIsEnabled : _props$isEnabled,
|
101 | open = props.open;
|
102 | var ignoreNextEnforceFocus = React.useRef(false);
|
103 | var sentinelStart = React.useRef(null);
|
104 | var sentinelEnd = React.useRef(null);
|
105 | var nodeToRestore = React.useRef(null);
|
106 | var reactFocusEventTarget = React.useRef(null);
|
107 |
|
108 |
|
109 | var activated = React.useRef(false);
|
110 | var rootRef = React.useRef(null);
|
111 |
|
112 | var handleRef = useForkRef(children.ref, rootRef);
|
113 | var lastKeydown = React.useRef(null);
|
114 | React.useEffect(function () {
|
115 |
|
116 | if (!open || !rootRef.current) {
|
117 | return;
|
118 | }
|
119 | activated.current = !disableAutoFocus;
|
120 | }, [disableAutoFocus, open]);
|
121 | React.useEffect(function () {
|
122 |
|
123 | if (!open || !rootRef.current) {
|
124 | return;
|
125 | }
|
126 | var doc = ownerDocument(rootRef.current);
|
127 | if (!rootRef.current.contains(doc.activeElement)) {
|
128 | if (!rootRef.current.hasAttribute('tabIndex')) {
|
129 | if (process.env.NODE_ENV !== 'production') {
|
130 | console.error(['MUI: 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'));
|
131 | }
|
132 | rootRef.current.setAttribute('tabIndex', '-1');
|
133 | }
|
134 | if (activated.current) {
|
135 | rootRef.current.focus();
|
136 | }
|
137 | }
|
138 | return function () {
|
139 |
|
140 | if (!disableRestoreFocus) {
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | if (nodeToRestore.current && nodeToRestore.current.focus) {
|
146 | ignoreNextEnforceFocus.current = true;
|
147 | nodeToRestore.current.focus();
|
148 | }
|
149 | nodeToRestore.current = null;
|
150 | }
|
151 | };
|
152 |
|
153 |
|
154 |
|
155 | }, [open]);
|
156 | React.useEffect(function () {
|
157 |
|
158 | if (!open || !rootRef.current) {
|
159 | return;
|
160 | }
|
161 | var doc = ownerDocument(rootRef.current);
|
162 | var contain = function contain(nativeEvent) {
|
163 | var rootElement = rootRef.current;
|
164 |
|
165 | if (rootElement === null) {
|
166 | return;
|
167 | }
|
168 | if (!doc.hasFocus() || disableEnforceFocus || !isEnabled() || ignoreNextEnforceFocus.current) {
|
169 | ignoreNextEnforceFocus.current = false;
|
170 | return;
|
171 | }
|
172 | if (!rootElement.contains(doc.activeElement)) {
|
173 |
|
174 | if (nativeEvent && reactFocusEventTarget.current !== nativeEvent.target || doc.activeElement !== reactFocusEventTarget.current) {
|
175 | reactFocusEventTarget.current = null;
|
176 | } else if (reactFocusEventTarget.current !== null) {
|
177 | return;
|
178 | }
|
179 | if (!activated.current) {
|
180 | return;
|
181 | }
|
182 | var tabbable = [];
|
183 | if (doc.activeElement === sentinelStart.current || doc.activeElement === sentinelEnd.current) {
|
184 | tabbable = getTabbable(rootRef.current);
|
185 | }
|
186 | if (tabbable.length > 0) {
|
187 | var _lastKeydown$current, _lastKeydown$current2;
|
188 | var isShiftTab = Boolean(((_lastKeydown$current = lastKeydown.current) == null ? void 0 : _lastKeydown$current.shiftKey) && ((_lastKeydown$current2 = lastKeydown.current) == null ? void 0 : _lastKeydown$current2.key) === 'Tab');
|
189 | var focusNext = tabbable[0];
|
190 | var focusPrevious = tabbable[tabbable.length - 1];
|
191 | if (typeof focusNext !== 'string' && typeof focusPrevious !== 'string') {
|
192 | if (isShiftTab) {
|
193 | focusPrevious.focus();
|
194 | } else {
|
195 | focusNext.focus();
|
196 | }
|
197 | }
|
198 | } else {
|
199 | rootElement.focus();
|
200 | }
|
201 | }
|
202 | };
|
203 | var loopFocus = function loopFocus(nativeEvent) {
|
204 | lastKeydown.current = nativeEvent;
|
205 | if (disableEnforceFocus || !isEnabled() || nativeEvent.key !== 'Tab') {
|
206 | return;
|
207 | }
|
208 |
|
209 |
|
210 |
|
211 | if (doc.activeElement === rootRef.current && nativeEvent.shiftKey) {
|
212 |
|
213 |
|
214 | ignoreNextEnforceFocus.current = true;
|
215 | if (sentinelEnd.current) {
|
216 | sentinelEnd.current.focus();
|
217 | }
|
218 | }
|
219 | };
|
220 | doc.addEventListener('focusin', contain);
|
221 | doc.addEventListener('keydown', loopFocus, true);
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | var interval = setInterval(function () {
|
230 | if (doc.activeElement && doc.activeElement.tagName === 'BODY') {
|
231 | contain(null);
|
232 | }
|
233 | }, 50);
|
234 | return function () {
|
235 | clearInterval(interval);
|
236 | doc.removeEventListener('focusin', contain);
|
237 | doc.removeEventListener('keydown', loopFocus, true);
|
238 | };
|
239 | }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open, getTabbable]);
|
240 | var onFocus = function onFocus(event) {
|
241 | if (nodeToRestore.current === null) {
|
242 | nodeToRestore.current = event.relatedTarget;
|
243 | }
|
244 | activated.current = true;
|
245 | reactFocusEventTarget.current = event.target;
|
246 | var childrenPropsHandler = children.props.onFocus;
|
247 | if (childrenPropsHandler) {
|
248 | childrenPropsHandler(event);
|
249 | }
|
250 | };
|
251 | var handleFocusSentinel = function handleFocusSentinel(event) {
|
252 | if (nodeToRestore.current === null) {
|
253 | nodeToRestore.current = event.relatedTarget;
|
254 | }
|
255 | activated.current = true;
|
256 | };
|
257 | return _jsxs(React.Fragment, {
|
258 | children: [_jsx("div", {
|
259 | tabIndex: open ? 0 : -1,
|
260 | onFocus: handleFocusSentinel,
|
261 | ref: sentinelStart,
|
262 | "data-testid": "sentinelStart"
|
263 | }), React.cloneElement(children, {
|
264 | ref: handleRef,
|
265 | onFocus: onFocus
|
266 | }), _jsx("div", {
|
267 | tabIndex: open ? 0 : -1,
|
268 | onFocus: handleFocusSentinel,
|
269 | ref: sentinelEnd,
|
270 | "data-testid": "sentinelEnd"
|
271 | })]
|
272 | });
|
273 | }
|
274 | process.env.NODE_ENV !== "production" ? FocusTrap.propTypes = {
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | |
280 |
|
281 |
|
282 | children: elementAcceptingRef,
|
283 | |
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 | disableAutoFocus: PropTypes.bool,
|
293 | |
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 | disableEnforceFocus: PropTypes.bool,
|
301 | |
302 |
|
303 |
|
304 |
|
305 |
|
306 | disableRestoreFocus: PropTypes.bool,
|
307 | |
308 |
|
309 |
|
310 |
|
311 |
|
312 | getTabbable: PropTypes.func,
|
313 | |
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | isEnabled: PropTypes.func,
|
323 | |
324 |
|
325 |
|
326 | open: PropTypes.bool.isRequired
|
327 | } : void 0;
|
328 | if (process.env.NODE_ENV !== 'production') {
|
329 |
|
330 | FocusTrap['propTypes' + ''] = exactProp(FocusTrap.propTypes);
|
331 | }
|
332 | export default FocusTrap; |
\ | No newline at end of file |