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