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
  (function (global, factory) {
@@ -21,6 +21,54 @@
21
21
  function _arrayWithoutHoles(r) {
22
22
  if (Array.isArray(r)) return _arrayLikeToArray(r);
23
23
  }
24
+ function _createForOfIteratorHelper(r, e) {
25
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
26
+ if (!t) {
27
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
28
+ t && (r = t);
29
+ var n = 0,
30
+ F = function () {};
31
+ return {
32
+ s: F,
33
+ n: function () {
34
+ return n >= r.length ? {
35
+ done: true
36
+ } : {
37
+ done: false,
38
+ value: r[n++]
39
+ };
40
+ },
41
+ e: function (r) {
42
+ throw r;
43
+ },
44
+ f: F
45
+ };
46
+ }
47
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
48
+ }
49
+ var o,
50
+ a = true,
51
+ u = false;
52
+ return {
53
+ s: function () {
54
+ t = t.call(r);
55
+ },
56
+ n: function () {
57
+ var r = t.next();
58
+ return a = r.done, r;
59
+ },
60
+ e: function (r) {
61
+ u = true, o = r;
62
+ },
63
+ f: function () {
64
+ try {
65
+ a || null == t.return || t.return();
66
+ } finally {
67
+ if (u) throw o;
68
+ }
69
+ }
70
+ };
71
+ }
24
72
  function _defineProperty(e, r, t) {
25
73
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
26
74
  value: t,
@@ -82,12 +130,18 @@
82
130
  }
83
131
 
84
132
  var activeFocusTraps = {
133
+ // Returns the trap from the top of the stack.
134
+ getActiveTrap: function getActiveTrap(trapStack) {
135
+ if ((trapStack === null || trapStack === void 0 ? void 0 : trapStack.length) > 0) {
136
+ return trapStack[trapStack.length - 1];
137
+ }
138
+ return null;
139
+ },
140
+ // Pauses the currently active trap, then adds a new trap to the stack.
85
141
  activateTrap: function activateTrap(trapStack, trap) {
86
- if (trapStack.length > 0) {
87
- var activeTrap = trapStack[trapStack.length - 1];
88
- if (activeTrap !== trap) {
89
- activeTrap._setPausedState(true);
90
- }
142
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
143
+ if (trap !== activeTrap) {
144
+ activeFocusTraps.pauseTrap(trapStack);
91
145
  }
92
146
  var trapIndex = trapStack.indexOf(trap);
93
147
  if (trapIndex === -1) {
@@ -98,13 +152,24 @@
98
152
  trapStack.push(trap);
99
153
  }
100
154
  },
155
+ // Removes the trap from the top of the stack, then unpauses the next trap down.
101
156
  deactivateTrap: function deactivateTrap(trapStack, trap) {
102
157
  var trapIndex = trapStack.indexOf(trap);
103
158
  if (trapIndex !== -1) {
104
159
  trapStack.splice(trapIndex, 1);
105
160
  }
106
- if (trapStack.length > 0 && !trapStack[trapStack.length - 1]._isManuallyPaused()) {
107
- trapStack[trapStack.length - 1]._setPausedState(false);
161
+ activeFocusTraps.unpauseTrap(trapStack);
162
+ },
163
+ // Pauses the trap at the top of the stack.
164
+ pauseTrap: function pauseTrap(trapStack) {
165
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
166
+ activeTrap === null || activeTrap === void 0 || activeTrap._setPausedState(true);
167
+ },
168
+ // Unpauses the trap at the top of the stack.
169
+ unpauseTrap: function unpauseTrap(trapStack) {
170
+ var activeTrap = activeFocusTraps.getActiveTrap(trapStack);
171
+ if (activeTrap && !activeTrap._isManuallyPaused()) {
172
+ activeTrap._setPausedState(false);
108
173
  }
109
174
  }
110
175
  };
@@ -167,29 +232,31 @@
167
232
  returnFocusOnDeactivate: true,
168
233
  escapeDeactivates: true,
169
234
  delayInitialFocus: true,
235
+ isolateSubtrees: false,
170
236
  isKeyForward: isKeyForward,
171
237
  isKeyBackward: isKeyBackward
172
238
  }, userOptions);
173
239
  var state = {
174
240
  // containers given to createFocusTrap()
175
- // @type {Array<HTMLElement>}
241
+ /** @type {Array<HTMLElement>} */
176
242
  containers: [],
177
243
  // list of objects identifying tabbable nodes in `containers` in the trap
178
244
  // NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
179
245
  // is active, but the trap should never get to a state where there isn't at least one group
180
246
  // with at least one tabbable node in it (that would lead to an error condition that would
181
247
  // result in an error being thrown)
182
- // @type {Array<{
183
- // container: HTMLElement,
184
- // tabbableNodes: Array<HTMLElement>, // empty if none
185
- // focusableNodes: Array<HTMLElement>, // empty if none
186
- // posTabIndexesFound: boolean,
187
- // firstTabbableNode: HTMLElement|undefined,
188
- // lastTabbableNode: HTMLElement|undefined,
189
- // firstDomTabbableNode: HTMLElement|undefined,
190
- // lastDomTabbableNode: HTMLElement|undefined,
191
- // nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
192
- // }>}
248
+ /** @type {Array<{
249
+ * container: HTMLElement,
250
+ * tabbableNodes: Array<HTMLElement>, // empty if none
251
+ * focusableNodes: Array<HTMLElement>, // empty if none
252
+ * posTabIndexesFound: boolean,
253
+ * firstTabbableNode: HTMLElement|undefined,
254
+ * lastTabbableNode: HTMLElement|undefined,
255
+ * firstDomTabbableNode: HTMLElement|undefined,
256
+ * lastDomTabbableNode: HTMLElement|undefined,
257
+ * nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
258
+ * }>}
259
+ */
193
260
  containerGroups: [],
194
261
  // same order/length as `containers` list
195
262
 
@@ -198,6 +265,12 @@
198
265
  // NOTE: same order as `containers` and `containerGroups`, but __not necessarily__
199
266
  // the same length
200
267
  tabbableGroups: [],
268
+ // references to nodes that are siblings to the ancestors of this trap's containers.
269
+ /** @type {Set<HTMLElement>} */
270
+ adjacentElements: new Set(),
271
+ // references to nodes that were inert before the trap was activated.
272
+ /** @type {Set<HTMLElement>} */
273
+ alreadyInert: new Set(),
201
274
  nodeFocusedBeforeActivation: null,
202
275
  mostRecentlyFocusedNode: null,
203
276
  active: false,
@@ -801,6 +874,74 @@
801
874
  doc.addEventListener('keydown', checkEscapeKey);
802
875
  return trap;
803
876
  };
877
+
878
+ /**
879
+ * Traverses up the DOM from each of `containers`, collecting references to
880
+ * the elements that are siblings to `container` or an ancestor of `container`.
881
+ * @param {Array<HTMLElement>} containers
882
+ */
883
+ var collectAdjacentElements = function collectAdjacentElements(containers) {
884
+ // Re-activate all adjacent elements & clear previous collection.
885
+ if (state.active && !state.paused) {
886
+ trap._setSubtreeIsolation(false);
887
+ }
888
+ state.adjacentElements.clear();
889
+ state.alreadyInert.clear();
890
+
891
+ // Collect all ancestors of all containers to avoid redundant processing.
892
+ var containerAncestors = new Set();
893
+ var adjacentElements = new Set();
894
+
895
+ // Compile all elements adjacent to the focus trap containers & lineage.
896
+ var _iterator = _createForOfIteratorHelper(containers),
897
+ _step;
898
+ try {
899
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
900
+ var container = _step.value;
901
+ containerAncestors.add(container);
902
+ var insideShadowRoot = typeof ShadowRoot !== 'undefined' && container.getRootNode() instanceof ShadowRoot;
903
+ var current = container;
904
+ while (current) {
905
+ containerAncestors.add(current);
906
+ var parent = current.parentElement;
907
+ var siblings = [];
908
+ if (parent) {
909
+ siblings = parent.children;
910
+ } else if (!parent && insideShadowRoot) {
911
+ siblings = current.getRootNode().children;
912
+ parent = current.getRootNode().host;
913
+ insideShadowRoot = typeof ShadowRoot !== 'undefined' && parent.getRootNode() instanceof ShadowRoot;
914
+ }
915
+
916
+ // Add all the children, we'll remove container lineage later.
917
+ var _iterator2 = _createForOfIteratorHelper(siblings),
918
+ _step2;
919
+ try {
920
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
921
+ var child = _step2.value;
922
+ adjacentElements.add(child);
923
+ }
924
+ } catch (err) {
925
+ _iterator2.e(err);
926
+ } finally {
927
+ _iterator2.f();
928
+ }
929
+ current = parent;
930
+ }
931
+ }
932
+
933
+ // Multi-container traps may overlap.
934
+ // Remove elements within container lineages.
935
+ } catch (err) {
936
+ _iterator.e(err);
937
+ } finally {
938
+ _iterator.f();
939
+ }
940
+ containerAncestors.forEach(function (el) {
941
+ adjacentElements["delete"](el);
942
+ });
943
+ state.adjacentElements = adjacentElements;
944
+ };
804
945
  var removeListeners = function removeListeners() {
805
946
  if (!state.active) {
806
947
  return;
@@ -869,26 +1010,47 @@
869
1010
  var onActivate = getOption(activateOptions, 'onActivate');
870
1011
  var onPostActivate = getOption(activateOptions, 'onPostActivate');
871
1012
  var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
872
- if (!checkCanFocusTrap) {
873
- updateTabbableNodes();
1013
+
1014
+ // If a currently-active trap is isolating its subtree, we need to remove
1015
+ // that isolation to allow the new trap to find tabbable nodes.
1016
+ var preexistingTrap = activeFocusTraps.getActiveTrap(trapStack);
1017
+ var revertState = false;
1018
+ if (preexistingTrap && !preexistingTrap.paused) {
1019
+ preexistingTrap._setSubtreeIsolation(false);
1020
+ revertState = true;
874
1021
  }
875
- state.active = true;
876
- state.paused = false;
877
- state.nodeFocusedBeforeActivation = _getActiveElement(doc);
878
- onActivate === null || onActivate === void 0 || onActivate();
879
- var finishActivation = function finishActivation() {
880
- if (checkCanFocusTrap) {
1022
+ try {
1023
+ if (!checkCanFocusTrap) {
881
1024
  updateTabbableNodes();
882
1025
  }
883
- addListeners();
884
- updateObservedNodes();
885
- onPostActivate === null || onPostActivate === void 0 || onPostActivate();
886
- };
887
- if (checkCanFocusTrap) {
888
- checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
889
- return this;
1026
+ state.active = true;
1027
+ state.paused = false;
1028
+ state.nodeFocusedBeforeActivation = _getActiveElement(doc);
1029
+ onActivate === null || onActivate === void 0 || onActivate();
1030
+ var finishActivation = function finishActivation() {
1031
+ if (checkCanFocusTrap) {
1032
+ updateTabbableNodes();
1033
+ }
1034
+ addListeners();
1035
+ updateObservedNodes();
1036
+ if (config.isolateSubtrees) {
1037
+ trap._setSubtreeIsolation(true);
1038
+ }
1039
+ onPostActivate === null || onPostActivate === void 0 || onPostActivate();
1040
+ };
1041
+ if (checkCanFocusTrap) {
1042
+ checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
1043
+ return this;
1044
+ }
1045
+ finishActivation();
1046
+ } catch (error) {
1047
+ // If our activation throws an exception and the stack hasn't changed,
1048
+ // we need to re-enable the prior trap's subtree isolation.
1049
+ if (preexistingTrap === activeFocusTraps.getActiveTrap(trapStack) && revertState) {
1050
+ preexistingTrap._setSubtreeIsolation(true);
1051
+ }
1052
+ throw error;
890
1053
  }
891
- finishActivation();
892
1054
  return this;
893
1055
  },
894
1056
  deactivate: function deactivate(deactivateOptions) {
@@ -902,6 +1064,15 @@
902
1064
  }, deactivateOptions);
903
1065
  clearTimeout(state.delayInitialFocusTimer); // noop if undefined
904
1066
  state.delayInitialFocusTimer = undefined;
1067
+
1068
+ // Prior to removing this trap from the trapStack, we need to remove any applications of `inert`.
1069
+ // This allows the next trap down to update its tabbable nodes properly.
1070
+ //
1071
+ // If this trap is not top of the stack, don't change any current isolation.
1072
+ if (!state.paused) {
1073
+ trap._setSubtreeIsolation(false);
1074
+ }
1075
+ state.alreadyInert.clear();
905
1076
  removeListeners();
906
1077
  state.active = false;
907
1078
  state.paused = false;
@@ -949,8 +1120,14 @@
949
1120
  state.containers = elementsAsArray.map(function (element) {
950
1121
  return typeof element === 'string' ? doc.querySelector(element) : element;
951
1122
  });
1123
+ if (config.isolateSubtrees) {
1124
+ collectAdjacentElements(state.containers);
1125
+ }
952
1126
  if (state.active) {
953
1127
  updateTabbableNodes();
1128
+ if (config.isolateSubtrees && !state.paused) {
1129
+ trap._setSubtreeIsolation(true);
1130
+ }
954
1131
  }
955
1132
  updateObservedNodes();
956
1133
  return this;
@@ -974,11 +1151,13 @@
974
1151
  onPause === null || onPause === void 0 || onPause();
975
1152
  removeListeners();
976
1153
  updateObservedNodes();
1154
+ trap._setSubtreeIsolation(false);
977
1155
  onPostPause === null || onPostPause === void 0 || onPostPause();
978
1156
  } else {
979
1157
  var onUnpause = getOption(options, 'onUnpause');
980
1158
  var onPostUnpause = getOption(options, 'onPostUnpause');
981
1159
  onUnpause === null || onUnpause === void 0 || onUnpause();
1160
+ trap._setSubtreeIsolation(true);
982
1161
  updateTabbableNodes();
983
1162
  addListeners();
984
1163
  updateObservedNodes();
@@ -986,6 +1165,27 @@
986
1165
  }
987
1166
  return this;
988
1167
  }
1168
+ },
1169
+ _setSubtreeIsolation: {
1170
+ value: function value(isEnabled) {
1171
+ if (config.isolateSubtrees) {
1172
+ state.adjacentElements.forEach(function (el) {
1173
+ if (isEnabled) {
1174
+ // check both attribute and property to ensure initial state is captured
1175
+ // correctly across different browsers and test environments (like JSDOM)
1176
+ var isInitiallyInert = el.inert || el.hasAttribute('inert');
1177
+ if (isInitiallyInert) {
1178
+ state.alreadyInert.add(el);
1179
+ }
1180
+ el.inert = true;
1181
+ } else {
1182
+ if (state.alreadyInert.has(el)) ; else {
1183
+ el.inert = false;
1184
+ }
1185
+ }
1186
+ });
1187
+ }
1188
+ }
989
1189
  }
990
1190
  });
991
1191