focus-trap 6.2.0 → 6.3.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/index.js CHANGED
@@ -1,19 +1,19 @@
1
1
  import { tabbable, isFocusable } from 'tabbable';
2
2
 
3
- var activeFocusDelay;
3
+ let activeFocusDelay;
4
4
 
5
- var activeFocusTraps = (function () {
6
- var trapQueue = [];
5
+ const activeFocusTraps = (function () {
6
+ const trapQueue = [];
7
7
  return {
8
- activateTrap: function (trap) {
8
+ activateTrap(trap) {
9
9
  if (trapQueue.length > 0) {
10
- var activeTrap = trapQueue[trapQueue.length - 1];
10
+ const activeTrap = trapQueue[trapQueue.length - 1];
11
11
  if (activeTrap !== trap) {
12
12
  activeTrap.pause();
13
13
  }
14
14
  }
15
15
 
16
- var trapIndex = trapQueue.indexOf(trap);
16
+ const trapIndex = trapQueue.indexOf(trap);
17
17
  if (trapIndex === -1) {
18
18
  trapQueue.push(trap);
19
19
  } else {
@@ -23,8 +23,8 @@ var activeFocusTraps = (function () {
23
23
  }
24
24
  },
25
25
 
26
- deactivateTrap: function (trap) {
27
- var trapIndex = trapQueue.indexOf(trap);
26
+ deactivateTrap(trap) {
27
+ const trapIndex = trapQueue.indexOf(trap);
28
28
  if (trapIndex !== -1) {
29
29
  trapQueue.splice(trapIndex, 1);
30
30
  }
@@ -36,200 +36,124 @@ var activeFocusTraps = (function () {
36
36
  };
37
37
  })();
38
38
 
39
- function createFocusTrap(elements, userOptions) {
40
- var doc = document;
39
+ const isSelectableInput = function (node) {
40
+ return (
41
+ node.tagName &&
42
+ node.tagName.toLowerCase() === 'input' &&
43
+ typeof node.select === 'function'
44
+ );
45
+ };
46
+
47
+ const isEscapeEvent = function (e) {
48
+ return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
49
+ };
50
+
51
+ const isTabEvent = function (e) {
52
+ return e.key === 'Tab' || e.keyCode === 9;
53
+ };
54
+
55
+ const delay = function (fn) {
56
+ return setTimeout(fn, 0);
57
+ };
58
+
59
+ // Array.find/findIndex() are not supported on IE; this replicates enough
60
+ // of Array.findIndex() for our needs
61
+ const findIndex = function (arr, fn) {
62
+ let idx = -1;
63
+
64
+ arr.every(function (value, i) {
65
+ if (fn(value)) {
66
+ idx = i;
67
+ return false; // break
68
+ }
69
+
70
+ return true; // next
71
+ });
72
+
73
+ return idx;
74
+ };
75
+
76
+ /**
77
+ * Get an option's value when it could be a plain value, or a handler that provides
78
+ * the value.
79
+ * @param {*} value Option's value to check.
80
+ * @param {...*} [params] Any parameters to pass to the handler, if `value` is a function.
81
+ * @returns {*} The `value`, or the handler's returned value.
82
+ */
83
+ const valueOrHandler = function (value, ...params) {
84
+ return typeof value === 'function' ? value(...params) : value;
85
+ };
86
+
87
+ const createFocusTrap = function (elements, userOptions) {
88
+ const doc = document;
41
89
 
42
- var config = {
90
+ const config = {
43
91
  returnFocusOnDeactivate: true,
44
92
  escapeDeactivates: true,
45
93
  delayInitialFocus: true,
46
94
  ...userOptions,
47
95
  };
48
96
 
49
- var state = {
97
+ const state = {
50
98
  // @type {Array<HTMLElement>}
51
99
  containers: [],
52
- // @type {{ firstTabbableNode: HTMLElement, lastTabbableNode: HTMLElement }}
100
+
101
+ // list of objects identifying the first and last tabbable nodes in all containers/groups in
102
+ // the trap
103
+ // NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
104
+ // is active, but the trap should never get to a state where there isn't at least one group
105
+ // with at least one tabbable node in it (that would lead to an error condition that would
106
+ // result in an error being thrown)
107
+ // @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>}
53
108
  tabbableGroups: [],
109
+
54
110
  nodeFocusedBeforeActivation: null,
55
111
  mostRecentlyFocusedNode: null,
56
112
  active: false,
57
113
  paused: false,
58
114
  };
59
115
 
60
- var trap = {
61
- activate: activate,
62
- deactivate: deactivate,
63
- pause: pause,
64
- unpause: unpause,
65
- updateContainerElements: updateContainerElements,
66
- };
67
-
68
- updateContainerElements(elements);
69
-
70
- return trap;
71
-
72
- function updateContainerElements(containerElements) {
73
- var elementsAsArray = [].concat(containerElements).filter(Boolean);
74
-
75
- state.containers = elementsAsArray.map((element) =>
76
- typeof element === 'string' ? doc.querySelector(element) : element
77
- );
78
-
79
- if (state.active) {
80
- updateTabbableNodes();
81
- }
82
-
83
- return trap;
84
- }
85
-
86
- function activate(activateOptions) {
87
- if (state.active) return;
88
-
89
- updateTabbableNodes();
90
-
91
- state.active = true;
92
- state.paused = false;
93
- state.nodeFocusedBeforeActivation = doc.activeElement;
94
-
95
- var onActivate =
96
- activateOptions && activateOptions.onActivate
97
- ? activateOptions.onActivate
98
- : config.onActivate;
99
- if (onActivate) {
100
- onActivate();
101
- }
102
-
103
- addListeners();
104
- return trap;
105
- }
106
-
107
- function deactivate(deactivateOptions) {
108
- if (!state.active) return;
109
-
110
- clearTimeout(activeFocusDelay);
111
-
112
- removeListeners();
113
- state.active = false;
114
- state.paused = false;
115
-
116
- activeFocusTraps.deactivateTrap(trap);
117
-
118
- var onDeactivate =
119
- deactivateOptions && deactivateOptions.onDeactivate !== undefined
120
- ? deactivateOptions.onDeactivate
121
- : config.onDeactivate;
122
- if (onDeactivate) {
123
- onDeactivate();
124
- }
125
-
126
- var returnFocus =
127
- deactivateOptions && deactivateOptions.returnFocus !== undefined
128
- ? deactivateOptions.returnFocus
129
- : config.returnFocusOnDeactivate;
130
- if (returnFocus) {
131
- delay(function () {
132
- tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
133
- });
134
- }
135
-
136
- return trap;
137
- }
138
-
139
- function pause() {
140
- if (state.paused || !state.active) return trap;
141
- state.paused = true;
142
- removeListeners();
143
-
144
- return trap;
145
- }
146
-
147
- function unpause() {
148
- if (!state.paused || !state.active) return trap;
149
- state.paused = false;
150
- updateTabbableNodes();
151
- addListeners();
152
-
153
- return trap;
154
- }
155
-
156
- function addListeners() {
157
- if (!state.active) return;
158
-
159
- // There can be only one listening focus trap at a time
160
- activeFocusTraps.activateTrap(trap);
161
-
162
- // Delay ensures that the focused element doesn't capture the event
163
- // that caused the focus trap activation.
164
- activeFocusDelay = config.delayInitialFocus
165
- ? delay(function () {
166
- tryFocus(getInitialFocusNode());
167
- })
168
- : tryFocus(getInitialFocusNode());
169
-
170
- doc.addEventListener('focusin', checkFocusIn, true);
171
- doc.addEventListener('mousedown', checkPointerDown, {
172
- capture: true,
173
- passive: false,
174
- });
175
- doc.addEventListener('touchstart', checkPointerDown, {
176
- capture: true,
177
- passive: false,
178
- });
179
- doc.addEventListener('click', checkClick, {
180
- capture: true,
181
- passive: false,
182
- });
183
- doc.addEventListener('keydown', checkKey, {
184
- capture: true,
185
- passive: false,
186
- });
116
+ let trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later
187
117
 
188
- return trap;
189
- }
190
-
191
- function removeListeners() {
192
- if (!state.active) return;
193
-
194
- doc.removeEventListener('focusin', checkFocusIn, true);
195
- doc.removeEventListener('mousedown', checkPointerDown, true);
196
- doc.removeEventListener('touchstart', checkPointerDown, true);
197
- doc.removeEventListener('click', checkClick, true);
198
- doc.removeEventListener('keydown', checkKey, true);
199
-
200
- return trap;
201
- }
118
+ const containersContain = function (element) {
119
+ return state.containers.some((container) => container.contains(element));
120
+ };
202
121
 
203
- function getNodeForOption(optionName) {
204
- var optionValue = config[optionName];
205
- var node = optionValue;
122
+ const getNodeForOption = function (optionName) {
123
+ const optionValue = config[optionName];
206
124
  if (!optionValue) {
207
125
  return null;
208
126
  }
127
+
128
+ let node = optionValue;
129
+
209
130
  if (typeof optionValue === 'string') {
210
131
  node = doc.querySelector(optionValue);
211
132
  if (!node) {
212
- throw new Error('`' + optionName + '` refers to no known node');
133
+ throw new Error(`\`${optionName}\` refers to no known node`);
213
134
  }
214
135
  }
136
+
215
137
  if (typeof optionValue === 'function') {
216
138
  node = optionValue();
217
139
  if (!node) {
218
- throw new Error('`' + optionName + '` did not return a node');
140
+ throw new Error(`\`${optionName}\` did not return a node`);
219
141
  }
220
142
  }
143
+
221
144
  return node;
222
- }
145
+ };
146
+
147
+ const getInitialFocusNode = function () {
148
+ let node;
223
149
 
224
- function getInitialFocusNode() {
225
- var node;
226
150
  if (getNodeForOption('initialFocus') !== null) {
227
151
  node = getNodeForOption('initialFocus');
228
152
  } else if (containersContain(doc.activeElement)) {
229
153
  node = doc.activeElement;
230
154
  } else {
231
- var firstTabbableGroup = state.tabbableGroups[0];
232
- var firstTabbableNode =
155
+ const firstTabbableGroup = state.tabbableGroups[0];
156
+ const firstTabbableNode =
233
157
  firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
234
158
  node = firstTabbableNode || getNodeForOption('fallbackFocus');
235
159
  }
@@ -241,24 +165,70 @@ function createFocusTrap(elements, userOptions) {
241
165
  }
242
166
 
243
167
  return node;
244
- }
168
+ };
169
+
170
+ const updateTabbableNodes = function () {
171
+ state.tabbableGroups = state.containers
172
+ .map((container) => {
173
+ const tabbableNodes = tabbable(container);
174
+
175
+ if (tabbableNodes.length > 0) {
176
+ return {
177
+ container,
178
+ firstTabbableNode: tabbableNodes[0],
179
+ lastTabbableNode: tabbableNodes[tabbableNodes.length - 1],
180
+ };
181
+ }
182
+
183
+ return undefined;
184
+ })
185
+ .filter((group) => !!group); // remove groups with no tabbable nodes
186
+
187
+ // throw if no groups have tabbable nodes and we don't have a fallback focus node either
188
+ if (
189
+ state.tabbableGroups.length <= 0 &&
190
+ !getNodeForOption('fallbackFocus')
191
+ ) {
192
+ throw new Error(
193
+ 'Your focus-trap must have at least one container with at least one tabbable node in it at all times'
194
+ );
195
+ }
196
+ };
197
+
198
+ const tryFocus = function (node) {
199
+ if (node === doc.activeElement) {
200
+ return;
201
+ }
202
+ if (!node || !node.focus) {
203
+ tryFocus(getInitialFocusNode());
204
+ return;
205
+ }
206
+
207
+ node.focus({ preventScroll: !!config.preventScroll });
208
+ state.mostRecentlyFocusedNode = node;
209
+
210
+ if (isSelectableInput(node)) {
211
+ node.select();
212
+ }
213
+ };
214
+
215
+ const getReturnFocusNode = function (previousActiveElement) {
216
+ const node = getNodeForOption('setReturnFocus');
245
217
 
246
- function getReturnFocusNode(previousActiveElement) {
247
- var node = getNodeForOption('setReturnFocus');
248
218
  return node ? node : previousActiveElement;
249
- }
219
+ };
250
220
 
251
221
  // This needs to be done on mousedown and touchstart instead of click
252
222
  // so that it precedes the focus event.
253
- function checkPointerDown(e) {
223
+ const checkPointerDown = function (e) {
254
224
  if (containersContain(e.target)) {
255
225
  // allow the click since it ocurred inside the trap
256
226
  return;
257
227
  }
258
228
 
259
- if (config.clickOutsideDeactivates) {
229
+ if (valueOrHandler(config.clickOutsideDeactivates, e)) {
260
230
  // immediately deactivate the trap
261
- deactivate({
231
+ trap.deactivate({
262
232
  // if, on deactivation, we should return focus to the node originally-focused
263
233
  // when the trap was activated (or the configured `setReturnFocus` node),
264
234
  // then assume it's also OK to return focus to the outside node that was
@@ -278,150 +248,290 @@ function createFocusTrap(elements, userOptions) {
278
248
  // This is needed for mobile devices.
279
249
  // (If we'll only let `click` events through,
280
250
  // then on mobile they will be blocked anyways if `touchstart` is blocked.)
281
- if (
282
- config.allowOutsideClick &&
283
- (typeof config.allowOutsideClick === 'boolean'
284
- ? config.allowOutsideClick
285
- : config.allowOutsideClick(e))
286
- ) {
251
+ if (valueOrHandler(config.allowOutsideClick, e)) {
287
252
  // allow the click outside the trap to take place
288
253
  return;
289
254
  }
290
255
 
291
256
  // otherwise, prevent the click
292
257
  e.preventDefault();
293
- }
258
+ };
294
259
 
295
260
  // In case focus escapes the trap for some strange reason, pull it back in.
296
- function checkFocusIn(e) {
261
+ const checkFocusIn = function (e) {
262
+ const targetContained = containersContain(e.target);
297
263
  // In Firefox when you Tab out of an iframe the Document is briefly focused.
298
- if (containersContain(e.target) || e.target instanceof Document) {
299
- return;
300
- }
301
- e.stopImmediatePropagation();
302
- tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
303
- }
304
-
305
- function checkKey(e) {
306
- if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
307
- e.preventDefault();
308
- deactivate();
309
- return;
310
- }
311
- if (isTabEvent(e)) {
312
- checkTab(e);
313
- return;
264
+ if (targetContained || e.target instanceof Document) {
265
+ if (targetContained) {
266
+ state.mostRecentlyFocusedNode = e.target;
267
+ }
268
+ } else {
269
+ // escaped! pull it back in to where it just left
270
+ e.stopImmediatePropagation();
271
+ tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
314
272
  }
315
- }
273
+ };
316
274
 
317
275
  // Hijack Tab events on the first and last focusable nodes of the trap,
318
276
  // in order to prevent focus from escaping. If it escapes for even a
319
277
  // moment it can end up scrolling the page and causing confusion so we
320
278
  // kind of need to capture the action at the keydown phase.
321
- function checkTab(e) {
279
+ const checkTab = function (e) {
322
280
  updateTabbableNodes();
323
281
 
324
282
  let destinationNode = null;
325
283
 
326
- if (e.shiftKey) {
327
- const startOfGroupIndex = state.tabbableGroups.findIndex(
328
- ({ firstTabbableNode }) => e.target === firstTabbableNode
284
+ if (state.tabbableGroups.length > 0) {
285
+ // make sure the target is actually contained in a group
286
+ const containerIndex = findIndex(state.tabbableGroups, ({ container }) =>
287
+ container.contains(e.target)
329
288
  );
330
289
 
331
- if (startOfGroupIndex >= 0) {
332
- const destinationGroupIndex =
333
- startOfGroupIndex === 0
334
- ? state.tabbableGroups.length - 1
335
- : startOfGroupIndex - 1;
336
-
337
- const destinationGroup = state.tabbableGroups[destinationGroupIndex];
338
- destinationNode = destinationGroup.lastTabbableNode;
290
+ if (containerIndex < 0) {
291
+ // target not found in any group: quite possible focus has escaped the trap,
292
+ // so bring it back in to...
293
+ if (e.shiftKey) {
294
+ // ...the last node in the last group
295
+ destinationNode =
296
+ state.tabbableGroups[state.tabbableGroups.length - 1]
297
+ .lastTabbableNode;
298
+ } else {
299
+ // ...the first node in the first group
300
+ destinationNode = state.tabbableGroups[0].firstTabbableNode;
301
+ }
302
+ } else if (e.shiftKey) {
303
+ // REVERSE
304
+ const startOfGroupIndex = findIndex(
305
+ state.tabbableGroups,
306
+ ({ firstTabbableNode }) => e.target === firstTabbableNode
307
+ );
308
+
309
+ if (startOfGroupIndex >= 0) {
310
+ const destinationGroupIndex =
311
+ startOfGroupIndex === 0
312
+ ? state.tabbableGroups.length - 1
313
+ : startOfGroupIndex - 1;
314
+
315
+ const destinationGroup = state.tabbableGroups[destinationGroupIndex];
316
+ destinationNode = destinationGroup.lastTabbableNode;
317
+ }
318
+ } else {
319
+ // FORWARD
320
+ const lastOfGroupIndex = findIndex(
321
+ state.tabbableGroups,
322
+ ({ lastTabbableNode }) => e.target === lastTabbableNode
323
+ );
324
+
325
+ if (lastOfGroupIndex >= 0) {
326
+ const destinationGroupIndex =
327
+ lastOfGroupIndex === state.tabbableGroups.length - 1
328
+ ? 0
329
+ : lastOfGroupIndex + 1;
330
+
331
+ const destinationGroup = state.tabbableGroups[destinationGroupIndex];
332
+ destinationNode = destinationGroup.firstTabbableNode;
333
+ }
339
334
  }
340
335
  } else {
341
- const lastOfGroupIndex = state.tabbableGroups.findIndex(
342
- ({ lastTabbableNode }) => e.target === lastTabbableNode
343
- );
344
-
345
- if (lastOfGroupIndex >= 0) {
346
- const destinationGroupIndex =
347
- lastOfGroupIndex === state.tabbableGroups.length - 1
348
- ? 0
349
- : lastOfGroupIndex + 1;
350
-
351
- const destinationGroup = state.tabbableGroups[destinationGroupIndex];
352
- destinationNode = destinationGroup.firstTabbableNode;
353
- }
336
+ destinationNode = getNodeForOption('fallbackFocus');
354
337
  }
355
338
 
356
339
  if (destinationNode) {
357
340
  e.preventDefault();
358
-
359
341
  tryFocus(destinationNode);
360
342
  }
361
- }
343
+ };
362
344
 
363
- function checkClick(e) {
364
- if (config.clickOutsideDeactivates) return;
365
- if (containersContain(e.target)) return;
366
- if (
367
- config.allowOutsideClick &&
368
- (typeof config.allowOutsideClick === 'boolean'
369
- ? config.allowOutsideClick
370
- : config.allowOutsideClick(e))
371
- ) {
345
+ const checkKey = function (e) {
346
+ if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
347
+ e.preventDefault();
348
+ trap.deactivate();
349
+ return;
350
+ }
351
+
352
+ if (isTabEvent(e)) {
353
+ checkTab(e);
354
+ return;
355
+ }
356
+ };
357
+
358
+ const checkClick = function (e) {
359
+ if (valueOrHandler(config.clickOutsideDeactivates, e)) {
360
+ return;
361
+ }
362
+
363
+ if (containersContain(e.target)) {
372
364
  return;
373
365
  }
366
+
367
+ if (valueOrHandler(config.allowOutsideClick, e)) {
368
+ return;
369
+ }
370
+
374
371
  e.preventDefault();
375
372
  e.stopImmediatePropagation();
376
- }
373
+ };
374
+
375
+ //
376
+ // EVENT LISTENERS
377
+ //
377
378
 
378
- function updateTabbableNodes() {
379
- state.tabbableGroups = state.containers.map((container) => {
380
- var tabbableNodes = tabbable(container);
379
+ const addListeners = function () {
380
+ if (!state.active) {
381
+ return;
382
+ }
383
+
384
+ // There can be only one listening focus trap at a time
385
+ activeFocusTraps.activateTrap(trap);
381
386
 
382
- return {
383
- firstTabbableNode: tabbableNodes[0],
384
- lastTabbableNode: tabbableNodes[tabbableNodes.length - 1],
385
- };
387
+ // Delay ensures that the focused element doesn't capture the event
388
+ // that caused the focus trap activation.
389
+ activeFocusDelay = config.delayInitialFocus
390
+ ? delay(function () {
391
+ tryFocus(getInitialFocusNode());
392
+ })
393
+ : tryFocus(getInitialFocusNode());
394
+
395
+ doc.addEventListener('focusin', checkFocusIn, true);
396
+ doc.addEventListener('mousedown', checkPointerDown, {
397
+ capture: true,
398
+ passive: false,
399
+ });
400
+ doc.addEventListener('touchstart', checkPointerDown, {
401
+ capture: true,
402
+ passive: false,
403
+ });
404
+ doc.addEventListener('click', checkClick, {
405
+ capture: true,
406
+ passive: false,
407
+ });
408
+ doc.addEventListener('keydown', checkKey, {
409
+ capture: true,
410
+ passive: false,
386
411
  });
387
- }
388
412
 
389
- function tryFocus(node) {
390
- if (node === doc.activeElement) return;
391
- if (!node || !node.focus) {
392
- tryFocus(getInitialFocusNode());
413
+ return trap;
414
+ };
415
+
416
+ const removeListeners = function () {
417
+ if (!state.active) {
393
418
  return;
394
419
  }
395
- node.focus({ preventScroll: !!config.preventScroll });
396
- state.mostRecentlyFocusedNode = node;
397
- if (isSelectableInput(node)) {
398
- node.select();
399
- }
400
- }
401
420
 
402
- function containersContain(element) {
403
- return state.containers.some((container) => container.contains(element));
404
- }
405
- }
421
+ doc.removeEventListener('focusin', checkFocusIn, true);
422
+ doc.removeEventListener('mousedown', checkPointerDown, true);
423
+ doc.removeEventListener('touchstart', checkPointerDown, true);
424
+ doc.removeEventListener('click', checkClick, true);
425
+ doc.removeEventListener('keydown', checkKey, true);
406
426
 
407
- function isSelectableInput(node) {
408
- return (
409
- node.tagName &&
410
- node.tagName.toLowerCase() === 'input' &&
411
- typeof node.select === 'function'
412
- );
413
- }
427
+ return trap;
428
+ };
414
429
 
415
- function isEscapeEvent(e) {
416
- return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
417
- }
430
+ //
431
+ // TRAP DEFINITION
432
+ //
418
433
 
419
- function isTabEvent(e) {
420
- return e.key === 'Tab' || e.keyCode === 9;
421
- }
434
+ trap = {
435
+ activate(activateOptions) {
436
+ if (state.active) {
437
+ return this;
438
+ }
422
439
 
423
- function delay(fn) {
424
- return setTimeout(fn, 0);
425
- }
440
+ updateTabbableNodes();
441
+
442
+ state.active = true;
443
+ state.paused = false;
444
+ state.nodeFocusedBeforeActivation = doc.activeElement;
445
+
446
+ const onActivate =
447
+ activateOptions && activateOptions.onActivate
448
+ ? activateOptions.onActivate
449
+ : config.onActivate;
450
+ if (onActivate) {
451
+ onActivate();
452
+ }
453
+
454
+ addListeners();
455
+ return this;
456
+ },
457
+
458
+ deactivate(deactivateOptions) {
459
+ if (!state.active) {
460
+ return this;
461
+ }
462
+
463
+ clearTimeout(activeFocusDelay);
464
+
465
+ removeListeners();
466
+ state.active = false;
467
+ state.paused = false;
468
+
469
+ activeFocusTraps.deactivateTrap(trap);
470
+
471
+ const onDeactivate =
472
+ deactivateOptions && deactivateOptions.onDeactivate !== undefined
473
+ ? deactivateOptions.onDeactivate
474
+ : config.onDeactivate;
475
+ if (onDeactivate) {
476
+ onDeactivate();
477
+ }
478
+
479
+ const returnFocus =
480
+ deactivateOptions && deactivateOptions.returnFocus !== undefined
481
+ ? deactivateOptions.returnFocus
482
+ : config.returnFocusOnDeactivate;
483
+
484
+ if (returnFocus) {
485
+ delay(function () {
486
+ tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
487
+ });
488
+ }
489
+
490
+ return this;
491
+ },
492
+
493
+ pause() {
494
+ if (state.paused || !state.active) {
495
+ return this;
496
+ }
497
+
498
+ state.paused = true;
499
+ removeListeners();
500
+
501
+ return this;
502
+ },
503
+
504
+ unpause() {
505
+ if (!state.paused || !state.active) {
506
+ return this;
507
+ }
508
+
509
+ state.paused = false;
510
+ updateTabbableNodes();
511
+ addListeners();
512
+
513
+ return this;
514
+ },
515
+
516
+ updateContainerElements(containerElements) {
517
+ const elementsAsArray = [].concat(containerElements).filter(Boolean);
518
+
519
+ state.containers = elementsAsArray.map((element) =>
520
+ typeof element === 'string' ? doc.querySelector(element) : element
521
+ );
522
+
523
+ if (state.active) {
524
+ updateTabbableNodes();
525
+ }
526
+
527
+ return this;
528
+ },
529
+ };
530
+
531
+ // initialize container elements
532
+ trap.updateContainerElements(elements);
533
+
534
+ return trap;
535
+ };
426
536
 
427
537
  export { createFocusTrap };