focus-trap 7.0.0 → 7.2.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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * focus-trap 7.0.0
2
+ * focus-trap 7.2.0
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
5
  (function (global, factory) {
@@ -15,17 +15,14 @@
15
15
 
16
16
  function ownKeys(object, enumerableOnly) {
17
17
  var keys = Object.keys(object);
18
-
19
18
  if (Object.getOwnPropertySymbols) {
20
19
  var symbols = Object.getOwnPropertySymbols(object);
21
20
  enumerableOnly && (symbols = symbols.filter(function (sym) {
22
21
  return Object.getOwnPropertyDescriptor(object, sym).enumerable;
23
22
  })), keys.push.apply(keys, symbols);
24
23
  }
25
-
26
24
  return keys;
27
25
  }
28
-
29
26
  function _objectSpread2(target) {
30
27
  for (var i = 1; i < arguments.length; i++) {
31
28
  var source = null != arguments[i] ? arguments[i] : {};
@@ -35,11 +32,10 @@
35
32
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
36
33
  });
37
34
  }
38
-
39
35
  return target;
40
36
  }
41
-
42
37
  function _defineProperty(obj, key, value) {
38
+ key = _toPropertyKey(key);
43
39
  if (key in obj) {
44
40
  Object.defineProperty(obj, key, {
45
41
  value: value,
@@ -50,64 +46,75 @@
50
46
  } else {
51
47
  obj[key] = value;
52
48
  }
53
-
54
49
  return obj;
55
50
  }
51
+ function _toPrimitive(input, hint) {
52
+ if (typeof input !== "object" || input === null) return input;
53
+ var prim = input[Symbol.toPrimitive];
54
+ if (prim !== undefined) {
55
+ var res = prim.call(input, hint || "default");
56
+ if (typeof res !== "object") return res;
57
+ throw new TypeError("@@toPrimitive must return a primitive value.");
58
+ }
59
+ return (hint === "string" ? String : Number)(input);
60
+ }
61
+ function _toPropertyKey(arg) {
62
+ var key = _toPrimitive(arg, "string");
63
+ return typeof key === "symbol" ? key : String(key);
64
+ }
56
65
 
57
- var activeFocusTraps = function () {
58
- var trapQueue = [];
59
- return {
60
- activateTrap: function activateTrap(trap) {
61
- if (trapQueue.length > 0) {
62
- var activeTrap = trapQueue[trapQueue.length - 1];
63
-
64
- if (activeTrap !== trap) {
65
- activeTrap.pause();
66
- }
67
- }
68
-
69
- var trapIndex = trapQueue.indexOf(trap);
70
-
71
- if (trapIndex === -1) {
72
- trapQueue.push(trap);
73
- } else {
74
- // move this existing trap to the front of the queue
75
- trapQueue.splice(trapIndex, 1);
76
- trapQueue.push(trap);
77
- }
78
- },
79
- deactivateTrap: function deactivateTrap(trap) {
80
- var trapIndex = trapQueue.indexOf(trap);
81
-
82
- if (trapIndex !== -1) {
83
- trapQueue.splice(trapIndex, 1);
84
- }
85
-
86
- if (trapQueue.length > 0) {
87
- trapQueue[trapQueue.length - 1].unpause();
66
+ var activeFocusTraps = {
67
+ activateTrap: function activateTrap(trapStack, trap) {
68
+ if (trapStack.length > 0) {
69
+ var activeTrap = trapStack[trapStack.length - 1];
70
+ if (activeTrap !== trap) {
71
+ activeTrap.pause();
88
72
  }
89
73
  }
90
- };
91
- }();
92
-
74
+ var trapIndex = trapStack.indexOf(trap);
75
+ if (trapIndex === -1) {
76
+ trapStack.push(trap);
77
+ } else {
78
+ // move this existing trap to the front of the queue
79
+ trapStack.splice(trapIndex, 1);
80
+ trapStack.push(trap);
81
+ }
82
+ },
83
+ deactivateTrap: function deactivateTrap(trapStack, trap) {
84
+ var trapIndex = trapStack.indexOf(trap);
85
+ if (trapIndex !== -1) {
86
+ trapStack.splice(trapIndex, 1);
87
+ }
88
+ if (trapStack.length > 0) {
89
+ trapStack[trapStack.length - 1].unpause();
90
+ }
91
+ }
92
+ };
93
93
  var isSelectableInput = function isSelectableInput(node) {
94
94
  return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function';
95
95
  };
96
-
97
96
  var isEscapeEvent = function isEscapeEvent(e) {
98
97
  return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
99
98
  };
100
-
101
99
  var isTabEvent = function isTabEvent(e) {
102
100
  return e.key === 'Tab' || e.keyCode === 9;
103
101
  };
104
102
 
103
+ // checks for TAB by default
104
+ var isKeyForward = function isKeyForward(e) {
105
+ return isTabEvent(e) && !e.shiftKey;
106
+ };
107
+
108
+ // checks for SHIFT+TAB by default
109
+ var isKeyBackward = function isKeyBackward(e) {
110
+ return isTabEvent(e) && e.shiftKey;
111
+ };
105
112
  var delay = function delay(fn) {
106
113
  return setTimeout(fn, 0);
107
- }; // Array.find/findIndex() are not supported on IE; this replicates enough
108
- // of Array.findIndex() for our needs
109
-
114
+ };
110
115
 
116
+ // Array.find/findIndex() are not supported on IE; this replicates enough
117
+ // of Array.findIndex() for our needs
111
118
  var findIndex = function findIndex(arr, fn) {
112
119
  var idx = -1;
113
120
  arr.every(function (value, i) {
@@ -118,8 +125,10 @@
118
125
 
119
126
  return true; // next
120
127
  });
128
+
121
129
  return idx;
122
130
  };
131
+
123
132
  /**
124
133
  * Get an option's value when it could be a plain value, or a handler that provides
125
134
  * the value.
@@ -127,16 +136,12 @@
127
136
  * @param {...*} [params] Any parameters to pass to the handler, if `value` is a function.
128
137
  * @returns {*} The `value`, or the handler's returned value.
129
138
  */
130
-
131
-
132
139
  var valueOrHandler = function valueOrHandler(value) {
133
140
  for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
134
141
  params[_key - 1] = arguments[_key];
135
142
  }
136
-
137
143
  return typeof value === 'function' ? value.apply(void 0, params) : value;
138
144
  };
139
-
140
145
  var getActualTarget = function getActualTarget(event) {
141
146
  // NOTE: If the trap is _inside_ a shadow DOM, event.target will always be the
142
147
  // shadow host. However, event.target.composedPath() will be an array of
@@ -148,17 +153,21 @@
148
153
  return event.target.shadowRoot && typeof event.composedPath === 'function' ? event.composedPath()[0] : event.target;
149
154
  };
150
155
 
156
+ // NOTE: this must be _outside_ `createFocusTrap()` to make sure all traps in this
157
+ // current instance use the same stack if `userOptions.trapStack` isn't specified
158
+ var internalTrapStack = [];
151
159
  var createFocusTrap = function createFocusTrap(elements, userOptions) {
152
160
  // SSR: a live trap shouldn't be created in this type of environment so this
153
161
  // should be safe code to execute if the `document` option isn't specified
154
162
  var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document;
155
-
163
+ var trapStack = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.trapStack) || internalTrapStack;
156
164
  var config = _objectSpread2({
157
165
  returnFocusOnDeactivate: true,
158
166
  escapeDeactivates: true,
159
- delayInitialFocus: true
167
+ delayInitialFocus: true,
168
+ isKeyForward: isKeyForward,
169
+ isKeyBackward: isKeyBackward
160
170
  }, userOptions);
161
-
162
171
  var state = {
163
172
  // containers given to createFocusTrap()
164
173
  // @type {Array<HTMLElement>}
@@ -178,6 +187,7 @@
178
187
  // }>}
179
188
  containerGroups: [],
180
189
  // same order/length as `containers` list
190
+
181
191
  // references to objects in `containerGroups`, but only those that actually have
182
192
  // tabbable nodes in them
183
193
  // NOTE: same order as `containers` and `containerGroups`, but __not necessarily__
@@ -201,10 +211,10 @@
201
211
  * @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName`
202
212
  * IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used.
203
213
  */
204
-
205
214
  var getOption = function getOption(configOverrideOptions, optionName, configOptionName) {
206
215
  return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName];
207
216
  };
217
+
208
218
  /**
209
219
  * Finds the index of the container that contains the element.
210
220
  * @param {HTMLElement} element
@@ -212,16 +222,15 @@
212
222
  * `state.containerGroups` (the order/length of these lists are the same); -1
213
223
  * if the element isn't found.
214
224
  */
215
-
216
-
217
225
  var findContainerIndex = function findContainerIndex(element) {
218
226
  // NOTE: search `containerGroups` because it's possible a group contains no tabbable
219
227
  // nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`)
220
228
  // and we still need to find the element in there
221
229
  return state.containerGroups.findIndex(function (_ref) {
222
230
  var container = _ref.container,
223
- tabbableNodes = _ref.tabbableNodes;
224
- return container.contains(element) || // fall back to explicit tabbable search which will take into consideration any
231
+ tabbableNodes = _ref.tabbableNodes;
232
+ return container.contains(element) ||
233
+ // fall back to explicit tabbable search which will take into consideration any
225
234
  // web components if the `tabbableOptions.getShadowRoot` option was used for
226
235
  // the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't
227
236
  // look inside web components even if open)
@@ -230,6 +239,7 @@
230
239
  });
231
240
  });
232
241
  };
242
+
233
243
  /**
234
244
  * Gets the node for the given option, which is expected to be an option that
235
245
  * can be either a DOM node, a string that is a selector to get a node, `false`
@@ -243,19 +253,14 @@
243
253
  * @throws {Error} If the option is set, not `false`, and is not, or does not
244
254
  * resolve to a node.
245
255
  */
246
-
247
-
248
256
  var getNodeForOption = function getNodeForOption(optionName) {
249
257
  var optionValue = config[optionName];
250
-
251
258
  if (typeof optionValue === 'function') {
252
259
  for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
253
260
  params[_key2 - 1] = arguments[_key2];
254
261
  }
255
-
256
262
  optionValue = optionValue.apply(void 0, params);
257
263
  }
258
-
259
264
  if (optionValue === true) {
260
265
  optionValue = undefined; // use default value
261
266
  }
@@ -263,56 +268,51 @@
263
268
  if (!optionValue) {
264
269
  if (optionValue === undefined || optionValue === false) {
265
270
  return optionValue;
266
- } // else, empty string (invalid), null (invalid), 0 (invalid)
267
-
271
+ }
272
+ // else, empty string (invalid), null (invalid), 0 (invalid)
268
273
 
269
274
  throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node"));
270
275
  }
271
-
272
276
  var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
273
277
 
274
278
  if (typeof optionValue === 'string') {
275
279
  node = doc.querySelector(optionValue); // resolve to node, or null if fails
276
-
277
280
  if (!node) {
278
281
  throw new Error("`".concat(optionName, "` as selector refers to no known node"));
279
282
  }
280
283
  }
281
-
282
284
  return node;
283
285
  };
284
-
285
286
  var getInitialFocusNode = function getInitialFocusNode() {
286
- var node = getNodeForOption('initialFocus'); // false explicitly indicates we want no initialFocus at all
287
+ var node = getNodeForOption('initialFocus');
287
288
 
289
+ // false explicitly indicates we want no initialFocus at all
288
290
  if (node === false) {
289
291
  return false;
290
292
  }
291
-
292
293
  if (node === undefined) {
293
294
  // option not specified: use fallback options
294
295
  if (findContainerIndex(doc.activeElement) >= 0) {
295
296
  node = doc.activeElement;
296
297
  } else {
297
298
  var firstTabbableGroup = state.tabbableGroups[0];
298
- var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode; // NOTE: `fallbackFocus` option function cannot return `false` (not supported)
299
+ var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
299
300
 
301
+ // NOTE: `fallbackFocus` option function cannot return `false` (not supported)
300
302
  node = firstTabbableNode || getNodeForOption('fallbackFocus');
301
303
  }
302
304
  }
303
-
304
305
  if (!node) {
305
306
  throw new Error('Your focus-trap needs to have at least one focusable element');
306
307
  }
307
-
308
308
  return node;
309
309
  };
310
-
311
310
  var updateTabbableNodes = function updateTabbableNodes() {
312
311
  state.containerGroups = state.containers.map(function (container) {
313
- var tabbableNodes = tabbable.tabbable(container, config.tabbableOptions); // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes
314
- // are a superset of tabbable nodes
312
+ var tabbableNodes = tabbable.tabbable(container, config.tabbableOptions);
315
313
 
314
+ // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes
315
+ // are a superset of tabbable nodes
316
316
  var focusableNodes = tabbable.focusable(container, config.tabbableOptions);
317
317
  return {
318
318
  container: container,
@@ -320,7 +320,6 @@
320
320
  focusableNodes: focusableNodes,
321
321
  firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null,
322
322
  lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null,
323
-
324
323
  /**
325
324
  * Finds the __tabbable__ node that follows the given node in the specified direction,
326
325
  * in this container, if any.
@@ -344,17 +343,14 @@
344
343
  var nodeIdx = focusableNodes.findIndex(function (n) {
345
344
  return n === node;
346
345
  });
347
-
348
346
  if (nodeIdx < 0) {
349
347
  return undefined;
350
348
  }
351
-
352
349
  if (forward) {
353
350
  return focusableNodes.slice(nodeIdx + 1).find(function (n) {
354
351
  return tabbable.isTabbable(n, config.tabbableOptions);
355
352
  });
356
353
  }
357
-
358
354
  return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) {
359
355
  return tabbable.isTabbable(n, config.tabbableOptions);
360
356
  });
@@ -363,53 +359,46 @@
363
359
  });
364
360
  state.tabbableGroups = state.containerGroups.filter(function (group) {
365
361
  return group.tabbableNodes.length > 0;
366
- }); // throw if no groups have tabbable nodes and we don't have a fallback focus node either
362
+ });
367
363
 
364
+ // throw if no groups have tabbable nodes and we don't have a fallback focus node either
368
365
  if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option
369
366
  ) {
370
367
  throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times');
371
368
  }
372
369
  };
373
-
374
370
  var tryFocus = function tryFocus(node) {
375
371
  if (node === false) {
376
372
  return;
377
373
  }
378
-
379
374
  if (node === doc.activeElement) {
380
375
  return;
381
376
  }
382
-
383
377
  if (!node || !node.focus) {
384
378
  tryFocus(getInitialFocusNode());
385
379
  return;
386
380
  }
387
-
388
381
  node.focus({
389
382
  preventScroll: !!config.preventScroll
390
383
  });
391
384
  state.mostRecentlyFocusedNode = node;
392
-
393
385
  if (isSelectableInput(node)) {
394
386
  node.select();
395
387
  }
396
388
  };
397
-
398
389
  var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
399
390
  var node = getNodeForOption('setReturnFocus', previousActiveElement);
400
391
  return node ? node : node === false ? false : previousActiveElement;
401
- }; // This needs to be done on mousedown and touchstart instead of click
402
- // so that it precedes the focus event.
403
-
392
+ };
404
393
 
394
+ // This needs to be done on mousedown and touchstart instead of click
395
+ // so that it precedes the focus event.
405
396
  var checkPointerDown = function checkPointerDown(e) {
406
397
  var target = getActualTarget(e);
407
-
408
398
  if (findContainerIndex(target) >= 0) {
409
399
  // allow the click since it ocurred inside the trap
410
400
  return;
411
401
  }
412
-
413
402
  if (valueOrHandler(config.clickOutsideDeactivates, e)) {
414
403
  // immediately deactivate the trap
415
404
  trap.deactivate({
@@ -427,25 +416,26 @@
427
416
  returnFocus: config.returnFocusOnDeactivate && !tabbable.isFocusable(target, config.tabbableOptions)
428
417
  });
429
418
  return;
430
- } // This is needed for mobile devices.
419
+ }
420
+
421
+ // This is needed for mobile devices.
431
422
  // (If we'll only let `click` events through,
432
423
  // then on mobile they will be blocked anyways if `touchstart` is blocked.)
433
-
434
-
435
424
  if (valueOrHandler(config.allowOutsideClick, e)) {
436
425
  // allow the click outside the trap to take place
437
426
  return;
438
- } // otherwise, prevent the click
439
-
427
+ }
440
428
 
429
+ // otherwise, prevent the click
441
430
  e.preventDefault();
442
- }; // In case focus escapes the trap for some strange reason, pull it back in.
443
-
431
+ };
444
432
 
433
+ // In case focus escapes the trap for some strange reason, pull it back in.
445
434
  var checkFocusIn = function checkFocusIn(e) {
446
435
  var target = getActualTarget(e);
447
- var targetContained = findContainerIndex(target) >= 0; // In Firefox when you Tab out of an iframe the Document is briefly focused.
436
+ var targetContained = findContainerIndex(target) >= 0;
448
437
 
438
+ // In Firefox when you Tab out of an iframe the Document is briefly focused.
449
439
  if (targetContained || target instanceof Document) {
450
440
  if (targetContained) {
451
441
  state.mostRecentlyFocusedNode = target;
@@ -455,42 +445,41 @@
455
445
  e.stopImmediatePropagation();
456
446
  tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
457
447
  }
458
- }; // Hijack Tab events on the first and last focusable nodes of the trap,
448
+ };
449
+
450
+ // Hijack key nav events on the first and last focusable nodes of the trap,
459
451
  // in order to prevent focus from escaping. If it escapes for even a
460
452
  // moment it can end up scrolling the page and causing confusion so we
461
453
  // kind of need to capture the action at the keydown phase.
462
-
463
-
464
- var checkTab = function checkTab(e) {
465
- var target = getActualTarget(e);
454
+ var checkKeyNav = function checkKeyNav(event) {
455
+ var isBackward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
456
+ var target = getActualTarget(event);
466
457
  updateTabbableNodes();
467
458
  var destinationNode = null;
468
-
469
459
  if (state.tabbableGroups.length > 0) {
470
460
  // make sure the target is actually contained in a group
471
461
  // NOTE: the target may also be the container itself if it's focusable
472
462
  // with tabIndex='-1' and was given initial focus
473
463
  var containerIndex = findContainerIndex(target);
474
464
  var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined;
475
-
476
465
  if (containerIndex < 0) {
477
466
  // target not found in any group: quite possible focus has escaped the trap,
478
- // so bring it back in to...
479
- if (e.shiftKey) {
467
+ // so bring it back into...
468
+ if (isBackward) {
480
469
  // ...the last node in the last group
481
470
  destinationNode = state.tabbableGroups[state.tabbableGroups.length - 1].lastTabbableNode;
482
471
  } else {
483
472
  // ...the first node in the first group
484
473
  destinationNode = state.tabbableGroups[0].firstTabbableNode;
485
474
  }
486
- } else if (e.shiftKey) {
475
+ } else if (isBackward) {
487
476
  // REVERSE
477
+
488
478
  // is the target the first tabbable node in a group?
489
479
  var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref2) {
490
480
  var firstTabbableNode = _ref2.firstTabbableNode;
491
481
  return target === firstTabbableNode;
492
482
  });
493
-
494
483
  if (startOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) {
495
484
  // an exception case where the target is either the container itself, or
496
485
  // a non-tabbable node that was given focus (i.e. tabindex is negative
@@ -500,7 +489,6 @@
500
489
  // first tabbable node, and go to the last tabbable node of the LAST group
501
490
  startOfGroupIndex = containerIndex;
502
491
  }
503
-
504
492
  if (startOfGroupIndex >= 0) {
505
493
  // YES: then shift+tab should go to the last tabbable node in the
506
494
  // previous group (and wrap around to the last tabbable node of
@@ -508,15 +496,19 @@
508
496
  var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
509
497
  var destinationGroup = state.tabbableGroups[destinationGroupIndex];
510
498
  destinationNode = destinationGroup.lastTabbableNode;
499
+ } else if (!isTabEvent(event)) {
500
+ // user must have customized the nav keys so we have to move focus manually _within_
501
+ // the active group: do this based on the order determined by tabbable()
502
+ destinationNode = containerGroup.nextTabbableNode(target, false);
511
503
  }
512
504
  } else {
513
505
  // FORWARD
506
+
514
507
  // is the target the last tabbable node in a group?
515
508
  var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
516
509
  var lastTabbableNode = _ref3.lastTabbableNode;
517
510
  return target === lastTabbableNode;
518
511
  });
519
-
520
512
  if (lastOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) {
521
513
  // an exception case where the target is the container itself, or
522
514
  // a non-tabbable node that was given focus (i.e. tabindex is negative
@@ -526,73 +518,76 @@
526
518
  // last tabbable node, and go to the first tabbable node of the FIRST group
527
519
  lastOfGroupIndex = containerIndex;
528
520
  }
529
-
530
521
  if (lastOfGroupIndex >= 0) {
531
522
  // YES: then tab should go to the first tabbable node in the next
532
523
  // group (and wrap around to the first tabbable node of the FIRST
533
524
  // group if it's the last tabbable node of the LAST group)
534
525
  var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
535
-
536
526
  var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
537
527
  destinationNode = _destinationGroup.firstTabbableNode;
528
+ } else if (!isTabEvent(event)) {
529
+ // user must have customized the nav keys so we have to move focus manually _within_
530
+ // the active group: do this based on the order determined by tabbable()
531
+ destinationNode = containerGroup.nextTabbableNode(target);
538
532
  }
539
533
  }
540
534
  } else {
535
+ // no groups available
541
536
  // NOTE: the fallbackFocus option does not support returning false to opt-out
542
537
  destinationNode = getNodeForOption('fallbackFocus');
543
538
  }
544
-
545
539
  if (destinationNode) {
546
- e.preventDefault();
540
+ if (isTabEvent(event)) {
541
+ // since tab natively moves focus, we wouldn't have a destination node unless we
542
+ // were on the edge of a container and had to move to the next/previous edge, in
543
+ // which case we want to prevent default to keep the browser from moving focus
544
+ // to where it normally would
545
+ event.preventDefault();
546
+ }
547
547
  tryFocus(destinationNode);
548
- } // else, let the browser take care of [shift+]tab and move the focus
549
-
548
+ }
549
+ // else, let the browser take care of [shift+]tab and move the focus
550
550
  };
551
551
 
552
- var checkKey = function checkKey(e) {
553
- if (isEscapeEvent(e) && valueOrHandler(config.escapeDeactivates, e) !== false) {
554
- e.preventDefault();
552
+ var checkKey = function checkKey(event) {
553
+ if (isEscapeEvent(event) && valueOrHandler(config.escapeDeactivates, event) !== false) {
554
+ event.preventDefault();
555
555
  trap.deactivate();
556
556
  return;
557
557
  }
558
-
559
- if (isTabEvent(e)) {
560
- checkTab(e);
561
- return;
558
+ if (config.isKeyForward(event) || config.isKeyBackward(event)) {
559
+ checkKeyNav(event, config.isKeyBackward(event));
562
560
  }
563
561
  };
564
-
565
562
  var checkClick = function checkClick(e) {
566
563
  var target = getActualTarget(e);
567
-
568
564
  if (findContainerIndex(target) >= 0) {
569
565
  return;
570
566
  }
571
-
572
567
  if (valueOrHandler(config.clickOutsideDeactivates, e)) {
573
568
  return;
574
569
  }
575
-
576
570
  if (valueOrHandler(config.allowOutsideClick, e)) {
577
571
  return;
578
572
  }
579
-
580
573
  e.preventDefault();
581
574
  e.stopImmediatePropagation();
582
- }; //
575
+ };
576
+
577
+ //
583
578
  // EVENT LISTENERS
584
579
  //
585
580
 
586
-
587
581
  var addListeners = function addListeners() {
588
582
  if (!state.active) {
589
583
  return;
590
- } // There can be only one listening focus trap at a time
584
+ }
591
585
 
586
+ // There can be only one listening focus trap at a time
587
+ activeFocusTraps.activateTrap(trapStack, trap);
592
588
 
593
- activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
589
+ // Delay ensures that the focused element doesn't capture the event
594
590
  // that caused the focus trap activation.
595
-
596
591
  state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () {
597
592
  tryFocus(getInitialFocusNode());
598
593
  }) : tryFocus(getInitialFocusNode());
@@ -615,70 +610,58 @@
615
610
  });
616
611
  return trap;
617
612
  };
618
-
619
613
  var removeListeners = function removeListeners() {
620
614
  if (!state.active) {
621
615
  return;
622
616
  }
623
-
624
617
  doc.removeEventListener('focusin', checkFocusIn, true);
625
618
  doc.removeEventListener('mousedown', checkPointerDown, true);
626
619
  doc.removeEventListener('touchstart', checkPointerDown, true);
627
620
  doc.removeEventListener('click', checkClick, true);
628
621
  doc.removeEventListener('keydown', checkKey, true);
629
622
  return trap;
630
- }; //
623
+ };
624
+
625
+ //
631
626
  // TRAP DEFINITION
632
627
  //
633
628
 
634
-
635
629
  trap = {
636
630
  get active() {
637
631
  return state.active;
638
632
  },
639
-
640
633
  get paused() {
641
634
  return state.paused;
642
635
  },
643
-
644
636
  activate: function activate(activateOptions) {
645
637
  if (state.active) {
646
638
  return this;
647
639
  }
648
-
649
640
  var onActivate = getOption(activateOptions, 'onActivate');
650
641
  var onPostActivate = getOption(activateOptions, 'onPostActivate');
651
642
  var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
652
-
653
643
  if (!checkCanFocusTrap) {
654
644
  updateTabbableNodes();
655
645
  }
656
-
657
646
  state.active = true;
658
647
  state.paused = false;
659
648
  state.nodeFocusedBeforeActivation = doc.activeElement;
660
-
661
649
  if (onActivate) {
662
650
  onActivate();
663
651
  }
664
-
665
652
  var finishActivation = function finishActivation() {
666
653
  if (checkCanFocusTrap) {
667
654
  updateTabbableNodes();
668
655
  }
669
-
670
656
  addListeners();
671
-
672
657
  if (onPostActivate) {
673
658
  onPostActivate();
674
659
  }
675
660
  };
676
-
677
661
  if (checkCanFocusTrap) {
678
662
  checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
679
663
  return this;
680
664
  }
681
-
682
665
  finishActivation();
683
666
  return this;
684
667
  },
@@ -686,46 +669,38 @@
686
669
  if (!state.active) {
687
670
  return this;
688
671
  }
689
-
690
672
  var options = _objectSpread2({
691
673
  onDeactivate: config.onDeactivate,
692
674
  onPostDeactivate: config.onPostDeactivate,
693
675
  checkCanReturnFocus: config.checkCanReturnFocus
694
676
  }, deactivateOptions);
695
-
696
677
  clearTimeout(state.delayInitialFocusTimer); // noop if undefined
697
-
698
678
  state.delayInitialFocusTimer = undefined;
699
679
  removeListeners();
700
680
  state.active = false;
701
681
  state.paused = false;
702
- activeFocusTraps.deactivateTrap(trap);
682
+ activeFocusTraps.deactivateTrap(trapStack, trap);
703
683
  var onDeactivate = getOption(options, 'onDeactivate');
704
684
  var onPostDeactivate = getOption(options, 'onPostDeactivate');
705
685
  var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus');
706
686
  var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate');
707
-
708
687
  if (onDeactivate) {
709
688
  onDeactivate();
710
689
  }
711
-
712
690
  var finishDeactivation = function finishDeactivation() {
713
691
  delay(function () {
714
692
  if (returnFocus) {
715
693
  tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
716
694
  }
717
-
718
695
  if (onPostDeactivate) {
719
696
  onPostDeactivate();
720
697
  }
721
698
  });
722
699
  };
723
-
724
700
  if (returnFocus && checkCanReturnFocus) {
725
701
  checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation);
726
702
  return this;
727
703
  }
728
-
729
704
  finishDeactivation();
730
705
  return this;
731
706
  },
@@ -733,7 +708,6 @@
733
708
  if (state.paused || !state.active) {
734
709
  return this;
735
710
  }
736
-
737
711
  state.paused = true;
738
712
  removeListeners();
739
713
  return this;
@@ -742,7 +716,6 @@
742
716
  if (!state.paused || !state.active) {
743
717
  return this;
744
718
  }
745
-
746
719
  state.paused = false;
747
720
  updateTabbableNodes();
748
721
  addListeners();
@@ -753,15 +726,14 @@
753
726
  state.containers = elementsAsArray.map(function (element) {
754
727
  return typeof element === 'string' ? doc.querySelector(element) : element;
755
728
  });
756
-
757
729
  if (state.active) {
758
730
  updateTabbableNodes();
759
731
  }
760
-
761
732
  return this;
762
733
  }
763
- }; // initialize container elements
734
+ };
764
735
 
736
+ // initialize container elements
765
737
  trap.updateContainerElements(elements);
766
738
  return trap;
767
739
  };