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