focus-trap-react 8.11.1 → 9.0.0

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 4a77d87: Stop using the infamous `findDOMNode()` on provided `containerElements`.
8
+ - 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.
9
+ - 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).
10
+
11
+ ## 8.11.3
12
+
13
+ ### Patch Changes
14
+
15
+ - 9947461: Bump focus-trap dependency to v6.9.4 to get typings fix.
16
+ - 519e5a5: Fix setReturnFocus option as function not being passed node focused prior to activation.
17
+
18
+ ## 8.11.2
19
+
20
+ ### Patch Changes
21
+
22
+ - 7547d93: Bumps focus-trap to v6.9.3 to pick-up some small bug fixes from underlying tabbable.
23
+
3
24
  ## 8.11.1
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -220,7 +220,7 @@ 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="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></td>
223
+ <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
224
  <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
225
  <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
226
  <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>
@@ -10,7 +10,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
10
10
 
11
11
  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
12
 
13
- function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
13
+ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
14
14
 
15
15
  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
16
 
@@ -20,11 +20,11 @@ function _assertThisInitialized(self) { if (self === void 0) { throw new Referen
20
20
 
21
21
  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
22
 
23
- function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
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
- var React = require('react');
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 ReactDOM = require('react-dom');
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; // TODO: These issues are related to older React features which we'll likely need
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 `tailoredFocusTrapOptions`, we maintain our own flag for
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 `tailoredFocusTrapOptions`, we keep these separate since
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 tailoredFocusTrapOptions
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 focusTrapElementDOMNodes = this.focusTrapElements.map( // NOTE: `findDOMNode()` does not support CSS selectors; it'll just return
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(focusTrapElementDOMNodes, this.internalOptions);
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 composedRefCallback = function composedRefCallback(element) {
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: composedRefCallback
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.func, PropTypes.bool]),
421
- fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(ElementType), PropTypes.string, PropTypes.func]),
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": "8.11.1",
3
+ "version": "9.0.0",
4
4
  "description": "A React component that traps focus.",
5
5
  "main": "dist/focus-trap-react.js",
6
6
  "types": "index.d.ts",
@@ -58,32 +58,32 @@
58
58
  "homepage": "https://github.com/focus-trap/focus-trap-react#readme",
59
59
  "devDependencies": {
60
60
  "@babel/cli": "^7.17.10",
61
- "@babel/core": "^7.17.10",
62
- "@babel/eslint-parser": "^7.17.0",
63
- "@babel/plugin-proposal-class-properties": "^7.16.5",
64
- "@babel/preset-env": "^7.17.10",
65
- "@babel/preset-react": "^7.16.7",
61
+ "@babel/core": "^7.18.2",
62
+ "@babel/eslint-parser": "^7.18.2",
63
+ "@babel/plugin-proposal-class-properties": "^7.17.12",
64
+ "@babel/preset-env": "^7.18.2",
65
+ "@babel/preset-react": "^7.17.12",
66
66
  "@changesets/cli": "^2.22.0",
67
- "@testing-library/cypress": "^8.0.2",
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.2.0",
71
- "@testing-library/user-event": "^14.1.1",
70
+ "@testing-library/react": "^13.3.0",
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.0.3",
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
- "cypress": "^9.6.0",
78
+ "cypress": "^9.7.0",
79
79
  "cypress-plugin-tab": "^1.0.5",
80
- "eslint": "^8.14.0",
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.1.5",
84
- "eslint-plugin-react": "^7.29.4",
85
- "jest": "^28.0.3",
86
- "jest-environment-jsdom": "^28.0.2",
83
+ "eslint-plugin-jest": "^26.5.3",
84
+ "eslint-plugin-react": "^7.30.0",
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,14 @@
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.6.4"
95
+ "typescript": "^4.7.3"
96
96
  },
97
97
  "dependencies": {
98
- "focus-trap": "^6.9.1"
98
+ "focus-trap": "^6.9.4"
99
99
  },
100
100
  "peerDependencies": {
101
101
  "prop-types": "^15.8.1",
102
- "react": ">=16.0.0",
103
- "react-dom": ">=16.0.0"
102
+ "react": ">=16.3.0",
103
+ "react-dom": ">=16.3.0"
104
104
  }
105
105
  }
@@ -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 `tailoredFocusTrapOptions`, we maintain our own flag for
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 `tailoredFocusTrapOptions`, we keep these separate since
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 tailoredFocusTrapOptions
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
- // TODO: Need more test coverage for this function
110
- getNodeForOption(optionName) {
111
- const optionValue = this.internalOptions[optionName];
112
- if (!optionValue) {
113
- return null;
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
- let node = optionValue;
125
+ if (optionValue === true) {
126
+ optionValue = undefined; // use default value
127
+ }
117
128
 
118
- if (typeof optionValue === 'string') {
119
- node = this.getDocument()?.querySelector(optionValue);
120
- if (!node) {
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
- if (typeof optionValue === 'function') {
126
- node = optionValue();
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(`\`${optionName}\` did not return a node`);
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('setReturnFocus');
137
-
138
- return node ? node : this.previouslyFocusedElement;
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 focusTrapElementDOMNodes = this.focusTrapElements.map(
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
- focusTrapElementDOMNodes,
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 composedRefCallback = (element) => {
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: composedRefCallback,
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]),