focus-trap-react 10.0.0 → 10.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +31 -6
- package/dist/focus-trap-react.js +55 -93
- package/package.json +24 -24
- package/src/focus-trap-react.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 10.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0c98e74: Add missing trapStack option from focus-trap 7.1.0, add new isKeyForward and isKeyBackward options from focus-trap 7.2.0, bump focus-trap to 7.2.0.
|
|
8
|
+
|
|
9
|
+
## 10.0.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c772db0: Add help for Strict Mode in README [#796](https://github.com/focus-trap/focus-trap-react/issues/796)
|
|
14
|
+
- d0de500: Bump focus-trap to 7.1.0 and tabbable to 6.0.1 for new trap features and bug fixes
|
|
15
|
+
|
|
3
16
|
## 10.0.0
|
|
4
17
|
|
|
5
18
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -145,9 +145,34 @@ ReactDOM.render(<Demo />, document.getElementById('root')); // React 16-17
|
|
|
145
145
|
createRoot(document.getElementById('root')).render(<Demo />); // React 18
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
## ❗️❗️ React 18 Strict Mode ❗️❗️
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
React 18 introduced [new behavior](https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state) in Strict Mode whereby it mimics a possible future behavior where React might optimize an app's performance by unmounting certain components that aren't in use and later remounting them with previous, reused state when the user needs them again. What constitutes "not in use" and "needs them again" is as yet undefined.
|
|
151
|
+
|
|
152
|
+
_Remounted with reused state_ is the key difference between what is otherwise expected about [unmounted components](https://reactjs.org/docs/react-component.html#componentwillunmount).
|
|
153
|
+
|
|
154
|
+
__[v9.0.2](https://github.com/focus-trap/focus-trap-react/pull/721) adds support__ for this new Strict Mode behavior: The trap attempts to detect that it has been remounted with previous state: If the `active` prop's value is `true`, and an internal focus trap instance already exists, the focus trap is re-activated on remount in order to reconcile stated expectations.
|
|
155
|
+
|
|
156
|
+
> 🚨 In Strict Mode (and so in dev builds only, since this behavior of Strict Mode only affects dev builds), the trap __will be deactivated as soon as it is mounted__, and then reactivated again, almost immediately, because React will immediately unmount and remount the trap as soon as it's rendered.
|
|
157
|
+
|
|
158
|
+
Therefore, __avoid using options like onActivate, onPostActivate, onDeactivate, or onPostDeactivate to affect component state__.
|
|
159
|
+
|
|
160
|
+
<details>
|
|
161
|
+
<summary>Explanation and sample anti-pattern to <strong>avoid</strong></summary>
|
|
162
|
+
<p>
|
|
163
|
+
See <a href="https://github.com/focus-trap/focus-trap-react/issues/796">this discussion</a> for an example sandbox (issue description) where <code>onDeactivate</code> was used to trigger the close of a dialog when the trap was deactivated (e.g. to react to the user clicking outside the trap with <code>focusTrapOptions.clickOutsideDeactivates=true</code>).
|
|
164
|
+
</p>
|
|
165
|
+
<p>
|
|
166
|
+
The result can be that (depending on how you render the trap) in Strict Mode, the dialog never appears because it gets closed as soon as the trap renders, since the trap is deactivated as soon as it's unmounted, and so the <code>onDeactivate</code> handler is called, thus hiding the dialog...
|
|
167
|
+
</p>
|
|
168
|
+
<p>
|
|
169
|
+
<strong>This is intentional</strong>: If the trap gets unmounted, it has no idea if it's being unmounted <em>for good</em> or if it's going to be remounted <em>at some future point in time</em>. It also has no idea of knowing <em>how long</em> it will be until it's remounted again. So it must be deactivated as though it's going away for good in order to prevent unintentional behavior and memory leaks (from orphaned document event listeners).
|
|
170
|
+
</p>
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
## Props
|
|
174
|
+
|
|
175
|
+
### children
|
|
151
176
|
|
|
152
177
|
> ⚠️ The `<FocusTrap>` component requires a __single__ child, and this child must __forward refs__ onto the element which will ultimately be considered the trap's container. Since React does not provide for a way to forward refs to class-based components, this means the child must be a __functional__ component that uses the `React.forwardRef()` API.
|
|
153
178
|
>
|
|
@@ -237,7 +262,7 @@ const root = createRoot(container);
|
|
|
237
262
|
root.render(<DemoFunctionChild />);
|
|
238
263
|
```
|
|
239
264
|
|
|
240
|
-
|
|
265
|
+
### focusTrapOptions
|
|
241
266
|
|
|
242
267
|
Type: `Object`, optional
|
|
243
268
|
|
|
@@ -245,7 +270,7 @@ Pass any of the options available in focus-trap's [createOptions](https://github
|
|
|
245
270
|
|
|
246
271
|
> ⚠️ See notes about __[testing in JSDom](#testing-in-jsdom)__ (e.g. using Jest) if that's what you currently use.
|
|
247
272
|
|
|
248
|
-
|
|
273
|
+
### active
|
|
249
274
|
|
|
250
275
|
Type: `Boolean`, optional
|
|
251
276
|
|
|
@@ -253,13 +278,13 @@ By default, the `FocusTrap` activates when it mounts. So you activate and deacti
|
|
|
253
278
|
|
|
254
279
|
See `demo/demo-special-element.js`.
|
|
255
280
|
|
|
256
|
-
|
|
281
|
+
### paused
|
|
257
282
|
|
|
258
283
|
Type: `Boolean`, optional
|
|
259
284
|
|
|
260
285
|
If you would like to pause or unpause the focus trap (see [`focus-trap`'s documentation](https://github.com/focus-trap/focus-trap#focustrappause)), toggle this prop.
|
|
261
286
|
|
|
262
|
-
|
|
287
|
+
### containerElements
|
|
263
288
|
|
|
264
289
|
Type: `Array of HTMLElement`, optional
|
|
265
290
|
|
package/dist/focus-trap-react.js
CHANGED
|
@@ -1,65 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
|
4
|
-
|
|
5
4
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
|
-
|
|
7
|
-
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
8
|
-
|
|
5
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
9
6
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
10
|
-
|
|
11
7
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
|
12
|
-
|
|
13
8
|
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
14
|
-
|
|
15
9
|
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
16
|
-
|
|
17
10
|
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
|
|
18
|
-
|
|
19
11
|
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
20
|
-
|
|
21
12
|
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
22
|
-
|
|
23
13
|
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
24
|
-
|
|
25
|
-
function
|
|
26
|
-
|
|
14
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
15
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
16
|
+
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
27
17
|
var React = require('react');
|
|
28
|
-
|
|
29
18
|
var PropTypes = require('prop-types');
|
|
30
|
-
|
|
31
19
|
var _require = require('focus-trap'),
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
createFocusTrap = _require.createFocusTrap;
|
|
34
21
|
var _require2 = require('tabbable'),
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
isFocusable = _require2.isFocusable;
|
|
37
23
|
var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
38
24
|
_inherits(FocusTrap, _React$Component);
|
|
39
|
-
|
|
40
25
|
var _super = _createSuper(FocusTrap);
|
|
41
|
-
|
|
42
26
|
function FocusTrap(props) {
|
|
43
27
|
var _this;
|
|
44
|
-
|
|
45
28
|
_classCallCheck(this, FocusTrap);
|
|
46
|
-
|
|
47
29
|
_this = _super.call(this, props);
|
|
48
|
-
|
|
49
30
|
_defineProperty(_assertThisInitialized(_this), "getNodeForOption", function (optionName) {
|
|
50
31
|
var _this$internalOptions;
|
|
51
|
-
|
|
52
32
|
// use internal options first, falling back to original options
|
|
53
33
|
var optionValue = (_this$internalOptions = this.internalOptions[optionName]) !== null && _this$internalOptions !== void 0 ? _this$internalOptions : this.originalOptions[optionName];
|
|
54
|
-
|
|
55
34
|
if (typeof optionValue === 'function') {
|
|
56
35
|
for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
57
36
|
params[_key - 1] = arguments[_key];
|
|
58
37
|
}
|
|
59
|
-
|
|
60
38
|
optionValue = optionValue.apply(void 0, params);
|
|
61
39
|
}
|
|
62
|
-
|
|
63
40
|
if (optionValue === true) {
|
|
64
41
|
optionValue = undefined; // use default value
|
|
65
42
|
}
|
|
@@ -67,31 +44,27 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
67
44
|
if (!optionValue) {
|
|
68
45
|
if (optionValue === undefined || optionValue === false) {
|
|
69
46
|
return optionValue;
|
|
70
|
-
}
|
|
71
|
-
|
|
47
|
+
}
|
|
48
|
+
// else, empty string (invalid), null (invalid), 0 (invalid)
|
|
72
49
|
|
|
73
50
|
throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node"));
|
|
74
51
|
}
|
|
75
|
-
|
|
76
52
|
var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
|
|
77
53
|
|
|
78
54
|
if (typeof optionValue === 'string') {
|
|
79
55
|
var _this$getDocument;
|
|
80
|
-
|
|
81
56
|
node = (_this$getDocument = this.getDocument()) === null || _this$getDocument === void 0 ? void 0 : _this$getDocument.querySelector(optionValue); // resolve to node, or null if fails
|
|
82
|
-
|
|
83
57
|
if (!node) {
|
|
84
58
|
throw new Error("`".concat(optionName, "` as selector refers to no known node"));
|
|
85
59
|
}
|
|
86
60
|
}
|
|
87
|
-
|
|
88
61
|
return node;
|
|
89
62
|
});
|
|
90
|
-
|
|
91
63
|
_this.handleDeactivate = _this.handleDeactivate.bind(_assertThisInitialized(_this));
|
|
92
64
|
_this.handlePostDeactivate = _this.handlePostDeactivate.bind(_assertThisInitialized(_this));
|
|
93
|
-
_this.handleClickOutsideDeactivates = _this.handleClickOutsideDeactivates.bind(_assertThisInitialized(_this));
|
|
65
|
+
_this.handleClickOutsideDeactivates = _this.handleClickOutsideDeactivates.bind(_assertThisInitialized(_this));
|
|
94
66
|
|
|
67
|
+
// focus-trap options used internally when creating the trap
|
|
95
68
|
_this.internalOptions = {
|
|
96
69
|
// We need to hijack the returnFocusOnDeactivate option,
|
|
97
70
|
// because React can move focus into the element before we arrived at
|
|
@@ -109,8 +82,9 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
109
82
|
// outside click (otherwise, we'll always think we should return focus because
|
|
110
83
|
// of how we manage that flag internally here)
|
|
111
84
|
clickOutsideDeactivates: _this.handleClickOutsideDeactivates
|
|
112
|
-
};
|
|
85
|
+
};
|
|
113
86
|
|
|
87
|
+
// original options provided by the consumer
|
|
114
88
|
_this.originalOptions = {
|
|
115
89
|
// because of the above `internalOptions`, we maintain our own flag for
|
|
116
90
|
// this option, and default it to `true` because that's focus-trap's default
|
|
@@ -125,48 +99,47 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
125
99
|
clickOutsideDeactivates: false
|
|
126
100
|
};
|
|
127
101
|
var focusTrapOptions = props.focusTrapOptions;
|
|
128
|
-
|
|
129
102
|
for (var optionName in focusTrapOptions) {
|
|
130
103
|
if (!Object.prototype.hasOwnProperty.call(focusTrapOptions, optionName)) {
|
|
131
104
|
continue;
|
|
132
105
|
}
|
|
133
|
-
|
|
134
106
|
if (optionName === 'returnFocusOnDeactivate' || optionName === 'onDeactivate' || optionName === 'onPostDeactivate' || optionName === 'checkCanReturnFocus' || optionName === 'clickOutsideDeactivates') {
|
|
135
107
|
_this.originalOptions[optionName] = focusTrapOptions[optionName];
|
|
136
108
|
continue; // exclude from internalOptions
|
|
137
109
|
}
|
|
138
110
|
|
|
139
111
|
_this.internalOptions[optionName] = focusTrapOptions[optionName];
|
|
140
|
-
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// if set, `{ target: Node, allowDeactivation: boolean }` where `target` is the outside
|
|
141
115
|
// node that was clicked, and `allowDeactivation` is the result of the consumer's
|
|
142
116
|
// option (stored in `this.originalOptions.clickOutsideDeactivates`, which may be a
|
|
143
117
|
// function) whether to allow or deny auto-deactivation on click on this outside node
|
|
118
|
+
_this.outsideClick = null;
|
|
144
119
|
|
|
145
|
-
|
|
146
|
-
_this.outsideClick = null; // elements from which to create the focus trap on mount; if a child is used
|
|
120
|
+
// elements from which to create the focus trap on mount; if a child is used
|
|
147
121
|
// instead of the `containerElements` prop, we'll get the child's related
|
|
148
122
|
// element when the trap renders and then is declared 'mounted'
|
|
123
|
+
_this.focusTrapElements = props.containerElements || [];
|
|
149
124
|
|
|
150
|
-
|
|
151
|
-
|
|
125
|
+
// now we remember what the currently focused element is, not relying on focus-trap
|
|
152
126
|
_this.updatePreviousElement();
|
|
153
|
-
|
|
154
127
|
return _this;
|
|
155
128
|
}
|
|
129
|
+
|
|
156
130
|
/**
|
|
157
131
|
* Gets the configured document.
|
|
158
132
|
* @returns {Document|undefined} Configured document, falling back to the main
|
|
159
133
|
* document, if it exists. During SSR, `undefined` is returned since the
|
|
160
134
|
* document doesn't exist.
|
|
161
135
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
136
|
_createClass(FocusTrap, [{
|
|
165
137
|
key: "getDocument",
|
|
166
138
|
value: function getDocument() {
|
|
167
139
|
// SSR: careful to check if `document` exists before accessing it as a variable
|
|
168
140
|
return this.props.focusTrapOptions.document || (typeof document !== 'undefined' ? document : undefined);
|
|
169
141
|
}
|
|
142
|
+
|
|
170
143
|
/**
|
|
171
144
|
* Gets the node for the given option, which is expected to be an option that
|
|
172
145
|
* can be either a DOM node, a string that is a selector to get a node, `false`
|
|
@@ -180,20 +153,18 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
180
153
|
* @throws {Error} If the option is set, not `false`, and is not, or does not
|
|
181
154
|
* resolve to a node.
|
|
182
155
|
*/
|
|
183
|
-
|
|
184
156
|
}, {
|
|
185
157
|
key: "getReturnFocusNode",
|
|
186
158
|
value: function getReturnFocusNode() {
|
|
187
159
|
var node = this.getNodeForOption('setReturnFocus', this.previouslyFocusedElement);
|
|
188
160
|
return node ? node : node === false ? false : this.previouslyFocusedElement;
|
|
189
161
|
}
|
|
190
|
-
/** Update the previously focused element with the currently focused element. */
|
|
191
162
|
|
|
163
|
+
/** Update the previously focused element with the currently focused element. */
|
|
192
164
|
}, {
|
|
193
165
|
key: "updatePreviousElement",
|
|
194
166
|
value: function updatePreviousElement() {
|
|
195
167
|
var currentDocument = this.getDocument();
|
|
196
|
-
|
|
197
168
|
if (currentDocument) {
|
|
198
169
|
this.previouslyFocusedElement = currentDocument.activeElement;
|
|
199
170
|
}
|
|
@@ -209,7 +180,6 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
209
180
|
if (!this.focusTrap || !this.focusTrap.active) {
|
|
210
181
|
return;
|
|
211
182
|
}
|
|
212
|
-
|
|
213
183
|
this.focusTrap.deactivate({
|
|
214
184
|
// NOTE: we never let the trap return the focus since we do that ourselves
|
|
215
185
|
returnFocus: false,
|
|
@@ -218,13 +188,13 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
218
188
|
checkCanReturnFocus: null,
|
|
219
189
|
// let it call the user's original deactivate handler, if any, instead of
|
|
220
190
|
// our own which calls back into this function
|
|
221
|
-
onDeactivate: this.originalOptions.onDeactivate
|
|
191
|
+
onDeactivate: this.originalOptions.onDeactivate
|
|
192
|
+
// NOTE: for post deactivate, don't specify anything so that it calls the
|
|
222
193
|
// onPostDeactivate handler specified on `this.internalOptions`
|
|
223
194
|
// which will always be our own `handlePostDeactivate()` handler, which
|
|
224
195
|
// will finish things off by calling the user's provided onPostDeactivate
|
|
225
196
|
// handler, if any, at the right time
|
|
226
197
|
// onPostDeactivate: NOTHING
|
|
227
|
-
|
|
228
198
|
});
|
|
229
199
|
}
|
|
230
200
|
}, {
|
|
@@ -242,7 +212,6 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
242
212
|
allowDeactivation: allowDeactivation
|
|
243
213
|
};
|
|
244
214
|
}
|
|
245
|
-
|
|
246
215
|
return allowDeactivation;
|
|
247
216
|
}
|
|
248
217
|
}, {
|
|
@@ -258,32 +227,35 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
258
227
|
key: "handlePostDeactivate",
|
|
259
228
|
value: function handlePostDeactivate() {
|
|
260
229
|
var _this2 = this;
|
|
261
|
-
|
|
262
230
|
var finishDeactivation = function finishDeactivation() {
|
|
263
231
|
var returnFocusNode = _this2.getReturnFocusNode();
|
|
232
|
+
var canReturnFocus = !!(
|
|
233
|
+
// did the consumer allow it?
|
|
264
234
|
|
|
265
|
-
var canReturnFocus = !!( // did the consumer allow it?
|
|
266
235
|
_this2.originalOptions.returnFocusOnDeactivate && // can we actually focus the node?
|
|
267
|
-
returnFocusNode !== null && returnFocusNode !== void 0 && returnFocusNode.focus && (
|
|
268
|
-
|
|
269
|
-
_this2.outsideClick
|
|
236
|
+
returnFocusNode !== null && returnFocusNode !== void 0 && returnFocusNode.focus && (
|
|
237
|
+
// was there an outside click that allowed deactivation?
|
|
238
|
+
!_this2.outsideClick ||
|
|
239
|
+
// did the consumer allow deactivation when the outside node was clicked?
|
|
240
|
+
_this2.outsideClick.allowDeactivation &&
|
|
241
|
+
// is the outside node NOT focusable (implying that it did NOT receive focus
|
|
270
242
|
// as a result of the click-through) -- in which case do NOT restore focus
|
|
271
243
|
// to `returnFocusNode` because focus should remain on the outside node
|
|
272
|
-
!isFocusable(_this2.outsideClick.target, _this2.internalOptions.tabbableOptions))
|
|
244
|
+
!isFocusable(_this2.outsideClick.target, _this2.internalOptions.tabbableOptions))
|
|
245
|
+
|
|
246
|
+
// if no, the restore focus to `returnFocusNode` at this point
|
|
273
247
|
);
|
|
274
|
-
var _this2$internalOption = _this2.internalOptions.preventScroll,
|
|
275
|
-
preventScroll = _this2$internalOption === void 0 ? false : _this2$internalOption;
|
|
276
248
|
|
|
249
|
+
var _this2$internalOption = _this2.internalOptions.preventScroll,
|
|
250
|
+
preventScroll = _this2$internalOption === void 0 ? false : _this2$internalOption;
|
|
277
251
|
if (canReturnFocus) {
|
|
278
252
|
// return focus to the element that had focus when the trap was activated
|
|
279
253
|
returnFocusNode.focus({
|
|
280
254
|
preventScroll: preventScroll
|
|
281
255
|
});
|
|
282
256
|
}
|
|
283
|
-
|
|
284
257
|
if (_this2.originalOptions.onPostDeactivate) {
|
|
285
258
|
_this2.originalOptions.onPostDeactivate.call(null); // don't call it in context of "this"
|
|
286
|
-
|
|
287
259
|
}
|
|
288
260
|
|
|
289
261
|
_this2.outsideClick = null; // reset: no longer needed
|
|
@@ -315,22 +287,18 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
315
287
|
// which means we need to reactivate and then pause. Otherwise, do nothing.
|
|
316
288
|
if (this.props.active && !this.focusTrap.active) {
|
|
317
289
|
this.focusTrap.activate();
|
|
318
|
-
|
|
319
290
|
if (this.props.paused) {
|
|
320
291
|
this.focusTrap.pause();
|
|
321
292
|
}
|
|
322
293
|
}
|
|
323
294
|
} else {
|
|
324
295
|
var nodesExist = this.focusTrapElements.some(Boolean);
|
|
325
|
-
|
|
326
296
|
if (nodesExist) {
|
|
327
297
|
// eslint-disable-next-line react/prop-types -- _createFocusTrap is an internal prop
|
|
328
298
|
this.focusTrap = this.props._createFocusTrap(this.focusTrapElements, this.internalOptions);
|
|
329
|
-
|
|
330
299
|
if (this.props.active) {
|
|
331
300
|
this.focusTrap.activate();
|
|
332
301
|
}
|
|
333
|
-
|
|
334
302
|
if (this.props.paused) {
|
|
335
303
|
this.focusTrap.pause();
|
|
336
304
|
}
|
|
@@ -342,12 +310,12 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
342
310
|
value: function componentDidMount() {
|
|
343
311
|
if (this.props.active) {
|
|
344
312
|
this.setupFocusTrap();
|
|
345
|
-
}
|
|
313
|
+
}
|
|
314
|
+
// else, wait for later activation in case the `focusTrapOptions` will be updated
|
|
346
315
|
// again before the trap is activated (e.g. if waiting to know what the document
|
|
347
316
|
// object will be, so the Trap must be rendered, but the consumer is waiting to
|
|
348
317
|
// activate until they have obtained the document from a ref)
|
|
349
318
|
// @see https://github.com/focus-trap/focus-trap-react/issues/539
|
|
350
|
-
|
|
351
319
|
}
|
|
352
320
|
}, {
|
|
353
321
|
key: "componentDidUpdate",
|
|
@@ -356,17 +324,14 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
356
324
|
if (prevProps.containerElements !== this.props.containerElements) {
|
|
357
325
|
this.focusTrap.updateContainerElements(this.props.containerElements);
|
|
358
326
|
}
|
|
359
|
-
|
|
360
327
|
var hasActivated = !prevProps.active && this.props.active;
|
|
361
328
|
var hasDeactivated = prevProps.active && !this.props.active;
|
|
362
329
|
var hasPaused = !prevProps.paused && this.props.paused;
|
|
363
330
|
var hasUnpaused = prevProps.paused && !this.props.paused;
|
|
364
|
-
|
|
365
331
|
if (hasActivated) {
|
|
366
332
|
this.updatePreviousElement();
|
|
367
333
|
this.focusTrap.activate();
|
|
368
334
|
}
|
|
369
|
-
|
|
370
335
|
if (hasDeactivated) {
|
|
371
336
|
this.deactivateTrap();
|
|
372
337
|
return; // un/pause does nothing on an inactive trap
|
|
@@ -375,7 +340,6 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
375
340
|
if (hasPaused) {
|
|
376
341
|
this.focusTrap.pause();
|
|
377
342
|
}
|
|
378
|
-
|
|
379
343
|
if (hasUnpaused) {
|
|
380
344
|
this.focusTrap.unpause();
|
|
381
345
|
}
|
|
@@ -384,13 +348,14 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
384
348
|
// it either means it shouldn't be active, or it should be but none of
|
|
385
349
|
// of given `containerElements` were present in the DOM the last time
|
|
386
350
|
// we tried to create the trap
|
|
351
|
+
|
|
387
352
|
if (prevProps.containerElements !== this.props.containerElements) {
|
|
388
353
|
this.focusTrapElements = this.props.containerElements;
|
|
389
|
-
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// don't create the trap unless it should be active in case the consumer
|
|
390
357
|
// is still updating `focusTrapOptions`
|
|
391
358
|
// @see https://github.com/focus-trap/focus-trap-react/issues/539
|
|
392
|
-
|
|
393
|
-
|
|
394
359
|
if (this.props.active) {
|
|
395
360
|
this.updatePreviousElement();
|
|
396
361
|
this.setupFocusTrap();
|
|
@@ -406,17 +371,13 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
406
371
|
key: "render",
|
|
407
372
|
value: function render() {
|
|
408
373
|
var _this3 = this;
|
|
409
|
-
|
|
410
374
|
var child = this.props.children ? React.Children.only(this.props.children) : undefined;
|
|
411
|
-
|
|
412
375
|
if (child) {
|
|
413
376
|
if (child.type && child.type === React.Fragment) {
|
|
414
377
|
throw new Error('A focus-trap cannot use a Fragment as its child container. Try replacing it with a <div> element.');
|
|
415
378
|
}
|
|
416
|
-
|
|
417
379
|
var callbackRef = function callbackRef(element) {
|
|
418
380
|
var containerElements = _this3.props.containerElements;
|
|
419
|
-
|
|
420
381
|
if (child) {
|
|
421
382
|
if (typeof child.ref === 'function') {
|
|
422
383
|
child.ref(element);
|
|
@@ -424,24 +385,18 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
424
385
|
child.ref.current = element;
|
|
425
386
|
}
|
|
426
387
|
}
|
|
427
|
-
|
|
428
388
|
_this3.focusTrapElements = containerElements ? containerElements : [element];
|
|
429
389
|
};
|
|
430
|
-
|
|
431
390
|
var childWithRef = React.cloneElement(child, {
|
|
432
391
|
ref: callbackRef
|
|
433
392
|
});
|
|
434
393
|
return childWithRef;
|
|
435
394
|
}
|
|
436
|
-
|
|
437
395
|
return null;
|
|
438
396
|
}
|
|
439
397
|
}]);
|
|
440
|
-
|
|
441
398
|
return FocusTrap;
|
|
442
399
|
}(React.Component); // support server-side rendering where `Element` will not be defined
|
|
443
|
-
|
|
444
|
-
|
|
445
400
|
var ElementType = typeof Element === 'undefined' ? Function : Element;
|
|
446
401
|
FocusTrap.propTypes = {
|
|
447
402
|
active: PropTypes.bool,
|
|
@@ -455,7 +410,8 @@ FocusTrap.propTypes = {
|
|
|
455
410
|
onPostDeactivate: PropTypes.func,
|
|
456
411
|
checkCanReturnFocus: PropTypes.func,
|
|
457
412
|
initialFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.bool, PropTypes.func]),
|
|
458
|
-
fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string,
|
|
413
|
+
fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string,
|
|
414
|
+
// NOTE: does not support `false` as value (or return value from function)
|
|
459
415
|
PropTypes.func]),
|
|
460
416
|
escapeDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
461
417
|
clickOutsideDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
@@ -466,17 +422,23 @@ FocusTrap.propTypes = {
|
|
|
466
422
|
tabbableOptions: PropTypes.shape({
|
|
467
423
|
displayCheck: PropTypes.oneOf(['full', 'legacy-full', 'non-zero-area', 'none']),
|
|
468
424
|
getShadowRoot: PropTypes.oneOfType([PropTypes.bool, PropTypes.func])
|
|
469
|
-
})
|
|
425
|
+
}),
|
|
426
|
+
trapStack: PropTypes.array,
|
|
427
|
+
isKeyForward: PropTypes.func,
|
|
428
|
+
isKeyBackward: PropTypes.func
|
|
470
429
|
}),
|
|
471
430
|
containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)),
|
|
472
431
|
// DOM element ONLY
|
|
473
|
-
children: PropTypes.oneOfType([PropTypes.element,
|
|
432
|
+
children: PropTypes.oneOfType([PropTypes.element,
|
|
433
|
+
// React element
|
|
474
434
|
PropTypes.instanceOf(ElementType) // DOM element
|
|
475
|
-
])
|
|
435
|
+
])
|
|
436
|
+
|
|
437
|
+
// NOTE: _createFocusTrap is internal, for testing purposes only, so we don't
|
|
476
438
|
// specify it here. It's expected to be set to the function returned from
|
|
477
439
|
// require('focus-trap'), or one with a compatible interface.
|
|
478
|
-
|
|
479
440
|
};
|
|
441
|
+
|
|
480
442
|
FocusTrap.defaultProps = {
|
|
481
443
|
active: true,
|
|
482
444
|
paused: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap-react",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.2",
|
|
4
4
|
"description": "A React component that traps focus.",
|
|
5
5
|
"main": "dist/focus-trap-react.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -57,46 +57,46 @@
|
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://github.com/focus-trap/focus-trap-react#readme",
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@babel/cli": "^7.
|
|
61
|
-
"@babel/core": "^7.
|
|
62
|
-
"@babel/eslint-parser": "^7.
|
|
60
|
+
"@babel/cli": "^7.19.3",
|
|
61
|
+
"@babel/core": "^7.20.5",
|
|
62
|
+
"@babel/eslint-parser": "^7.19.1",
|
|
63
63
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
64
|
-
"@babel/preset-env": "^7.
|
|
64
|
+
"@babel/preset-env": "^7.20.2",
|
|
65
65
|
"@babel/preset-react": "^7.18.6",
|
|
66
|
-
"@changesets/cli": "^2.
|
|
67
|
-
"@testing-library/cypress": "^8.0.
|
|
68
|
-
"@testing-library/dom": "^8.
|
|
66
|
+
"@changesets/cli": "^2.25.2",
|
|
67
|
+
"@testing-library/cypress": "^8.0.7",
|
|
68
|
+
"@testing-library/dom": "^8.19.0",
|
|
69
69
|
"@testing-library/jest-dom": "^5.16.5",
|
|
70
|
-
"@testing-library/react": "^13.
|
|
70
|
+
"@testing-library/react": "^13.4.0",
|
|
71
71
|
"@testing-library/user-event": "^14.4.3",
|
|
72
72
|
"@types/jquery": "^3.5.14",
|
|
73
|
-
"all-contributors-cli": "^6.
|
|
74
|
-
"babel-jest": "^
|
|
73
|
+
"all-contributors-cli": "^6.24.0",
|
|
74
|
+
"babel-jest": "^29.3.1",
|
|
75
75
|
"babelify": "^10.0.0",
|
|
76
76
|
"browserify": "^17.0.0",
|
|
77
|
-
"budo": "^11.
|
|
78
|
-
"cypress": "^
|
|
77
|
+
"budo": "^11.8.4",
|
|
78
|
+
"cypress": "^11.2.0",
|
|
79
79
|
"cypress-plugin-tab": "^1.0.5",
|
|
80
|
-
"eslint": "^8.
|
|
80
|
+
"eslint": "^8.28.0",
|
|
81
81
|
"eslint-config-prettier": "^8.5.0",
|
|
82
82
|
"eslint-plugin-cypress": "^2.12.1",
|
|
83
|
-
"eslint-plugin-jest": "^
|
|
84
|
-
"eslint-plugin-react": "^7.
|
|
85
|
-
"jest": "^
|
|
86
|
-
"jest-environment-jsdom": "^
|
|
87
|
-
"jest-watch-typeahead": "^2.
|
|
83
|
+
"eslint-plugin-jest": "^27.1.6",
|
|
84
|
+
"eslint-plugin-react": "^7.31.11",
|
|
85
|
+
"jest": "^29.3.1",
|
|
86
|
+
"jest-environment-jsdom": "^29.3.1",
|
|
87
|
+
"jest-watch-typeahead": "^2.2.1",
|
|
88
88
|
"onchange": "^7.1.0",
|
|
89
|
-
"prettier": "^2.
|
|
89
|
+
"prettier": "^2.8.0",
|
|
90
90
|
"prop-types": "^15.8.1",
|
|
91
91
|
"react": "^18.2.0",
|
|
92
92
|
"react-dom": "^18.2.0",
|
|
93
|
-
"regenerator-runtime": "^0.13.
|
|
93
|
+
"regenerator-runtime": "^0.13.11",
|
|
94
94
|
"start-server-and-test": "^1.14.0",
|
|
95
|
-
"typescript": "^4.
|
|
95
|
+
"typescript": "^4.9.3"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"focus-trap": "^7.
|
|
99
|
-
"tabbable": "^6.0.
|
|
98
|
+
"focus-trap": "^7.2.0",
|
|
99
|
+
"tabbable": "^6.0.1"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"prop-types": "^15.8.1",
|
package/src/focus-trap-react.js
CHANGED
|
@@ -461,6 +461,9 @@ FocusTrap.propTypes = {
|
|
|
461
461
|
]),
|
|
462
462
|
getShadowRoot: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
463
463
|
}),
|
|
464
|
+
trapStack: PropTypes.array,
|
|
465
|
+
isKeyForward: PropTypes.func,
|
|
466
|
+
isKeyBackward: PropTypes.func,
|
|
464
467
|
}),
|
|
465
468
|
containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)), // DOM element ONLY
|
|
466
469
|
children: PropTypes.oneOfType([
|