UNPKG

7.67 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.default = void 0;
9
10var _react = _interopRequireDefault(require("react"));
11
12var _reactDom = _interopRequireDefault(require("react-dom"));
13
14var _propTypes = _interopRequireDefault(require("prop-types"));
15
16var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
17
18var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
19
20/* eslint-disable consistent-return, jsx-a11y/no-noninteractive-tabindex */
21
22/**
23 * @ignore - internal component.
24 */
25function TrapFocus(props) {
26 var children = props.children,
27 _props$disableAutoFoc = props.disableAutoFocus,
28 disableAutoFocus = _props$disableAutoFoc === void 0 ? false : _props$disableAutoFoc,
29 _props$disableEnforce = props.disableEnforceFocus,
30 disableEnforceFocus = _props$disableEnforce === void 0 ? false : _props$disableEnforce,
31 _props$disableRestore = props.disableRestoreFocus,
32 disableRestoreFocus = _props$disableRestore === void 0 ? false : _props$disableRestore,
33 getDoc = props.getDoc,
34 isEnabled = props.isEnabled,
35 open = props.open;
36
37 var ignoreNextEnforceFocus = _react.default.useRef();
38
39 var sentinelStart = _react.default.useRef(null);
40
41 var sentinelEnd = _react.default.useRef(null);
42
43 var nodeToRestore = _react.default.useRef();
44
45 var rootRef = _react.default.useRef(null); // can be removed once we drop support for non ref forwarding class components
46
47
48 var handleOwnRef = _react.default.useCallback(function (instance) {
49 // #StrictMode ready
50 rootRef.current = _reactDom.default.findDOMNode(instance);
51 }, []);
52
53 var handleRef = (0, _useForkRef.default)(children.ref, handleOwnRef); // ⚠️ You may rely on React.useMemo as a performance optimization, not as a semantic guarantee.
54 // https://reactjs.org/docs/hooks-reference.html#usememo
55
56 _react.default.useMemo(function () {
57 if (!open || typeof window === 'undefined') {
58 return;
59 }
60
61 nodeToRestore.current = getDoc().activeElement;
62 }, [open]); // eslint-disable-line react-hooks/exhaustive-deps
63
64
65 _react.default.useEffect(function () {
66 if (!open) {
67 return;
68 }
69
70 var doc = (0, _ownerDocument.default)(rootRef.current); // We might render an empty child.
71
72 if (!disableAutoFocus && rootRef.current && !rootRef.current.contains(doc.activeElement)) {
73 if (!rootRef.current.hasAttribute('tabIndex')) {
74 if (process.env.NODE_ENV !== 'production') {
75 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'));
76 }
77
78 rootRef.current.setAttribute('tabIndex', -1);
79 }
80
81 rootRef.current.focus();
82 }
83
84 var contain = function contain() {
85 if (disableEnforceFocus || !isEnabled() || ignoreNextEnforceFocus.current) {
86 ignoreNextEnforceFocus.current = false;
87 return;
88 }
89
90 if (rootRef.current && !rootRef.current.contains(doc.activeElement)) {
91 rootRef.current.focus();
92 }
93 };
94
95 var loopFocus = function loopFocus(event) {
96 // 9 = Tab
97 if (disableEnforceFocus || !isEnabled() || event.keyCode !== 9) {
98 return;
99 } // Make sure the next tab starts from the right place.
100
101
102 if (doc.activeElement === rootRef.current) {
103 // We need to ignore the next contain as
104 // it will try to move the focus back to the rootRef element.
105 ignoreNextEnforceFocus.current = true;
106
107 if (event.shiftKey) {
108 sentinelEnd.current.focus();
109 } else {
110 sentinelStart.current.focus();
111 }
112 }
113 };
114
115 doc.addEventListener('focus', contain, true);
116 doc.addEventListener('keydown', loopFocus, true); // With Edge, Safari and Firefox, no focus related events are fired when the focused area stops being a focused area
117 // e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=559561.
118 //
119 // The whatwg spec defines how the browser should behave but does not explicitly mention any events:
120 // https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule.
121
122 var interval = setInterval(function () {
123 contain();
124 }, 50);
125 return function () {
126 clearInterval(interval);
127 doc.removeEventListener('focus', contain, true);
128 doc.removeEventListener('keydown', loopFocus, true); // restoreLastFocus()
129
130 if (!disableRestoreFocus) {
131 // In IE 11 it is possible for document.activeElement to be null resulting
132 // in nodeToRestore.current being null.
133 // Not all elements in IE 11 have a focus method.
134 // Once IE 11 support is dropped the focus() call can be unconditional.
135 if (nodeToRestore.current && nodeToRestore.current.focus) {
136 nodeToRestore.current.focus();
137 }
138
139 nodeToRestore.current = null;
140 }
141 };
142 }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open]);
143
144 return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement("div", {
145 tabIndex: 0,
146 ref: sentinelStart,
147 "data-test": "sentinelStart"
148 }), _react.default.cloneElement(children, {
149 ref: handleRef
150 }), _react.default.createElement("div", {
151 tabIndex: 0,
152 ref: sentinelEnd,
153 "data-test": "sentinelEnd"
154 }));
155}
156
157process.env.NODE_ENV !== "production" ? TrapFocus.propTypes = {
158 /**
159 * A single child content element.
160 */
161 children: _propTypes.default.element.isRequired,
162
163 /**
164 * If `true`, the modal will not automatically shift focus to itself when it opens, and
165 * replace it to the last focused element when it closes.
166 * This also works correctly with any modal children that have the `disableAutoFocus` prop.
167 *
168 * Generally this should never be set to `true` as it makes the modal less
169 * accessible to assistive technologies, like screen readers.
170 */
171 disableAutoFocus: _propTypes.default.bool,
172
173 /**
174 * If `true`, the modal will not prevent focus from leaving the modal while open.
175 *
176 * Generally this should never be set to `true` as it makes the modal less
177 * accessible to assistive technologies, like screen readers.
178 */
179 disableEnforceFocus: _propTypes.default.bool,
180
181 /**
182 * If `true`, the modal will not restore focus to previously focused element once
183 * modal is hidden.
184 */
185 disableRestoreFocus: _propTypes.default.bool,
186
187 /**
188 * Return the document to consider.
189 * We use it to implement the restore focus between different browser documents.
190 */
191 getDoc: _propTypes.default.func.isRequired,
192
193 /**
194 * Do we still want to enforce the focus?
195 * This prop helps nesting TrapFocus elements.
196 */
197 isEnabled: _propTypes.default.func.isRequired,
198
199 /**
200 * If `true`, the modal is open.
201 */
202 open: _propTypes.default.bool.isRequired
203} : void 0;
204/*
205
206In the future, we should be able to replace TrapFocus with:
207https://github.com/facebook/react/blob/master/packages/react-events/docs/FocusScope.md
208
209```jsx
210import FocusScope from 'react-dom/FocusScope';
211
212function TrapFocus(props) {
213 const {
214 children
215 disableAutoFocus = false,
216 disableEnforceFocus = false,
217 disableRestoreFocus = false,
218 open,
219 } = props;
220
221 if (!open) {
222 return children;
223 }
224
225 return (
226 <FocusScope
227 autoFocus={!disableAutoFocus}
228 contain={!disableEnforceFocus}
229 restoreFocus={!disableRestoreFocus}
230 >
231 {children}
232 </FocusScope>
233 );
234}
235```
236
237*/
238
239var _default = TrapFocus;
240exports.default = _default;
\No newline at end of file