UNPKG

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