focus-trap 7.6.6 → 7.7.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.6.6
2
+ * focus-trap 7.7.0
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
5
  'use strict';
@@ -14,6 +14,54 @@ function _arrayLikeToArray(r, a) {
14
14
  function _arrayWithoutHoles(r) {
15
15
  if (Array.isArray(r)) return _arrayLikeToArray(r);
16
16
  }
17
+ function _createForOfIteratorHelper(r, e) {
18
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
19
+ if (!t) {
20
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
21
+ t && (r = t);
22
+ var n = 0,
23
+ F = function () {};
24
+ return {
25
+ s: F,
26
+ n: function () {
27
+ return n >= r.length ? {
28
+ done: true
29
+ } : {
30
+ done: false,
31
+ value: r[n++]
32
+ };
33
+ },
34
+ e: function (r) {
35
+ throw r;
36
+ },
37
+ f: F
38
+ };
39
+ }
40
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
41
+ }
42
+ var o,
43
+ a = true,
44
+ u = false;
45
+ return {
46
+ s: function () {
47
+ t = t.call(r);
48
+ },
49
+ n: function () {
50
+ var r = t.next();
51
+ return a = r.done, r;
52
+ },
53
+ e: function (r) {
54
+ u = true, o = r;
55
+ },
56
+ f: function () {
57
+ try {
58
+ a || null == t.return || t.return();
59
+ } finally {
60
+ if (u) throw o;
61
+ }
62
+ }
63
+ };
64
+ }
17
65
  function _defineProperty(e, r, t) {
18
66
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
19
67
  value: t,
@@ -75,12 +123,18 @@ function _unsupportedIterableToArray(r, a) {
75
123
  }
76
124
 
77
125
  var activeFocusTraps = {
126
+ // Returns the trap from the top of the stack.
127
+ getActiveTrap: function getActiveTrap(trapStack) {
128
+ if ((trapStack === null || trapStack === void 0 ? void 0 : trapStack.length) > 0) {
129
+ return trapStack[trapStack.length - 1];
130
+ }
131
+ return null;
132
+ },
133
+ // Pauses the currently active trap, then adds a new trap to the stack.
78
134
  activateTrap: function activateTrap(trapStack, trap) {
79
- if (trapStack.length > 0) {
80
- var activeTrap = trapStack[trapStack.length - 1];
81
- if (activeTrap !== trap) {
82
- activeTrap._setPausedState(true);
83
- }
135
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
136
+ if (trap !== activeTrap) {
137
+ activeFocusTraps.pauseTrap(trapStack);
84
138
  }
85
139
  var trapIndex = trapStack.indexOf(trap);
86
140
  if (trapIndex === -1) {
@@ -91,13 +145,24 @@ var activeFocusTraps = {
91
145
  trapStack.push(trap);
92
146
  }
93
147
  },
148
+ // Removes the trap from the top of the stack, then unpauses the next trap down.
94
149
  deactivateTrap: function deactivateTrap(trapStack, trap) {
95
150
  var trapIndex = trapStack.indexOf(trap);
96
151
  if (trapIndex !== -1) {
97
152
  trapStack.splice(trapIndex, 1);
98
153
  }
99
- if (trapStack.length > 0 && !trapStack[trapStack.length - 1]._isManuallyPaused()) {
100
- trapStack[trapStack.length - 1]._setPausedState(false);
154
+ activeFocusTraps.unpauseTrap(trapStack);
155
+ },
156
+ // Pauses the trap at the top of the stack.
157
+ pauseTrap: function pauseTrap(trapStack) {
158
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
159
+ activeTrap === null || activeTrap === void 0 || activeTrap._setPausedState(true);
160
+ },
161
+ // Unpauses the trap at the top of the stack.
162
+ unpauseTrap: function unpauseTrap(trapStack) {
163
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
164
+ if (activeTrap && !activeTrap._isManuallyPaused()) {
165
+ activeTrap._setPausedState(false);
101
166
  }
102
167
  }
103
168
  };
@@ -160,29 +225,31 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
160
225
  returnFocusOnDeactivate: true,
161
226
  escapeDeactivates: true,
162
227
  delayInitialFocus: true,
228
+ isolateSubtrees: false,
163
229
  isKeyForward: isKeyForward,
164
230
  isKeyBackward: isKeyBackward
165
231
  }, userOptions);
166
232
  var state = {
167
233
  // containers given to createFocusTrap()
168
- // @type {Array<HTMLElement>}
234
+ /** @type {Array<HTMLElement>} */
169
235
  containers: [],
170
236
  // list of objects identifying tabbable nodes in `containers` in the trap
171
237
  // NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
172
238
  // is active, but the trap should never get to a state where there isn't at least one group
173
239
  // with at least one tabbable node in it (that would lead to an error condition that would
174
240
  // result in an error being thrown)
175
- // @type {Array<{
176
- // container: HTMLElement,
177
- // tabbableNodes: Array<HTMLElement>, // empty if none
178
- // focusableNodes: Array<HTMLElement>, // empty if none
179
- // posTabIndexesFound: boolean,
180
- // firstTabbableNode: HTMLElement|undefined,
181
- // lastTabbableNode: HTMLElement|undefined,
182
- // firstDomTabbableNode: HTMLElement|undefined,
183
- // lastDomTabbableNode: HTMLElement|undefined,
184
- // nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
185
- // }>}
241
+ /** @type {Array<{
242
+ * container: HTMLElement,
243
+ * tabbableNodes: Array<HTMLElement>, // empty if none
244
+ * focusableNodes: Array<HTMLElement>, // empty if none
245
+ * posTabIndexesFound: boolean,
246
+ * firstTabbableNode: HTMLElement|undefined,
247
+ * lastTabbableNode: HTMLElement|undefined,
248
+ * firstDomTabbableNode: HTMLElement|undefined,
249
+ * lastDomTabbableNode: HTMLElement|undefined,
250
+ * nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
251
+ * }>}
252
+ */
186
253
  containerGroups: [],
187
254
  // same order/length as `containers` list
188
255
 
@@ -191,6 +258,12 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
191
258
  // NOTE: same order as `containers` and `containerGroups`, but __not necessarily__
192
259
  // the same length
193
260
  tabbableGroups: [],
261
+ // references to nodes that are siblings to the ancestors of this trap's containers.
262
+ /** @type {Set<HTMLElement>} */
263
+ adjacentElements: new Set(),
264
+ // references to nodes that were inert before the trap was activated.
265
+ /** @type {Set<HTMLElement>} */
266
+ alreadyInert: new Set(),
194
267
  nodeFocusedBeforeActivation: null,
195
268
  mostRecentlyFocusedNode: null,
196
269
  active: false,
@@ -794,6 +867,74 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
794
867
  doc.addEventListener('keydown', checkEscapeKey);
795
868
  return trap;
796
869
  };
870
+
871
+ /**
872
+ * Traverses up the DOM from each of `containers`, collecting references to
873
+ * the elements that are siblings to `container` or an ancestor of `container`.
874
+ * @param {Array<HTMLElement>} containers
875
+ */
876
+ var collectAdjacentElements = function collectAdjacentElements(containers) {
877
+ // Re-activate all adjacent elements & clear previous collection.
878
+ if (state.active && !state.paused) {
879
+ trap._setSubtreeIsolation(false);
880
+ }
881
+ state.adjacentElements.clear();
882
+ state.alreadyInert.clear();
883
+
884
+ // Collect all ancestors of all containers to avoid redundant processing.
885
+ var containerAncestors = new Set();
886
+ var adjacentElements = new Set();
887
+
888
+ // Compile all elements adjacent to the focus trap containers & lineage.
889
+ var _iterator = _createForOfIteratorHelper(containers),
890
+ _step;
891
+ try {
892
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
893
+ var container = _step.value;
894
+ containerAncestors.add(container);
895
+ var insideShadowRoot = typeof ShadowRoot !== 'undefined' && container.getRootNode() instanceof ShadowRoot;
896
+ var current = container;
897
+ while (current) {
898
+ containerAncestors.add(current);
899
+ var parent = current.parentElement;
900
+ var siblings = [];
901
+ if (parent) {
902
+ siblings = parent.children;
903
+ } else if (!parent && insideShadowRoot) {
904
+ siblings = current.getRootNode().children;
905
+ parent = current.getRootNode().host;
906
+ insideShadowRoot = typeof ShadowRoot !== 'undefined' && parent.getRootNode() instanceof ShadowRoot;
907
+ }
908
+
909
+ // Add all the children, we'll remove container lineage later.
910
+ var _iterator2 = _createForOfIteratorHelper(siblings),
911
+ _step2;
912
+ try {
913
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
914
+ var child = _step2.value;
915
+ adjacentElements.add(child);
916
+ }
917
+ } catch (err) {
918
+ _iterator2.e(err);
919
+ } finally {
920
+ _iterator2.f();
921
+ }
922
+ current = parent;
923
+ }
924
+ }
925
+
926
+ // Multi-container traps may overlap.
927
+ // Remove elements within container lineages.
928
+ } catch (err) {
929
+ _iterator.e(err);
930
+ } finally {
931
+ _iterator.f();
932
+ }
933
+ containerAncestors.forEach(function (el) {
934
+ adjacentElements["delete"](el);
935
+ });
936
+ state.adjacentElements = adjacentElements;
937
+ };
797
938
  var removeListeners = function removeListeners() {
798
939
  if (!state.active) {
799
940
  return;
@@ -862,26 +1003,47 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
862
1003
  var onActivate = getOption(activateOptions, 'onActivate');
863
1004
  var onPostActivate = getOption(activateOptions, 'onPostActivate');
864
1005
  var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
865
- if (!checkCanFocusTrap) {
866
- updateTabbableNodes();
1006
+
1007
+ // If a currently-active trap is isolating its subtree, we need to remove
1008
+ // that isolation to allow the new trap to find tabbable nodes.
1009
+ var preexistingTrap = activeFocusTraps.getActiveTrap(trapStack);
1010
+ var revertState = false;
1011
+ if (preexistingTrap && !preexistingTrap.paused) {
1012
+ preexistingTrap._setSubtreeIsolation(false);
1013
+ revertState = true;
867
1014
  }
868
- state.active = true;
869
- state.paused = false;
870
- state.nodeFocusedBeforeActivation = _getActiveElement(doc);
871
- onActivate === null || onActivate === void 0 || onActivate();
872
- var finishActivation = function finishActivation() {
873
- if (checkCanFocusTrap) {
1015
+ try {
1016
+ if (!checkCanFocusTrap) {
874
1017
  updateTabbableNodes();
875
1018
  }
876
- addListeners();
877
- updateObservedNodes();
878
- onPostActivate === null || onPostActivate === void 0 || onPostActivate();
879
- };
880
- if (checkCanFocusTrap) {
881
- checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
882
- return this;
1019
+ state.active = true;
1020
+ state.paused = false;
1021
+ state.nodeFocusedBeforeActivation = _getActiveElement(doc);
1022
+ onActivate === null || onActivate === void 0 || onActivate();
1023
+ var finishActivation = function finishActivation() {
1024
+ if (checkCanFocusTrap) {
1025
+ updateTabbableNodes();
1026
+ }
1027
+ addListeners();
1028
+ updateObservedNodes();
1029
+ if (config.isolateSubtrees) {
1030
+ trap._setSubtreeIsolation(true);
1031
+ }
1032
+ onPostActivate === null || onPostActivate === void 0 || onPostActivate();
1033
+ };
1034
+ if (checkCanFocusTrap) {
1035
+ checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
1036
+ return this;
1037
+ }
1038
+ finishActivation();
1039
+ } catch (error) {
1040
+ // If our activation throws an exception and the stack hasn't changed,
1041
+ // we need to re-enable the prior trap's subtree isolation.
1042
+ if (preexistingTrap === activeFocusTraps.getActiveTrap(trapStack) && revertState) {
1043
+ preexistingTrap._setSubtreeIsolation(true);
1044
+ }
1045
+ throw error;
883
1046
  }
884
- finishActivation();
885
1047
  return this;
886
1048
  },
887
1049
  deactivate: function deactivate(deactivateOptions) {
@@ -895,6 +1057,15 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
895
1057
  }, deactivateOptions);
896
1058
  clearTimeout(state.delayInitialFocusTimer); // noop if undefined
897
1059
  state.delayInitialFocusTimer = undefined;
1060
+
1061
+ // Prior to removing this trap from the trapStack, we need to remove any applications of `inert`.
1062
+ // This allows the next trap down to update its tabbable nodes properly.
1063
+ //
1064
+ // If this trap is not top of the stack, don't change any current isolation.
1065
+ if (!state.paused) {
1066
+ trap._setSubtreeIsolation(false);
1067
+ }
1068
+ state.alreadyInert.clear();
898
1069
  removeListeners();
899
1070
  state.active = false;
900
1071
  state.paused = false;
@@ -942,8 +1113,14 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
942
1113
  state.containers = elementsAsArray.map(function (element) {
943
1114
  return typeof element === 'string' ? doc.querySelector(element) : element;
944
1115
  });
1116
+ if (config.isolateSubtrees) {
1117
+ collectAdjacentElements(state.containers);
1118
+ }
945
1119
  if (state.active) {
946
1120
  updateTabbableNodes();
1121
+ if (config.isolateSubtrees && !state.paused) {
1122
+ trap._setSubtreeIsolation(true);
1123
+ }
947
1124
  }
948
1125
  updateObservedNodes();
949
1126
  return this;
@@ -967,11 +1144,13 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
967
1144
  onPause === null || onPause === void 0 || onPause();
968
1145
  removeListeners();
969
1146
  updateObservedNodes();
1147
+ trap._setSubtreeIsolation(false);
970
1148
  onPostPause === null || onPostPause === void 0 || onPostPause();
971
1149
  } else {
972
1150
  var onUnpause = getOption(options, 'onUnpause');
973
1151
  var onPostUnpause = getOption(options, 'onPostUnpause');
974
1152
  onUnpause === null || onUnpause === void 0 || onUnpause();
1153
+ trap._setSubtreeIsolation(true);
975
1154
  updateTabbableNodes();
976
1155
  addListeners();
977
1156
  updateObservedNodes();
@@ -979,6 +1158,27 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
979
1158
  }
980
1159
  return this;
981
1160
  }
1161
+ },
1162
+ _setSubtreeIsolation: {
1163
+ value: function value(isEnabled) {
1164
+ if (config.isolateSubtrees) {
1165
+ state.adjacentElements.forEach(function (el) {
1166
+ if (isEnabled) {
1167
+ // check both attribute and property to ensure initial state is captured
1168
+ // correctly across different browsers and test environments (like JSDOM)
1169
+ var isInitiallyInert = el.inert || el.hasAttribute('inert');
1170
+ if (isInitiallyInert) {
1171
+ state.alreadyInert.add(el);
1172
+ }
1173
+ el.inert = true;
1174
+ } else {
1175
+ if (state.alreadyInert.has(el)) ; else {
1176
+ el.inert = false;
1177
+ }
1178
+ }
1179
+ });
1180
+ }
1181
+ }
982
1182
  }
983
1183
  });
984
1184