UNPKG

8.34 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
22var _utils = require("@material-ui/utils");
23
24/* eslint-disable consistent-return, jsx-a11y/no-noninteractive-tabindex, camelcase */
25
26/**
27 * Utility component that locks focus inside the component.
28 */
29function Unstable_TrapFocus(props) {
30 var children = props.children,
31 _props$disableAutoFoc = props.disableAutoFocus,
32 disableAutoFocus = _props$disableAutoFoc === void 0 ? false : _props$disableAutoFoc,
33 _props$disableEnforce = props.disableEnforceFocus,
34 disableEnforceFocus = _props$disableEnforce === void 0 ? false : _props$disableEnforce,
35 _props$disableRestore = props.disableRestoreFocus,
36 disableRestoreFocus = _props$disableRestore === void 0 ? false : _props$disableRestore,
37 getDoc = props.getDoc,
38 isEnabled = props.isEnabled,
39 open = props.open;
40 var ignoreNextEnforceFocus = React.useRef();
41 var sentinelStart = React.useRef(null);
42 var sentinelEnd = React.useRef(null);
43 var nodeToRestore = React.useRef();
44 var rootRef = React.useRef(null); // can be removed once we drop support for non ref forwarding class components
45
46 var handleOwnRef = React.useCallback(function (instance) {
47 // #StrictMode ready
48 rootRef.current = ReactDOM.findDOMNode(instance);
49 }, []);
50 var handleRef = (0, _useForkRef.default)(children.ref, handleOwnRef);
51 var prevOpenRef = React.useRef();
52 React.useEffect(function () {
53 prevOpenRef.current = open;
54 }, [open]);
55
56 if (!prevOpenRef.current && open && typeof window !== 'undefined') {
57 // WARNING: Potentially unsafe in concurrent mode.
58 // The way the read on `nodeToRestore` is setup could make this actually safe.
59 // Say we render `open={false}` -> `open={true}` but never commit.
60 // We have now written a state that wasn't committed. But no committed effect
61 // will read this wrong value. We only read from `nodeToRestore` in effects
62 // that were committed on `open={true}`
63 // WARNING: Prevents the instance from being garbage collected. Should only
64 // hold a weak ref.
65 nodeToRestore.current = getDoc().activeElement;
66 }
67
68 React.useEffect(function () {
69 if (!open) {
70 return;
71 }
72
73 var doc = (0, _ownerDocument.default)(rootRef.current); // We might render an empty child.
74
75 if (!disableAutoFocus && rootRef.current && !rootRef.current.contains(doc.activeElement)) {
76 if (!rootRef.current.hasAttribute('tabIndex')) {
77 if (process.env.NODE_ENV !== 'production') {
78 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'));
79 }
80
81 rootRef.current.setAttribute('tabIndex', -1);
82 }
83
84 rootRef.current.focus();
85 }
86
87 var contain = function contain() {
88 var rootElement = rootRef.current; // Cleanup functions are executed lazily in React 17.
89 // Contain can be called between the component being unmounted and its cleanup function being run.
90
91 if (rootElement === null) {
92 return;
93 }
94
95 if (!doc.hasFocus() || disableEnforceFocus || !isEnabled() || ignoreNextEnforceFocus.current) {
96 ignoreNextEnforceFocus.current = false;
97 return;
98 }
99
100 if (rootRef.current && !rootRef.current.contains(doc.activeElement)) {
101 rootRef.current.focus();
102 }
103 };
104
105 var loopFocus = function loopFocus(event) {
106 // 9 = Tab
107 if (disableEnforceFocus || !isEnabled() || event.keyCode !== 9) {
108 return;
109 } // Make sure the next tab starts from the right place.
110
111
112 if (doc.activeElement === rootRef.current) {
113 // We need to ignore the next contain as
114 // it will try to move the focus back to the rootRef element.
115 ignoreNextEnforceFocus.current = true;
116
117 if (event.shiftKey) {
118 sentinelEnd.current.focus();
119 } else {
120 sentinelStart.current.focus();
121 }
122 }
123 };
124
125 doc.addEventListener('focus', contain, true);
126 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
127 // e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=559561.
128 //
129 // The whatwg spec defines how the browser should behave but does not explicitly mention any events:
130 // https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule.
131
132 var interval = setInterval(function () {
133 contain();
134 }, 50);
135 return function () {
136 clearInterval(interval);
137 doc.removeEventListener('focus', contain, true);
138 doc.removeEventListener('keydown', loopFocus, true); // restoreLastFocus()
139
140 if (!disableRestoreFocus) {
141 // In IE 11 it is possible for document.activeElement to be null resulting
142 // in nodeToRestore.current being null.
143 // Not all elements in IE 11 have a focus method.
144 // Once IE 11 support is dropped the focus() call can be unconditional.
145 if (nodeToRestore.current && nodeToRestore.current.focus) {
146 nodeToRestore.current.focus();
147 }
148
149 nodeToRestore.current = null;
150 }
151 };
152 }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open]);
153 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
154 tabIndex: 0,
155 ref: sentinelStart,
156 "data-test": "sentinelStart"
157 }), /*#__PURE__*/React.cloneElement(children, {
158 ref: handleRef
159 }), /*#__PURE__*/React.createElement("div", {
160 tabIndex: 0,
161 ref: sentinelEnd,
162 "data-test": "sentinelEnd"
163 }));
164}
165
166process.env.NODE_ENV !== "production" ? Unstable_TrapFocus.propTypes = {
167 // ----------------------------- Warning --------------------------------
168 // | These PropTypes are generated from the TypeScript type definitions |
169 // | To update them edit the d.ts file and run "yarn proptypes" |
170 // ----------------------------------------------------------------------
171
172 /**
173 * A single child content element.
174 */
175 children: _propTypes.default.node,
176
177 /**
178 * If `true`, the trap focus will not automatically shift focus to itself when it opens, and
179 * replace it to the last focused element when it closes.
180 * This also works correctly with any trap focus children that have the `disableAutoFocus` prop.
181 *
182 * Generally this should never be set to `true` as it makes the trap focus less
183 * accessible to assistive technologies, like screen readers.
184 */
185 disableAutoFocus: _propTypes.default.bool,
186
187 /**
188 * If `true`, the trap focus will not prevent focus from leaving the trap focus while open.
189 *
190 * Generally this should never be set to `true` as it makes the trap focus less
191 * accessible to assistive technologies, like screen readers.
192 */
193 disableEnforceFocus: _propTypes.default.bool,
194
195 /**
196 * If `true`, the trap focus will not restore focus to previously focused element once
197 * trap focus is hidden.
198 */
199 disableRestoreFocus: _propTypes.default.bool,
200
201 /**
202 * Return the document to consider.
203 * We use it to implement the restore focus between different browser documents.
204 */
205 getDoc: _propTypes.default.func.isRequired,
206
207 /**
208 * Do we still want to enforce the focus?
209 * This prop helps nesting TrapFocus elements.
210 */
211 isEnabled: _propTypes.default.func.isRequired,
212
213 /**
214 * If `true`, focus will be locked.
215 */
216 open: _propTypes.default.bool.isRequired
217} : void 0;
218
219if (process.env.NODE_ENV !== 'production') {
220 // eslint-disable-next-line
221 Unstable_TrapFocus['propTypes' + ''] = (0, _utils.exactProp)(Unstable_TrapFocus.propTypes);
222}
223
224var _default = Unstable_TrapFocus;
225exports.default = _default;
\No newline at end of file