focus-trap 6.1.1 → 6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2267d17: Adding support for multiple elements to be passed in #217
8
+
9
+ ## 6.1.4
10
+
11
+ ### Patch Changes
12
+
13
+ - 38b6b98: Update tabbable to [5.1.3](https://github.com/focus-trap/tabbable/blob/master/CHANGELOG.md#513) to get bug fixes related to detail and summary elements.
14
+
15
+ ## 6.1.3
16
+
17
+ ### Patch Changes
18
+
19
+ - 6a39217: Close the gap with #172 and bump `tabbable` to 5.1.2 which has a similar fix.
20
+ - 756c79d: Fix #172 (again): Transpile ESM bundle down to the same browser target used for the CJS and UMD bundles. ESM is just the module system, not the browser target.
21
+
22
+ ## 6.1.2
23
+
24
+ ### Patch Changes
25
+
26
+ - 00674dd: Fix #172: Transpile non-minified bundles so they are compatible with IE11.
27
+ - 679009b: Update tabbable dependency to 5.1.1 to get transpiled non-minified bundles.
28
+
3
29
  ## 6.1.1
4
30
 
5
31
  ### Patch 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-7-orange.svg?style=flat-square)](#contributors)
4
+ [![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors)
5
5
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
6
6
 
7
7
  Trap focus within a DOM node.
@@ -28,7 +28,7 @@ When the focus trap is deactivated, this is what should happen:
28
28
  - Focus is passed to *whichever element had focus when the trap was activated* (e.g. the button that opened the modal or menu).
29
29
  - Tabbing and clicking behave normally everywhere.
30
30
 
31
- [Check out the demos.](http://davidtheclark.github.io/focus-trap/demo/)
31
+ [Check out the demos.](http://focus-trap.github.io/focus-trap/demo/)
32
32
 
33
33
  For more advanced usage (e.g. focus traps within focus traps), you can also pause a focus trap's behavior without deactivating it entirely, then unpause at will.
34
34
 
@@ -63,7 +63,8 @@ Returns a new focus trap on `element`.
63
63
 
64
64
  `element` can be
65
65
  - a DOM node (the focus trap itself) or
66
- - a selector string (which will be pass to `document.querySelector()` to find the DOM node).
66
+ - a selector string (which will be passed to `document.querySelector()` to find the DOM node) or
67
+ - an array of DOM nodes or selector strings (where the order determines where the focus will go after the last tabbable element of a DOM node/selector is reached).
67
68
 
68
69
  `createOptions`:
69
70
 
@@ -134,6 +135,16 @@ If the focus trap has not been activated or has not been paused, nothing happens
134
135
 
135
136
  Returns the `focusTrap`.
136
137
 
138
+ ### focusTrap.updateContainerElements()
139
+
140
+ Update the element(s) that are used as containers for the focus trap.
141
+
142
+ When you call the function `createFocusTrap`, you pass in an element (or selector), or an array of elements (or selectors) to keep the focus within. This method simply allows you to update which elements to keep the focus within.
143
+
144
+ A use case for this is found in focus-trap-react, where React `ref`'s may not be initialized yet, but when they are you want to have them be a container element.
145
+
146
+ Returns the `focusTrap`.
147
+
137
148
  ## Examples
138
149
 
139
150
  Read code in `demo/` and [see how it works](http://davidtheclark.github.io/focus-trap/demo/).
@@ -206,17 +217,22 @@ In alphabetical order:
206
217
  <!-- markdownlint-disable -->
207
218
  <table>
208
219
  <tr>
209
- <td align="center"><a href="http://davidtheclark.com/"><img src="https://avatars2.githubusercontent.com/u/628431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Clark</b></sub></a><br /><a href="https://github.com/stefcameron/focus-trap/commits?author=davidtheclark" title="Code">💻</a> <a href="https://github.com/stefcameron/focus-trap/issues?q=author%3Adavidtheclark" title="Bug reports">🐛</a> <a href="#infra-davidtheclark" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=davidtheclark" title="Tests">⚠️</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=davidtheclark" title="Documentation">📖</a> <a href="#maintenance-davidtheclark" title="Maintenance">🚧</a></td>
210
- <td align="center"><a href="https://github.com/features/security"><img src="https://avatars1.githubusercontent.com/u/27347476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot" title="Maintenance">🚧</a></td>
211
- <td align="center"><a href="https://github.com/michael-ar"><img src="https://avatars3.githubusercontent.com/u/18557997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Reynolds</b></sub></a><br /><a href="https://github.com/stefcameron/focus-trap/issues?q=author%3Amichael-ar" title="Bug reports">🐛</a></td>
212
- <td align="center"><a href="https://github.com/liunate"><img src="https://avatars2.githubusercontent.com/u/38996291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nate Liu</b></sub></a><br /><a href="https://github.com/stefcameron/focus-trap/commits?author=liunate" title="Tests">⚠️</a></td>
213
- <td align="center"><a href="https://github.com/sadick254"><img src="https://avatars2.githubusercontent.com/u/5238135?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sadick</b></sub></a><br /><a href="https://github.com/stefcameron/focus-trap/commits?author=sadick254" title="Code">💻</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=sadick254" title="Tests">⚠️</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=sadick254" title="Documentation">📖</a></td>
214
- <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/stefcameron/focus-trap/commits?author=SeanMcP" title="Code">💻</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=SeanMcP" title="Documentation">📖</a></td>
215
- <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/stefcameron/focus-trap/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/stefcameron/focus-trap/issues?q=author%3Astefcameron" title="Bug reports">🐛</a> <a href="#infra-stefcameron" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/stefcameron/focus-trap/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td>
220
+ <td align="center"><a href="http://davidtheclark.com/"><img src="https://avatars2.githubusercontent.com/u/628431?v=4" width="100px;" alt=""/><br /><sub><b>David Clark</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Adavidtheclark" title="Bug reports">🐛</a> <a href="#infra-davidtheclark" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Documentation">📖</a> <a href="#maintenance-davidtheclark" title="Maintenance">🚧</a></td>
221
+ <td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/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/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td>
222
+ <td align="center"><a href="https://github.com/liunate"><img src="https://avatars2.githubusercontent.com/u/38996291?v=4" width="100px;" alt=""/><br /><sub><b>Nate Liu</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=liunate" title="Tests">⚠️</a></td>
223
+ <td align="center"><a href="https://github.com/sadick254"><img src="https://avatars2.githubusercontent.com/u/5238135?v=4" width="100px;" alt=""/><br /><sub><b>Sadick</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=sadick254" title="Documentation">📖</a></td>
224
+ <td align="center"><a href="https://github.com/michael-ar"><img src="https://avatars3.githubusercontent.com/u/18557997?v=4" width="100px;" alt=""/><br /><sub><b>Michael Reynolds</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Amichael-ar" title="Bug reports">🐛</a></td>
225
+ <td align="center"><a href="https://seanmcp.com/"><img src="https://avatars1.githubusercontent.com/u/6360367?v=4" width="100px;" alt=""/><br /><sub><b>Sean McPherson</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Documentation">📖</a></td>
226
+ <td align="center"><a href="https://github.com/features/security"><img src="https://avatars1.githubusercontent.com/u/27347476?v=4" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot" title="Maintenance">🚧</a></td>
227
+ </tr>
228
+ <tr>
229
+ <td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td>
230
+ <td align="center"><a href="https://github.com/bparish628"><img src="https://avatars1.githubusercontent.com/u/8492971?v=4" width="100px;" alt=""/><br /><sub><b>Benjamin Parish</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Abparish628" title="Bug reports">🐛</a></td>
231
+ <td align="center"><a href="https://scottblinch.me/"><img src="https://avatars2.githubusercontent.com/u/4682114?v=4" width="100px;" alt=""/><br /><sub><b>Scott Blinch</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=scottblinch" title="Documentation">📖</a></td>
232
+ <td align="center"><a href="https://clintgoodman.com"><img src="https://avatars3.githubusercontent.com/u/5473697?v=4" width="100px;" alt=""/><br /><sub><b>Clint Goodman</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Documentation">📖</a> <a href="#example-cgood92" title="Examples">💡</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=cgood92" title="Tests">⚠️</a></td>
216
233
  </tr>
217
234
  </table>
218
235
 
219
- <!-- markdownlint-restore -->
236
+ <!-- markdownlint-enable -->
220
237
  <!-- prettier-ignore-end -->
221
-
222
238
  <!-- ALL-CONTRIBUTORS-LIST:END -->
@@ -1,23 +1,74 @@
1
1
  /*!
2
- * focus-trap 6.1.1
2
+ * focus-trap 6.2.0
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
5
  import { isFocusable, tabbable } from 'tabbable';
6
6
 
7
+ function _defineProperty(obj, key, value) {
8
+ if (key in obj) {
9
+ Object.defineProperty(obj, key, {
10
+ value: value,
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true
14
+ });
15
+ } else {
16
+ obj[key] = value;
17
+ }
18
+
19
+ return obj;
20
+ }
21
+
22
+ function ownKeys(object, enumerableOnly) {
23
+ var keys = Object.keys(object);
24
+
25
+ if (Object.getOwnPropertySymbols) {
26
+ var symbols = Object.getOwnPropertySymbols(object);
27
+ if (enumerableOnly) symbols = symbols.filter(function (sym) {
28
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
29
+ });
30
+ keys.push.apply(keys, symbols);
31
+ }
32
+
33
+ return keys;
34
+ }
35
+
36
+ function _objectSpread2(target) {
37
+ for (var i = 1; i < arguments.length; i++) {
38
+ var source = arguments[i] != null ? arguments[i] : {};
39
+
40
+ if (i % 2) {
41
+ ownKeys(Object(source), true).forEach(function (key) {
42
+ _defineProperty(target, key, source[key]);
43
+ });
44
+ } else if (Object.getOwnPropertyDescriptors) {
45
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
46
+ } else {
47
+ ownKeys(Object(source)).forEach(function (key) {
48
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
49
+ });
50
+ }
51
+ }
52
+
53
+ return target;
54
+ }
55
+
7
56
  var activeFocusDelay;
8
57
 
9
- var activeFocusTraps = (function () {
58
+ var activeFocusTraps = function () {
10
59
  var trapQueue = [];
11
60
  return {
12
- activateTrap: function (trap) {
61
+ activateTrap: function activateTrap(trap) {
13
62
  if (trapQueue.length > 0) {
14
63
  var activeTrap = trapQueue[trapQueue.length - 1];
64
+
15
65
  if (activeTrap !== trap) {
16
66
  activeTrap.pause();
17
67
  }
18
68
  }
19
69
 
20
70
  var trapIndex = trapQueue.indexOf(trap);
71
+
21
72
  if (trapIndex === -1) {
22
73
  trapQueue.push(trap);
23
74
  } else {
@@ -26,9 +77,9 @@ var activeFocusTraps = (function () {
26
77
  trapQueue.push(trap);
27
78
  }
28
79
  },
29
-
30
- deactivateTrap: function (trap) {
80
+ deactivateTrap: function deactivateTrap(trap) {
31
81
  var trapIndex = trapQueue.indexOf(trap);
82
+
32
83
  if (trapIndex !== -1) {
33
84
  trapQueue.splice(trapIndex, 1);
34
85
  }
@@ -36,53 +87,60 @@ var activeFocusTraps = (function () {
36
87
  if (trapQueue.length > 0) {
37
88
  trapQueue[trapQueue.length - 1].unpause();
38
89
  }
39
- },
90
+ }
40
91
  };
41
- })();
92
+ }();
42
93
 
43
- function createFocusTrap(element, userOptions) {
94
+ function createFocusTrap(elements, userOptions) {
44
95
  var doc = document;
45
- var container =
46
- typeof element === 'string' ? doc.querySelector(element) : element;
47
96
 
48
- var config = {
97
+ var config = _objectSpread2({
49
98
  returnFocusOnDeactivate: true,
50
99
  escapeDeactivates: true,
51
- delayInitialFocus: true,
52
- ...userOptions,
53
- };
100
+ delayInitialFocus: true
101
+ }, userOptions);
54
102
 
55
103
  var state = {
56
- firstTabbableNode: null,
57
- lastTabbableNode: null,
104
+ // @type {Array<HTMLElement>}
105
+ containers: [],
106
+ // @type {{ firstTabbableNode: HTMLElement, lastTabbableNode: HTMLElement }}
107
+ tabbableGroups: [],
58
108
  nodeFocusedBeforeActivation: null,
59
109
  mostRecentlyFocusedNode: null,
60
110
  active: false,
61
- paused: false,
111
+ paused: false
62
112
  };
63
-
64
113
  var trap = {
65
114
  activate: activate,
66
115
  deactivate: deactivate,
67
116
  pause: pause,
68
117
  unpause: unpause,
118
+ updateContainerElements: updateContainerElements
69
119
  };
70
-
120
+ updateContainerElements(elements);
71
121
  return trap;
72
122
 
123
+ function updateContainerElements(containerElements) {
124
+ var elementsAsArray = [].concat(containerElements).filter(Boolean);
125
+ state.containers = elementsAsArray.map(function (element) {
126
+ return typeof element === 'string' ? doc.querySelector(element) : element;
127
+ });
128
+
129
+ if (state.active) {
130
+ updateTabbableNodes();
131
+ }
132
+
133
+ return trap;
134
+ }
135
+
73
136
  function activate(activateOptions) {
74
137
  if (state.active) return;
75
-
76
138
  updateTabbableNodes();
77
-
78
139
  state.active = true;
79
140
  state.paused = false;
80
141
  state.nodeFocusedBeforeActivation = doc.activeElement;
142
+ var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
81
143
 
82
- var onActivate =
83
- activateOptions && activateOptions.onActivate
84
- ? activateOptions.onActivate
85
- : config.onActivate;
86
144
  if (onActivate) {
87
145
  onActivate();
88
146
  }
@@ -93,27 +151,19 @@ function createFocusTrap(element, userOptions) {
93
151
 
94
152
  function deactivate(deactivateOptions) {
95
153
  if (!state.active) return;
96
-
97
154
  clearTimeout(activeFocusDelay);
98
-
99
155
  removeListeners();
100
156
  state.active = false;
101
157
  state.paused = false;
102
-
103
158
  activeFocusTraps.deactivateTrap(trap);
159
+ var onDeactivate = deactivateOptions && deactivateOptions.onDeactivate !== undefined ? deactivateOptions.onDeactivate : config.onDeactivate;
104
160
 
105
- var onDeactivate =
106
- deactivateOptions && deactivateOptions.onDeactivate !== undefined
107
- ? deactivateOptions.onDeactivate
108
- : config.onDeactivate;
109
161
  if (onDeactivate) {
110
162
  onDeactivate();
111
163
  }
112
164
 
113
- var returnFocus =
114
- deactivateOptions && deactivateOptions.returnFocus !== undefined
115
- ? deactivateOptions.returnFocus
116
- : config.returnFocusOnDeactivate;
165
+ var returnFocus = deactivateOptions && deactivateOptions.returnFocus !== undefined ? deactivateOptions.returnFocus : config.returnFocusOnDeactivate;
166
+
117
167
  if (returnFocus) {
118
168
  delay(function () {
119
169
  tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
@@ -124,100 +174,101 @@ function createFocusTrap(element, userOptions) {
124
174
  }
125
175
 
126
176
  function pause() {
127
- if (state.paused || !state.active) return;
177
+ if (state.paused || !state.active) return trap;
128
178
  state.paused = true;
129
179
  removeListeners();
180
+ return trap;
130
181
  }
131
182
 
132
183
  function unpause() {
133
- if (!state.paused || !state.active) return;
184
+ if (!state.paused || !state.active) return trap;
134
185
  state.paused = false;
135
186
  updateTabbableNodes();
136
187
  addListeners();
188
+ return trap;
137
189
  }
138
190
 
139
191
  function addListeners() {
140
- if (!state.active) return;
141
-
142
- // There can be only one listening focus trap at a time
143
- activeFocusTraps.activateTrap(trap);
192
+ if (!state.active) return; // There can be only one listening focus trap at a time
144
193
 
145
- // Delay ensures that the focused element doesn't capture the event
194
+ activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
146
195
  // that caused the focus trap activation.
147
- activeFocusDelay = config.delayInitialFocus
148
- ? delay(function () {
149
- tryFocus(getInitialFocusNode());
150
- })
151
- : tryFocus(getInitialFocusNode());
152
196
 
197
+ activeFocusDelay = config.delayInitialFocus ? delay(function () {
198
+ tryFocus(getInitialFocusNode());
199
+ }) : tryFocus(getInitialFocusNode());
153
200
  doc.addEventListener('focusin', checkFocusIn, true);
154
201
  doc.addEventListener('mousedown', checkPointerDown, {
155
202
  capture: true,
156
- passive: false,
203
+ passive: false
157
204
  });
158
205
  doc.addEventListener('touchstart', checkPointerDown, {
159
206
  capture: true,
160
- passive: false,
207
+ passive: false
161
208
  });
162
209
  doc.addEventListener('click', checkClick, {
163
210
  capture: true,
164
- passive: false,
211
+ passive: false
165
212
  });
166
213
  doc.addEventListener('keydown', checkKey, {
167
214
  capture: true,
168
- passive: false,
215
+ passive: false
169
216
  });
170
-
171
217
  return trap;
172
218
  }
173
219
 
174
220
  function removeListeners() {
175
221
  if (!state.active) return;
176
-
177
222
  doc.removeEventListener('focusin', checkFocusIn, true);
178
223
  doc.removeEventListener('mousedown', checkPointerDown, true);
179
224
  doc.removeEventListener('touchstart', checkPointerDown, true);
180
225
  doc.removeEventListener('click', checkClick, true);
181
226
  doc.removeEventListener('keydown', checkKey, true);
182
-
183
227
  return trap;
184
228
  }
185
229
 
186
230
  function getNodeForOption(optionName) {
187
231
  var optionValue = config[optionName];
188
232
  var node = optionValue;
233
+
189
234
  if (!optionValue) {
190
235
  return null;
191
236
  }
237
+
192
238
  if (typeof optionValue === 'string') {
193
239
  node = doc.querySelector(optionValue);
240
+
194
241
  if (!node) {
195
242
  throw new Error('`' + optionName + '` refers to no known node');
196
243
  }
197
244
  }
245
+
198
246
  if (typeof optionValue === 'function') {
199
247
  node = optionValue();
248
+
200
249
  if (!node) {
201
250
  throw new Error('`' + optionName + '` did not return a node');
202
251
  }
203
252
  }
253
+
204
254
  return node;
205
255
  }
206
256
 
207
257
  function getInitialFocusNode() {
208
258
  var node;
259
+
209
260
  if (getNodeForOption('initialFocus') !== null) {
210
261
  node = getNodeForOption('initialFocus');
211
- } else if (container.contains(doc.activeElement)) {
262
+ } else if (containersContain(doc.activeElement)) {
212
263
  node = doc.activeElement;
213
264
  } else {
214
- node = state.firstTabbableNode || getNodeForOption('fallbackFocus');
265
+ var firstTabbableGroup = state.tabbableGroups[0];
266
+ var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
267
+ node = firstTabbableNode || getNodeForOption('fallbackFocus');
215
268
  }
216
269
 
217
270
  if (!node) {
218
- throw new Error(
219
- 'Your focus-trap needs to have at least one focusable element'
220
- );
271
+ throw new Error('Your focus-trap needs to have at least one focusable element');
221
272
  }
222
273
 
223
274
  return node;
@@ -226,12 +277,12 @@ function createFocusTrap(element, userOptions) {
226
277
  function getReturnFocusNode(previousActiveElement) {
227
278
  var node = getNodeForOption('setReturnFocus');
228
279
  return node ? node : previousActiveElement;
229
- }
230
-
231
- // This needs to be done on mousedown and touchstart instead of click
280
+ } // This needs to be done on mousedown and touchstart instead of click
232
281
  // so that it precedes the focus event.
282
+
283
+
233
284
  function checkPointerDown(e) {
234
- if (container.contains(e.target)) {
285
+ if (containersContain(e.target)) {
235
286
  // allow the click since it ocurred inside the trap
236
287
  return;
237
288
  }
@@ -250,34 +301,30 @@ function createFocusTrap(element, userOptions) {
250
301
  // that was clicked, whether it's focusable or not; by setting
251
302
  // `returnFocus: true`, we'll attempt to re-focus the node originally-focused
252
303
  // on activation (or the configured `setReturnFocus` node)
253
- returnFocus: config.returnFocusOnDeactivate && !isFocusable(e.target),
304
+ returnFocus: config.returnFocusOnDeactivate && !isFocusable(e.target)
254
305
  });
255
306
  return;
256
- }
257
-
258
- // This is needed for mobile devices.
307
+ } // This is needed for mobile devices.
259
308
  // (If we'll only let `click` events through,
260
309
  // then on mobile they will be blocked anyways if `touchstart` is blocked.)
261
- if (
262
- config.allowOutsideClick &&
263
- (typeof config.allowOutsideClick === 'boolean'
264
- ? config.allowOutsideClick
265
- : config.allowOutsideClick(e))
266
- ) {
310
+
311
+
312
+ if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
267
313
  // allow the click outside the trap to take place
268
314
  return;
269
- }
315
+ } // otherwise, prevent the click
316
+
270
317
 
271
- // otherwise, prevent the click
272
318
  e.preventDefault();
273
- }
319
+ } // In case focus escapes the trap for some strange reason, pull it back in.
320
+
274
321
 
275
- // In case focus escapes the trap for some strange reason, pull it back in.
276
322
  function checkFocusIn(e) {
277
323
  // In Firefox when you Tab out of an iframe the Document is briefly focused.
278
- if (container.contains(e.target) || e.target instanceof Document) {
324
+ if (containersContain(e.target) || e.target instanceof Document) {
279
325
  return;
280
326
  }
327
+
281
328
  e.stopImmediatePropagation();
282
329
  tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
283
330
  }
@@ -288,72 +335,101 @@ function createFocusTrap(element, userOptions) {
288
335
  deactivate();
289
336
  return;
290
337
  }
338
+
291
339
  if (isTabEvent(e)) {
292
340
  checkTab(e);
293
341
  return;
294
342
  }
295
- }
296
-
297
- // Hijack Tab events on the first and last focusable nodes of the trap,
343
+ } // Hijack Tab events on the first and last focusable nodes of the trap,
298
344
  // in order to prevent focus from escaping. If it escapes for even a
299
345
  // moment it can end up scrolling the page and causing confusion so we
300
346
  // kind of need to capture the action at the keydown phase.
347
+
348
+
301
349
  function checkTab(e) {
302
350
  updateTabbableNodes();
303
- if (e.shiftKey && e.target === state.firstTabbableNode) {
304
- e.preventDefault();
305
- tryFocus(state.lastTabbableNode);
306
- return;
351
+ var destinationNode = null;
352
+
353
+ if (e.shiftKey) {
354
+ var startOfGroupIndex = state.tabbableGroups.findIndex(function (_ref) {
355
+ var firstTabbableNode = _ref.firstTabbableNode;
356
+ return e.target === firstTabbableNode;
357
+ });
358
+
359
+ if (startOfGroupIndex >= 0) {
360
+ var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
361
+ var destinationGroup = state.tabbableGroups[destinationGroupIndex];
362
+ destinationNode = destinationGroup.lastTabbableNode;
363
+ }
364
+ } else {
365
+ var lastOfGroupIndex = state.tabbableGroups.findIndex(function (_ref2) {
366
+ var lastTabbableNode = _ref2.lastTabbableNode;
367
+ return e.target === lastTabbableNode;
368
+ });
369
+
370
+ if (lastOfGroupIndex >= 0) {
371
+ var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
372
+
373
+ var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
374
+ destinationNode = _destinationGroup.firstTabbableNode;
375
+ }
307
376
  }
308
- if (!e.shiftKey && e.target === state.lastTabbableNode) {
377
+
378
+ if (destinationNode) {
309
379
  e.preventDefault();
310
- tryFocus(state.firstTabbableNode);
311
- return;
380
+ tryFocus(destinationNode);
312
381
  }
313
382
  }
314
383
 
315
384
  function checkClick(e) {
316
385
  if (config.clickOutsideDeactivates) return;
317
- if (container.contains(e.target)) return;
318
- if (
319
- config.allowOutsideClick &&
320
- (typeof config.allowOutsideClick === 'boolean'
321
- ? config.allowOutsideClick
322
- : config.allowOutsideClick(e))
323
- ) {
386
+ if (containersContain(e.target)) return;
387
+
388
+ if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
324
389
  return;
325
390
  }
391
+
326
392
  e.preventDefault();
327
393
  e.stopImmediatePropagation();
328
394
  }
329
395
 
330
396
  function updateTabbableNodes() {
331
- var tabbableNodes = tabbable(container);
332
- state.firstTabbableNode = tabbableNodes[0] || getInitialFocusNode();
333
- state.lastTabbableNode =
334
- tabbableNodes[tabbableNodes.length - 1] || getInitialFocusNode();
397
+ state.tabbableGroups = state.containers.map(function (container) {
398
+ var tabbableNodes = tabbable(container);
399
+ return {
400
+ firstTabbableNode: tabbableNodes[0],
401
+ lastTabbableNode: tabbableNodes[tabbableNodes.length - 1]
402
+ };
403
+ });
335
404
  }
336
405
 
337
406
  function tryFocus(node) {
338
407
  if (node === doc.activeElement) return;
408
+
339
409
  if (!node || !node.focus) {
340
410
  tryFocus(getInitialFocusNode());
341
411
  return;
342
412
  }
343
- node.focus({ preventScroll: !!config.preventScroll });
413
+
414
+ node.focus({
415
+ preventScroll: !!config.preventScroll
416
+ });
344
417
  state.mostRecentlyFocusedNode = node;
418
+
345
419
  if (isSelectableInput(node)) {
346
420
  node.select();
347
421
  }
348
422
  }
423
+
424
+ function containersContain(element) {
425
+ return state.containers.some(function (container) {
426
+ return container.contains(element);
427
+ });
428
+ }
349
429
  }
350
430
 
351
431
  function isSelectableInput(node) {
352
- return (
353
- node.tagName &&
354
- node.tagName.toLowerCase() === 'input' &&
355
- typeof node.select === 'function'
356
- );
432
+ return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function';
357
433
  }
358
434
 
359
435
  function isEscapeEvent(e) {