focus-trap-react 8.11.2 → 9.0.1
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 +21 -0
- package/README.md +4 -3
- package/dist/focus-trap-react.js +73 -56
- package/package.json +15 -14
- package/src/focus-trap-react.js +54 -38
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2d6cd9b: Add explicit dependency on tabbable since the source directly requires it.
|
|
8
|
+
|
|
9
|
+
## 9.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- 4a77d87: Stop using the infamous `findDOMNode()` on provided `containerElements`.
|
|
14
|
+
- There seems to have been no good reason for this as this prop, if specified, is already required to be an array of HTMLElement references, which means these nodes have already been rendered (if they were once React elements). There appears to have been no remaining need for this API.
|
|
15
|
+
- Furthermore, the minimum supported version of React is now 16.3 as it technically has been for a while now since that is the version that introduced callback refs, which we've been using for quite some time now (so this bump will hopefully not cause any ripples).
|
|
16
|
+
|
|
17
|
+
## 8.11.3
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 9947461: Bump focus-trap dependency to v6.9.4 to get typings fix.
|
|
22
|
+
- 519e5a5: Fix setReturnFocus option as function not being passed node focused prior to activation.
|
|
23
|
+
|
|
3
24
|
## 8.11.2
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# focus-trap-react [](https://github.com/focus-trap/focus-trap-react/actions?query=workflow:CI+branch:master) [](https://codecov.io/gh/focus-trap/focus-trap-react) [](./LICENSE)
|
|
2
2
|
|
|
3
3
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
4
|
-
[](#contributors)
|
|
5
5
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
6
6
|
|
|
7
7
|
A React component that traps focus.
|
|
@@ -220,13 +220,14 @@ In alphabetical order:
|
|
|
220
220
|
<tr>
|
|
221
221
|
<td align="center"><a href="https://scottrippey.github.io/"><img src="https://avatars3.githubusercontent.com/u/430608?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Rippey</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=scottrippey" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Ascottrippey" title="Bug reports">🐛</a></td>
|
|
222
222
|
<td align="center"><a href="https://seanmcp.com/"><img src="https://avatars1.githubusercontent.com/u/6360367?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean McPherson</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=SeanMcP" title="Code">💻</a></td>
|
|
223
|
-
<td align="center"><a href="
|
|
223
|
+
<td align="center"><a href="http://smoores.dev"><img src="https://avatars.githubusercontent.com/u/5354254?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shane Moore</b></sub></a><br /><a href="#platform-SMores" title="Packaging/porting to new platform">📦</a></td>
|
|
224
|
+
<td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=Slapbox" title="Documentation">📖</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td>
|
|
224
225
|
<td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Astefcameron" title="Bug reports">🐛</a> <a href="#infra-stefcameron" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td>
|
|
225
226
|
<td align="center"><a href="http://tylerhawkins.info/201R/"><img src="https://avatars0.githubusercontent.com/u/13806458?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tyler Hawkins</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=thawkin3" title="Documentation">📖</a> <a href="#example-thawkin3" title="Examples">💡</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=thawkin3" title="Tests">⚠️</a> <a href="#tool-thawkin3" title="Tools">🔧</a></td>
|
|
226
227
|
<td align="center"><a href="https://github.com/wandroll"><img src="https://avatars.githubusercontent.com/u/4492317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wandrille Verlut</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Tests">⚠️</a></td>
|
|
227
|
-
<td align="center"><a href="https://github.com/krikienoid"><img src="https://avatars3.githubusercontent.com/u/8528227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>krikienoid</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Akrikienoid" title="Bug reports">🐛</a></td>
|
|
228
228
|
</tr>
|
|
229
229
|
<tr>
|
|
230
|
+
<td align="center"><a href="https://github.com/krikienoid"><img src="https://avatars3.githubusercontent.com/u/8528227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>krikienoid</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Akrikienoid" title="Bug reports">🐛</a></td>
|
|
230
231
|
<td align="center"><a href="https://github.com/syntactic-salt"><img src="https://avatars.githubusercontent.com/u/9385662?v=4?s=100" width="100px;" alt=""/><br /><sub><b>syntactic-salt</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Asyntactic-salt" title="Bug reports">🐛</a></td>
|
|
231
232
|
</tr>
|
|
232
233
|
</table>
|
package/dist/focus-trap-react.js
CHANGED
|
@@ -22,9 +22,9 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
|
|
|
22
22
|
|
|
23
23
|
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
26
26
|
|
|
27
|
-
var
|
|
27
|
+
var React = require('react');
|
|
28
28
|
|
|
29
29
|
var PropTypes = require('prop-types');
|
|
30
30
|
|
|
@@ -32,12 +32,7 @@ var _require = require('focus-trap'),
|
|
|
32
32
|
createFocusTrap = _require.createFocusTrap;
|
|
33
33
|
|
|
34
34
|
var _require2 = require('tabbable'),
|
|
35
|
-
isFocusable = _require2.isFocusable;
|
|
36
|
-
// to fix in order to move the code forward to the next major version of React.
|
|
37
|
-
// @see https://github.com/davidtheclark/focus-trap-react/issues/77
|
|
38
|
-
|
|
39
|
-
/* eslint-disable react/no-find-dom-node */
|
|
40
|
-
|
|
35
|
+
isFocusable = _require2.isFocusable;
|
|
41
36
|
|
|
42
37
|
var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
43
38
|
_inherits(FocusTrap, _React$Component);
|
|
@@ -50,6 +45,49 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
50
45
|
_classCallCheck(this, FocusTrap);
|
|
51
46
|
|
|
52
47
|
_this = _super.call(this, props);
|
|
48
|
+
|
|
49
|
+
_defineProperty(_assertThisInitialized(_this), "getNodeForOption", function (optionName) {
|
|
50
|
+
var _this$internalOptions;
|
|
51
|
+
|
|
52
|
+
// use internal options first, falling back to original options
|
|
53
|
+
var optionValue = (_this$internalOptions = this.internalOptions[optionName]) !== null && _this$internalOptions !== void 0 ? _this$internalOptions : this.originalOptions[optionName];
|
|
54
|
+
|
|
55
|
+
if (typeof optionValue === 'function') {
|
|
56
|
+
for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
57
|
+
params[_key - 1] = arguments[_key];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
optionValue = optionValue.apply(void 0, params);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (optionValue === true) {
|
|
64
|
+
optionValue = undefined; // use default value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!optionValue) {
|
|
68
|
+
if (optionValue === undefined || optionValue === false) {
|
|
69
|
+
return optionValue;
|
|
70
|
+
} // else, empty string (invalid), null (invalid), 0 (invalid)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
|
|
77
|
+
|
|
78
|
+
if (typeof optionValue === 'string') {
|
|
79
|
+
var _this$getDocument;
|
|
80
|
+
|
|
81
|
+
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
|
+
if (!node) {
|
|
84
|
+
throw new Error("`".concat(optionName, "` as selector refers to no known node"));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return node;
|
|
89
|
+
});
|
|
90
|
+
|
|
53
91
|
_this.handleDeactivate = _this.handleDeactivate.bind(_assertThisInitialized(_this));
|
|
54
92
|
_this.handlePostDeactivate = _this.handlePostDeactivate.bind(_assertThisInitialized(_this));
|
|
55
93
|
_this.handleClickOutsideDeactivates = _this.handleClickOutsideDeactivates.bind(_assertThisInitialized(_this)); // focus-trap options used internally when creating the trap
|
|
@@ -74,10 +112,10 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
74
112
|
}; // original options provided by the consumer
|
|
75
113
|
|
|
76
114
|
_this.originalOptions = {
|
|
77
|
-
// because of the above `
|
|
115
|
+
// because of the above `internalOptions`, we maintain our own flag for
|
|
78
116
|
// this option, and default it to `true` because that's focus-trap's default
|
|
79
117
|
returnFocusOnDeactivate: true,
|
|
80
|
-
// because of the above `
|
|
118
|
+
// because of the above `internalOptions`, we keep these separate since
|
|
81
119
|
// they're part of the deactivation process which we configure (internally) to
|
|
82
120
|
// be shared between focus-trap and focus-trap-react
|
|
83
121
|
onDeactivate: null,
|
|
@@ -95,7 +133,7 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
95
133
|
|
|
96
134
|
if (optionName === 'returnFocusOnDeactivate' || optionName === 'onDeactivate' || optionName === 'onPostDeactivate' || optionName === 'checkCanReturnFocus' || optionName === 'clickOutsideDeactivates') {
|
|
97
135
|
_this.originalOptions[optionName] = focusTrapOptions[optionName];
|
|
98
|
-
continue; // exclude from
|
|
136
|
+
continue; // exclude from internalOptions
|
|
99
137
|
}
|
|
100
138
|
|
|
101
139
|
_this.internalOptions[optionName] = focusTrapOptions[optionName];
|
|
@@ -128,44 +166,26 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
128
166
|
value: function getDocument() {
|
|
129
167
|
// SSR: careful to check if `document` exists before accessing it as a variable
|
|
130
168
|
return this.props.focusTrapOptions.document || (typeof document !== 'undefined' ? document : undefined);
|
|
131
|
-
} // TODO: Need more test coverage for this function
|
|
132
|
-
|
|
133
|
-
}, {
|
|
134
|
-
key: "getNodeForOption",
|
|
135
|
-
value: function getNodeForOption(optionName) {
|
|
136
|
-
var optionValue = this.internalOptions[optionName];
|
|
137
|
-
|
|
138
|
-
if (!optionValue) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
var node = optionValue;
|
|
143
|
-
|
|
144
|
-
if (typeof optionValue === 'string') {
|
|
145
|
-
var _this$getDocument;
|
|
146
|
-
|
|
147
|
-
node = (_this$getDocument = this.getDocument()) === null || _this$getDocument === void 0 ? void 0 : _this$getDocument.querySelector(optionValue);
|
|
148
|
-
|
|
149
|
-
if (!node) {
|
|
150
|
-
throw new Error("`".concat(optionName, "` refers to no known node"));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (typeof optionValue === 'function') {
|
|
155
|
-
node = optionValue();
|
|
156
|
-
|
|
157
|
-
if (!node) {
|
|
158
|
-
throw new Error("`".concat(optionName, "` did not return a node"));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return node;
|
|
163
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Gets the node for the given option, which is expected to be an option that
|
|
172
|
+
* can be either a DOM node, a string that is a selector to get a node, `false`
|
|
173
|
+
* (if a node is explicitly NOT given), or a function that returns any of these
|
|
174
|
+
* values.
|
|
175
|
+
* @param {string} optionName
|
|
176
|
+
* @returns {undefined | false | HTMLElement | SVGElement} Returns
|
|
177
|
+
* `undefined` if the option is not specified; `false` if the option
|
|
178
|
+
* resolved to `false` (node explicitly not given); otherwise, the resolved
|
|
179
|
+
* DOM node.
|
|
180
|
+
* @throws {Error} If the option is set, not `false`, and is not, or does not
|
|
181
|
+
* resolve to a node.
|
|
182
|
+
*/
|
|
183
|
+
|
|
164
184
|
}, {
|
|
165
185
|
key: "getReturnFocusNode",
|
|
166
186
|
value: function getReturnFocusNode() {
|
|
167
|
-
var node = this.getNodeForOption('setReturnFocus');
|
|
168
|
-
return node ? node : this.previouslyFocusedElement;
|
|
187
|
+
var node = this.getNodeForOption('setReturnFocus', this.previouslyFocusedElement);
|
|
188
|
+
return node ? node : node === false ? false : this.previouslyFocusedElement;
|
|
169
189
|
}
|
|
170
190
|
/** Update the previously focused element with the currently focused element. */
|
|
171
191
|
|
|
@@ -280,15 +300,11 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
280
300
|
key: "setupFocusTrap",
|
|
281
301
|
value: function setupFocusTrap() {
|
|
282
302
|
if (!this.focusTrap) {
|
|
283
|
-
var
|
|
284
|
-
// a new text node with the text wrapped in it instead of treating the
|
|
285
|
-
// string as a selector and resolving it to a node in the DOM
|
|
286
|
-
ReactDOM.findDOMNode);
|
|
287
|
-
var nodesExist = focusTrapElementDOMNodes.some(Boolean);
|
|
303
|
+
var nodesExist = this.focusTrapElements.some(Boolean);
|
|
288
304
|
|
|
289
305
|
if (nodesExist) {
|
|
290
306
|
// eslint-disable-next-line react/prop-types -- _createFocusTrap is an internal prop
|
|
291
|
-
this.focusTrap = this.props._createFocusTrap(
|
|
307
|
+
this.focusTrap = this.props._createFocusTrap(this.focusTrapElements, this.internalOptions);
|
|
292
308
|
|
|
293
309
|
if (this.props.active) {
|
|
294
310
|
this.focusTrap.activate();
|
|
@@ -377,7 +393,7 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
377
393
|
throw new Error('A focus-trap cannot use a Fragment as its child container. Try replacing it with a <div> element.');
|
|
378
394
|
}
|
|
379
395
|
|
|
380
|
-
var
|
|
396
|
+
var callbackRef = function callbackRef(element) {
|
|
381
397
|
var containerElements = _this3.props.containerElements;
|
|
382
398
|
|
|
383
399
|
if (child) {
|
|
@@ -392,7 +408,7 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
|
|
|
392
408
|
};
|
|
393
409
|
|
|
394
410
|
var childWithRef = React.cloneElement(child, {
|
|
395
|
-
ref:
|
|
411
|
+
ref: callbackRef
|
|
396
412
|
});
|
|
397
413
|
return childWithRef;
|
|
398
414
|
}
|
|
@@ -417,12 +433,13 @@ FocusTrap.propTypes = {
|
|
|
417
433
|
onDeactivate: PropTypes.func,
|
|
418
434
|
onPostDeactivate: PropTypes.func,
|
|
419
435
|
checkCanReturnFocus: PropTypes.func,
|
|
420
|
-
initialFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.
|
|
421
|
-
fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string,
|
|
436
|
+
initialFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.bool, PropTypes.func]),
|
|
437
|
+
fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, // NOTE: does not support `false` as value (or return value from function)
|
|
438
|
+
PropTypes.func]),
|
|
422
439
|
escapeDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
423
440
|
clickOutsideDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
424
441
|
returnFocusOnDeactivate: PropTypes.bool,
|
|
425
|
-
setReturnFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.func]),
|
|
442
|
+
setReturnFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.bool, PropTypes.func]),
|
|
426
443
|
allowOutsideClick: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
427
444
|
preventScroll: PropTypes.bool,
|
|
428
445
|
tabbableOptions: PropTypes.shape({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.1",
|
|
4
4
|
"description": "A React component that traps focus.",
|
|
5
5
|
"main": "dist/focus-trap-react.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -59,31 +59,31 @@
|
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@babel/cli": "^7.17.10",
|
|
61
61
|
"@babel/core": "^7.18.2",
|
|
62
|
-
"@babel/eslint-parser": "^7.
|
|
62
|
+
"@babel/eslint-parser": "^7.18.2",
|
|
63
63
|
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
|
64
|
-
"@babel/preset-env": "^7.18.
|
|
64
|
+
"@babel/preset-env": "^7.18.2",
|
|
65
65
|
"@babel/preset-react": "^7.17.12",
|
|
66
66
|
"@changesets/cli": "^2.22.0",
|
|
67
|
-
"@testing-library/cypress": "^8.0.
|
|
67
|
+
"@testing-library/cypress": "^8.0.3",
|
|
68
68
|
"@testing-library/dom": "^8.13.0",
|
|
69
69
|
"@testing-library/jest-dom": "^5.16.4",
|
|
70
|
-
"@testing-library/react": "^13.
|
|
70
|
+
"@testing-library/react": "^13.3.0",
|
|
71
71
|
"@testing-library/user-event": "^14.2.0",
|
|
72
72
|
"@types/jquery": "^3.5.14",
|
|
73
73
|
"all-contributors-cli": "^6.20.0",
|
|
74
|
-
"babel-jest": "^28.1.
|
|
74
|
+
"babel-jest": "^28.1.1",
|
|
75
75
|
"babelify": "^10.0.0",
|
|
76
76
|
"browserify": "^17.0.0",
|
|
77
77
|
"budo": "^11.7.0",
|
|
78
78
|
"cypress": "^9.7.0",
|
|
79
79
|
"cypress-plugin-tab": "^1.0.5",
|
|
80
|
-
"eslint": "^8.
|
|
80
|
+
"eslint": "^8.17.0",
|
|
81
81
|
"eslint-config-prettier": "^8.5.0",
|
|
82
82
|
"eslint-plugin-cypress": "^2.12.1",
|
|
83
|
-
"eslint-plugin-jest": "^26.
|
|
83
|
+
"eslint-plugin-jest": "^26.5.3",
|
|
84
84
|
"eslint-plugin-react": "^7.30.0",
|
|
85
|
-
"jest": "^28.1.
|
|
86
|
-
"jest-environment-jsdom": "^28.1.
|
|
85
|
+
"jest": "^28.1.1",
|
|
86
|
+
"jest-environment-jsdom": "^28.1.1",
|
|
87
87
|
"jest-watch-typeahead": "^1.1.0",
|
|
88
88
|
"onchange": "^7.1.0",
|
|
89
89
|
"prettier": "^2.6.2",
|
|
@@ -92,14 +92,15 @@
|
|
|
92
92
|
"react-dom": "^18.1.0",
|
|
93
93
|
"regenerator-runtime": "^0.13.9",
|
|
94
94
|
"start-server-and-test": "^1.14.0",
|
|
95
|
-
"typescript": "^4.7.
|
|
95
|
+
"typescript": "^4.7.3"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"focus-trap": "^6.9.
|
|
98
|
+
"focus-trap": "^6.9.4",
|
|
99
|
+
"tabbable": "^5.3.3"
|
|
99
100
|
},
|
|
100
101
|
"peerDependencies": {
|
|
101
102
|
"prop-types": "^15.8.1",
|
|
102
|
-
"react": ">=16.
|
|
103
|
-
"react-dom": ">=16.
|
|
103
|
+
"react": ">=16.3.0",
|
|
104
|
+
"react-dom": ">=16.3.0"
|
|
104
105
|
}
|
|
105
106
|
}
|
package/src/focus-trap-react.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const React = require('react');
|
|
2
|
-
const ReactDOM = require('react-dom');
|
|
3
2
|
const PropTypes = require('prop-types');
|
|
4
3
|
const { createFocusTrap } = require('focus-trap');
|
|
5
4
|
const { isFocusable } = require('tabbable');
|
|
6
5
|
|
|
7
|
-
// TODO: These issues are related to older React features which we'll likely need
|
|
8
|
-
// to fix in order to move the code forward to the next major version of React.
|
|
9
|
-
// @see https://github.com/davidtheclark/focus-trap-react/issues/77
|
|
10
|
-
/* eslint-disable react/no-find-dom-node */
|
|
11
|
-
|
|
12
6
|
class FocusTrap extends React.Component {
|
|
13
7
|
constructor(props) {
|
|
14
8
|
super(props);
|
|
@@ -42,11 +36,11 @@ class FocusTrap extends React.Component {
|
|
|
42
36
|
|
|
43
37
|
// original options provided by the consumer
|
|
44
38
|
this.originalOptions = {
|
|
45
|
-
// because of the above `
|
|
39
|
+
// because of the above `internalOptions`, we maintain our own flag for
|
|
46
40
|
// this option, and default it to `true` because that's focus-trap's default
|
|
47
41
|
returnFocusOnDeactivate: true,
|
|
48
42
|
|
|
49
|
-
// because of the above `
|
|
43
|
+
// because of the above `internalOptions`, we keep these separate since
|
|
50
44
|
// they're part of the deactivation process which we configure (internally) to
|
|
51
45
|
// be shared between focus-trap and focus-trap-react
|
|
52
46
|
onDeactivate: null,
|
|
@@ -71,7 +65,7 @@ class FocusTrap extends React.Component {
|
|
|
71
65
|
optionName === 'clickOutsideDeactivates'
|
|
72
66
|
) {
|
|
73
67
|
this.originalOptions[optionName] = focusTrapOptions[optionName];
|
|
74
|
-
continue; // exclude from
|
|
68
|
+
continue; // exclude from internalOptions
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
this.internalOptions[optionName] = focusTrapOptions[optionName];
|
|
@@ -106,36 +100,63 @@ class FocusTrap extends React.Component {
|
|
|
106
100
|
);
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Gets the node for the given option, which is expected to be an option that
|
|
105
|
+
* can be either a DOM node, a string that is a selector to get a node, `false`
|
|
106
|
+
* (if a node is explicitly NOT given), or a function that returns any of these
|
|
107
|
+
* values.
|
|
108
|
+
* @param {string} optionName
|
|
109
|
+
* @returns {undefined | false | HTMLElement | SVGElement} Returns
|
|
110
|
+
* `undefined` if the option is not specified; `false` if the option
|
|
111
|
+
* resolved to `false` (node explicitly not given); otherwise, the resolved
|
|
112
|
+
* DOM node.
|
|
113
|
+
* @throws {Error} If the option is set, not `false`, and is not, or does not
|
|
114
|
+
* resolve to a node.
|
|
115
|
+
*/
|
|
116
|
+
getNodeForOption = function (optionName, ...params) {
|
|
117
|
+
// use internal options first, falling back to original options
|
|
118
|
+
let optionValue =
|
|
119
|
+
this.internalOptions[optionName] ?? this.originalOptions[optionName];
|
|
120
|
+
|
|
121
|
+
if (typeof optionValue === 'function') {
|
|
122
|
+
optionValue = optionValue(...params);
|
|
114
123
|
}
|
|
115
124
|
|
|
116
|
-
|
|
125
|
+
if (optionValue === true) {
|
|
126
|
+
optionValue = undefined; // use default value
|
|
127
|
+
}
|
|
117
128
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
throw new Error(`\`${optionName}\` refers to no known node`);
|
|
129
|
+
if (!optionValue) {
|
|
130
|
+
if (optionValue === undefined || optionValue === false) {
|
|
131
|
+
return optionValue;
|
|
122
132
|
}
|
|
133
|
+
// else, empty string (invalid), null (invalid), 0 (invalid)
|
|
134
|
+
|
|
135
|
+
throw new Error(
|
|
136
|
+
`\`${optionName}\` was specified but was not a node, or did not return a node`
|
|
137
|
+
);
|
|
123
138
|
}
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
let node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
|
|
141
|
+
|
|
142
|
+
if (typeof optionValue === 'string') {
|
|
143
|
+
node = this.getDocument()?.querySelector(optionValue); // resolve to node, or null if fails
|
|
127
144
|
if (!node) {
|
|
128
|
-
throw new Error(
|
|
145
|
+
throw new Error(
|
|
146
|
+
`\`${optionName}\` as selector refers to no known node`
|
|
147
|
+
);
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
return node;
|
|
133
|
-
}
|
|
152
|
+
};
|
|
134
153
|
|
|
135
154
|
getReturnFocusNode() {
|
|
136
|
-
const node = this.getNodeForOption(
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
const node = this.getNodeForOption(
|
|
156
|
+
'setReturnFocus',
|
|
157
|
+
this.previouslyFocusedElement
|
|
158
|
+
);
|
|
159
|
+
return node ? node : node === false ? false : this.previouslyFocusedElement;
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
/** Update the previously focused element with the currently focused element. */
|
|
@@ -250,18 +271,11 @@ class FocusTrap extends React.Component {
|
|
|
250
271
|
|
|
251
272
|
setupFocusTrap() {
|
|
252
273
|
if (!this.focusTrap) {
|
|
253
|
-
const
|
|
254
|
-
// NOTE: `findDOMNode()` does not support CSS selectors; it'll just return
|
|
255
|
-
// a new text node with the text wrapped in it instead of treating the
|
|
256
|
-
// string as a selector and resolving it to a node in the DOM
|
|
257
|
-
ReactDOM.findDOMNode
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
const nodesExist = focusTrapElementDOMNodes.some(Boolean);
|
|
274
|
+
const nodesExist = this.focusTrapElements.some(Boolean);
|
|
261
275
|
if (nodesExist) {
|
|
262
276
|
// eslint-disable-next-line react/prop-types -- _createFocusTrap is an internal prop
|
|
263
277
|
this.focusTrap = this.props._createFocusTrap(
|
|
264
|
-
|
|
278
|
+
this.focusTrapElements,
|
|
265
279
|
this.internalOptions
|
|
266
280
|
);
|
|
267
281
|
|
|
@@ -351,7 +365,7 @@ class FocusTrap extends React.Component {
|
|
|
351
365
|
);
|
|
352
366
|
}
|
|
353
367
|
|
|
354
|
-
const
|
|
368
|
+
const callbackRef = (element) => {
|
|
355
369
|
const { containerElements } = this.props;
|
|
356
370
|
|
|
357
371
|
if (child) {
|
|
@@ -368,7 +382,7 @@ class FocusTrap extends React.Component {
|
|
|
368
382
|
};
|
|
369
383
|
|
|
370
384
|
const childWithRef = React.cloneElement(child, {
|
|
371
|
-
ref:
|
|
385
|
+
ref: callbackRef,
|
|
372
386
|
});
|
|
373
387
|
|
|
374
388
|
return childWithRef;
|
|
@@ -395,12 +409,13 @@ FocusTrap.propTypes = {
|
|
|
395
409
|
initialFocus: PropTypes.oneOfType([
|
|
396
410
|
PropTypes.instanceOf(ElementType),
|
|
397
411
|
PropTypes.string,
|
|
398
|
-
PropTypes.func,
|
|
399
412
|
PropTypes.bool,
|
|
413
|
+
PropTypes.func,
|
|
400
414
|
]),
|
|
401
415
|
fallbackFocus: PropTypes.oneOfType([
|
|
402
416
|
PropTypes.instanceOf(ElementType),
|
|
403
417
|
PropTypes.string,
|
|
418
|
+
// NOTE: does not support `false` as value (or return value from function)
|
|
404
419
|
PropTypes.func,
|
|
405
420
|
]),
|
|
406
421
|
escapeDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
@@ -412,6 +427,7 @@ FocusTrap.propTypes = {
|
|
|
412
427
|
setReturnFocus: PropTypes.oneOfType([
|
|
413
428
|
PropTypes.instanceOf(ElementType),
|
|
414
429
|
PropTypes.string,
|
|
430
|
+
PropTypes.bool,
|
|
415
431
|
PropTypes.func,
|
|
416
432
|
]),
|
|
417
433
|
allowOutsideClick: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|