focus-trap 7.6.0 β†’ 7.6.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.6.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d4a4c34: Replace findIndex with native implementation - [Closes #1305](https://github.com/focus-trap/focus-trap/issues/1305
8
+
9
+ ## 7.6.1
10
+
11
+ ### Patch Changes
12
+
13
+ - fc5910d: Fix fallbackFocus not used when initialFocus is selector to non-existent node ([#1218](https://github.com/focus-trap/focus-trap/issues/1218))
14
+
3
15
  ## 7.6.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # focus-trap [![CI](https://github.com/focus-trap/focus-trap/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/focus-trap/actions?query=workflow:CI+branch:master) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE)
2
2
 
3
3
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
4
- [![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg?style=flat-square)](#contributors)
4
+ [![All Contributors](https://img.shields.io/badge/all_contributors-34-orange.svg?style=flat-square)](#contributors)
5
5
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
6
6
 
7
7
  Trap focus within a DOM node.
@@ -101,9 +101,12 @@ Returns a new focus trap on `element` (one or more "containers" of tabbable node
101
101
  - **onDeactivate** `{() => void}`: A function that will be called **before** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation.
102
102
  - **onPostDeactivate** `{() => void}`: A function that will be called after the trap is deactivated, after `onDeactivate`. If the `returnFocus` deactivation option was set, it will be called **after** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation; otherwise, it will be called after deactivation completes.
103
103
  - **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: An animated trigger button will have a small delay between when `onDeactivate` is called and when the focus is able to be sent back to the trigger. `checkCanReturnFocus` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to to the node that had focus prior to the activation of the trap (or the node configured in the `setReturnFocus` option).
104
- - **initialFocus** `{HTMLElement | SVGElement | string | false | undefined | (() => HTMLElement | SVGElement | string | false | undefined)}`: By default, when a focus trap is activated the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns any of these. You can also set this option to `false` (or to a function that returns `false`) to prevent any initial focus at all when the trap activates.
104
+ - **initialFocus** `{HTMLElement | SVGElement | string | false | undefined | (() => HTMLElement | SVGElement | string | false | undefined)}`: By default (when `undefined` or the function returns `undefined`), when a focus trap is activated, the active element will receive focus if it's in the trap, otherwise, the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns any of these. You can also set this option to `false` (or to a function that returns `false`) to prevent any initial focus at all when the trap activates.
105
105
  - πŸ’¬ Setting this option to `false` (or a function that returns `false`) will prevent the `fallbackFocus` option from being used.
106
- - Returning `undefined` from a function will result in the default behavior.
106
+ - πŸ’¬ If the option resolves to a non-focusable node (e.g. one that exists, but is hidden), the default behavior will be used (as though the option weren't set at all).
107
+ - πŸ’¬ If the option resolves to a non-existent node, an exception will be thrown.
108
+ - πŸ’¬ If the option resolves to a valid selector string (directly set, or returned from a function), but the selector doesn't match a node, the trap will fall back to the `fallbackFocus` node option. If that option also fails to yield a node, an exception will be thrown.
109
+ - πŸ’¬ If the option resolves to `undefined` (i.e. not set or function returns `undefined`), the default behavior will be used.
107
110
  - ⚠️ See warning below about **Shadow DOM** and selector strings.
108
111
  - **fallbackFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement | string}`: By default, an error will be thrown if the focus trap contains no elements in its tab order. With this option you can specify a fallback element to programmatically receive focus if no other tabbable elements are found. For example, you may want a popover's `<div>` to receive focus if the popover's content includes no tabbable elements. *Make sure the fallback element has a negative `tabindex` so it can be programmatically focused.* The option value can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns any of these.
109
112
  - πŸ’¬ If `initialFocus` is `false` (or a function that returns `false`), this function will not be called when the trap is activated, and no element will be initially focused. This function may still be called while the trap is active if things change such that there are no longer any tabbable nodes in the trap.
@@ -413,6 +416,8 @@ In alphabetical order:
413
416
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/zioth"><img src="https://avatars3.githubusercontent.com/u/945603?v=4?s=100" width="100px;" alt="Zioth"/><br /><sub><b>Zioth</b></sub></a><br /><a href="#ideas-zioth" title="Ideas, Planning, & Feedback">πŸ€”</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Azioth" title="Bug reports">πŸ›</a></td>
414
417
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/glushkova91"><img src="https://avatars.githubusercontent.com/u/13402897?v=4?s=100" width="100px;" alt="glushkova91"/><br /><sub><b>glushkova91</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=glushkova91" title="Documentation">πŸ“–</a></td>
415
418
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/jpveooys"><img src="https://avatars.githubusercontent.com/u/66470099?v=4?s=100" width="100px;" alt="jpveooys"/><br /><sub><b>jpveooys</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Ajpveooys" title="Bug reports">πŸ›</a></td>
419
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/konradr33"><img src="https://avatars.githubusercontent.com/u/32595283?v=4?s=100" width="100px;" alt="konradr33"/><br /><sub><b>konradr33</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Akonradr33" title="Bug reports">πŸ›</a></td>
420
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/tomasvn"><img src="https://avatars.githubusercontent.com/u/17225564?v=4?s=100" width="100px;" alt="tomasvn"/><br /><sub><b>tomasvn</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=tomasvn" title="Code">πŸ’»</a></td>
416
421
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/simonxabris"><img src="https://avatars.githubusercontent.com/u/27497229?v=4?s=100" width="100px;" alt="Ábris Simon"/><br /><sub><b>Ábris Simon</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=simonxabris" title="Code">πŸ’»</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Asimonxabris" title="Bug reports">πŸ›</a></td>
417
422
  </tr>
418
423
  </tbody>
@@ -1,9 +1,17 @@
1
1
  /*!
2
- * focus-trap 7.6.0
2
+ * focus-trap 7.6.2
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
5
  import { isFocusable, tabbable, focusable, isTabbable, getTabIndex } from 'tabbable';
6
6
 
7
+ function _arrayLikeToArray(r, a) {
8
+ (null == a || a > r.length) && (a = r.length);
9
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
10
+ return n;
11
+ }
12
+ function _arrayWithoutHoles(r) {
13
+ if (Array.isArray(r)) return _arrayLikeToArray(r);
14
+ }
7
15
  function _defineProperty(e, r, t) {
8
16
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
9
17
  value: t,
@@ -12,6 +20,12 @@ function _defineProperty(e, r, t) {
12
20
  writable: !0
13
21
  }) : e[r] = t, e;
14
22
  }
23
+ function _iterableToArray(r) {
24
+ if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
25
+ }
26
+ function _nonIterableSpread() {
27
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
28
+ }
15
29
  function ownKeys(e, r) {
16
30
  var t = Object.keys(e);
17
31
  if (Object.getOwnPropertySymbols) {
@@ -33,6 +47,9 @@ function _objectSpread2(e) {
33
47
  }
34
48
  return e;
35
49
  }
50
+ function _toConsumableArray(r) {
51
+ return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
52
+ }
36
53
  function _toPrimitive(t, r) {
37
54
  if ("object" != typeof t || !t) return t;
38
55
  var e = t[Symbol.toPrimitive];
@@ -47,6 +64,13 @@ function _toPropertyKey(t) {
47
64
  var i = _toPrimitive(t, "string");
48
65
  return "symbol" == typeof i ? i : i + "";
49
66
  }
67
+ function _unsupportedIterableToArray(r, a) {
68
+ if (r) {
69
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
70
+ var t = {}.toString.call(r).slice(8, -1);
71
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
72
+ }
73
+ }
50
74
 
51
75
  var activeFocusTraps = {
52
76
  activateTrap: function activateTrap(trapStack, trap) {
@@ -98,20 +122,6 @@ var delay = function delay(fn) {
98
122
  return setTimeout(fn, 0);
99
123
  };
100
124
 
101
- // Array.find/findIndex() are not supported on IE; this replicates enough
102
- // of Array.findIndex() for our needs
103
- var findIndex = function findIndex(arr, fn) {
104
- var idx = -1;
105
- arr.every(function (value, i) {
106
- if (fn(value)) {
107
- idx = i;
108
- return false; // break
109
- }
110
- return true; // next
111
- });
112
- return idx;
113
- };
114
-
115
125
  /**
116
126
  * Get an option's value when it could be a plain value, or a handler that provides
117
127
  * the value.
@@ -221,7 +231,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
221
231
  return state.containerGroups.findIndex(function (_ref) {
222
232
  var container = _ref.container,
223
233
  tabbableNodes = _ref.tabbableNodes;
224
- return container.contains(element) || ( // fall back to explicit tabbable search which will take into consideration any
234
+ return container.contains(element) || (// fall back to explicit tabbable search which will take into consideration any
225
235
  // web components if the `tabbableOptions.getShadowRoot` option was used for
226
236
  // the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't
227
237
  // look inside web components even if open)
@@ -237,20 +247,27 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
237
247
  * (if a node is explicitly NOT given), or a function that returns any of these
238
248
  * values.
239
249
  * @param {string} optionName
240
- * @returns {undefined | false | HTMLElement | SVGElement} Returns
241
- * `undefined` if the option is not specified; `false` if the option
242
- * resolved to `false` (node explicitly not given); otherwise, the resolved
243
- * DOM node.
250
+ * @param {Object} options
251
+ * @param {boolean} [options.hasFallback] True if the option could be a selector string
252
+ * and the option allows for a fallback scenario in the case where the selector is
253
+ * valid but does not match a node (i.e. the queried node doesn't exist in the DOM).
254
+ * @param {Array} [options.params] Params to pass to the option if it's a function.
255
+ * @returns {undefined | null | false | HTMLElement | SVGElement} Returns
256
+ * `undefined` if the option is not specified; `null` if the option didn't resolve
257
+ * to a node but `options.hasFallback=true`, `false` if the option resolved to `false`
258
+ * (node explicitly not given); otherwise, the resolved DOM node.
244
259
  * @throws {Error} If the option is set, not `false`, and is not, or does not
245
- * resolve to a node.
260
+ * resolve to a node, unless the option is a selector string and `options.hasFallback=true`.
246
261
  */
247
262
  var getNodeForOption = function getNodeForOption(optionName) {
263
+ var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
264
+ _ref2$hasFallback = _ref2.hasFallback,
265
+ hasFallback = _ref2$hasFallback === void 0 ? false : _ref2$hasFallback,
266
+ _ref2$params = _ref2.params,
267
+ params = _ref2$params === void 0 ? [] : _ref2$params;
248
268
  var optionValue = config[optionName];
249
269
  if (typeof optionValue === 'function') {
250
- for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
251
- params[_key2 - 1] = arguments[_key2];
252
- }
253
- optionValue = optionValue.apply(void 0, params);
270
+ optionValue = optionValue.apply(void 0, _toConsumableArray(params));
254
271
  }
255
272
  if (optionValue === true) {
256
273
  optionValue = undefined; // use default value
@@ -266,21 +283,31 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
266
283
  var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
267
284
 
268
285
  if (typeof optionValue === 'string') {
269
- node = doc.querySelector(optionValue); // resolve to node, or null if fails
286
+ try {
287
+ node = doc.querySelector(optionValue); // resolve to node, or null if fails
288
+ } catch (err) {
289
+ throw new Error("`".concat(optionName, "` appears to be an invalid selector; error=\"").concat(err.message, "\""));
290
+ }
270
291
  if (!node) {
271
- throw new Error("`".concat(optionName, "` as selector refers to no known node"));
292
+ if (!hasFallback) {
293
+ throw new Error("`".concat(optionName, "` as selector refers to no known node"));
294
+ }
295
+ // else, `node` MUST be `null` because that's what `Document.querySelector()` returns
296
+ // if the selector is valid but doesn't match anything
272
297
  }
273
298
  }
274
299
  return node;
275
300
  };
276
301
  var getInitialFocusNode = function getInitialFocusNode() {
277
- var node = getNodeForOption('initialFocus');
302
+ var node = getNodeForOption('initialFocus', {
303
+ hasFallback: true
304
+ });
278
305
 
279
306
  // false explicitly indicates we want no initialFocus at all
280
307
  if (node === false) {
281
308
  return false;
282
309
  }
283
- if (node === undefined || !isFocusable(node, config.tabbableOptions)) {
310
+ if (node === undefined || node && !isFocusable(node, config.tabbableOptions)) {
284
311
  // option not specified nor focusable: use fallback options
285
312
  if (findContainerIndex(doc.activeElement) >= 0) {
286
313
  node = doc.activeElement;
@@ -291,6 +318,10 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
291
318
  // NOTE: `fallbackFocus` option function cannot return `false` (not supported)
292
319
  node = firstTabbableNode || getNodeForOption('fallbackFocus');
293
320
  }
321
+ } else if (node === null) {
322
+ // option is a VALID selector string that doesn't yield a node: use the `fallbackFocus`
323
+ // option instead of the default behavior when the option isn't specified at all
324
+ node = getNodeForOption('fallbackFocus');
294
325
  }
295
326
  if (!node) {
296
327
  throw new Error('Your focus-trap needs to have at least one focusable element');
@@ -431,7 +462,9 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
431
462
  }
432
463
  };
433
464
  var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
434
- var node = getNodeForOption('setReturnFocus', previousActiveElement);
465
+ var node = getNodeForOption('setReturnFocus', {
466
+ params: [previousActiveElement]
467
+ });
435
468
  return node ? node : node === false ? false : previousActiveElement;
436
469
  };
437
470
 
@@ -446,11 +479,11 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
446
479
  * @returns {Node|undefined} The next node, or `undefined` if a next node couldn't be
447
480
  * determined given the current state of the trap.
448
481
  */
449
- var findNextNavNode = function findNextNavNode(_ref2) {
450
- var target = _ref2.target,
451
- event = _ref2.event,
452
- _ref2$isBackward = _ref2.isBackward,
453
- isBackward = _ref2$isBackward === void 0 ? false : _ref2$isBackward;
482
+ var findNextNavNode = function findNextNavNode(_ref3) {
483
+ var target = _ref3.target,
484
+ event = _ref3.event,
485
+ _ref3$isBackward = _ref3.isBackward,
486
+ isBackward = _ref3$isBackward === void 0 ? false : _ref3$isBackward;
454
487
  target = target || getActualTarget(event);
455
488
  updateTabbableNodes();
456
489
  var destinationNode = null;
@@ -474,8 +507,8 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
474
507
  // REVERSE
475
508
 
476
509
  // is the target the first tabbable node in a group?
477
- var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
478
- var firstTabbableNode = _ref3.firstTabbableNode;
510
+ var startOfGroupIndex = state.tabbableGroups.findIndex(function (_ref4) {
511
+ var firstTabbableNode = _ref4.firstTabbableNode;
479
512
  return target === firstTabbableNode;
480
513
  });
481
514
  if (startOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) {
@@ -503,8 +536,8 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
503
536
  // FORWARD
504
537
 
505
538
  // is the target the last tabbable node in a group?
506
- var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref4) {
507
- var lastTabbableNode = _ref4.lastTabbableNode;
539
+ var lastOfGroupIndex = state.tabbableGroups.findIndex(function (_ref5) {
540
+ var lastTabbableNode = _ref5.lastTabbableNode;
508
541
  return target === lastTabbableNode;
509
542
  });
510
543
  if (lastOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) {