focus-trap 7.8.0 → 8.0.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - b6ea4b5: **Breaking:** Fixed a long-standing bug where `onPostActivate()` would be called _before_ the initial focus node was focused and the trap was fully activated. ([#1747](https://github.com/focus-trap/focus-trap/issues/1747))
8
+ - By default (and for many years now), a trap delays setting focus to the initial focus node to the next frame (`setTimeout(0)`) but wasn't delaying calling `onPostActivate()` until after that delay.
9
+ - With the new `isolateSubtrees='aria-hidden'` option, the currently-focused node's container (a non-subtree being "disabled") would get hidden before the delay was up, resulting in Chrome preventing the effect of `aria-hidden` on that subtree with a warning in the console due to the container being hidden still containing focus (e.g. the "activate trap" button).
10
+ - **With the fix**, subtree isolation and the call to `onPostActivate()` await the initial focus delay (if there is one, which is default behavior; remove it with `delayInitialFocus=false`) before being applied/called.
11
+ - This may cause tests to fail, requiring the addition of slight delays before testing a given state (e.g. `await waitFor(() => expect(initialFocusNode).toBeFocused())`.
12
+ - It may also disrupt current assumptions about the state of the initial focus node in code that runs in your `onPostActivate()` handler (prior to this change, that node would **not** be focused yet; after this change, **it will be focused**).
13
+
3
14
  ## 7.8.0
4
15
 
5
16
  ### Minor Changes
@@ -1,8 +1,8 @@
1
1
  /*!
2
- * focus-trap 7.8.0
2
+ * focus-trap 8.0.0
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
- import { tabbable, focusable, isTabbable, getTabIndex, isFocusable } from 'tabbable';
5
+ import { isFocusable, tabbable, focusable, isTabbable, getTabIndex } from 'tabbable';
6
6
 
7
7
  function _arrayLikeToArray(r, a) {
8
8
  (null == a || a > r.length) && (a = r.length);
@@ -12,6 +12,31 @@ function _arrayLikeToArray(r, a) {
12
12
  function _arrayWithoutHoles(r) {
13
13
  if (Array.isArray(r)) return _arrayLikeToArray(r);
14
14
  }
15
+ function asyncGeneratorStep(n, t, e, r, o, a, c) {
16
+ try {
17
+ var i = n[a](c),
18
+ u = i.value;
19
+ } catch (n) {
20
+ return void e(n);
21
+ }
22
+ i.done ? t(u) : Promise.resolve(u).then(r, o);
23
+ }
24
+ function _asyncToGenerator(n) {
25
+ return function () {
26
+ var t = this,
27
+ e = arguments;
28
+ return new Promise(function (r, o) {
29
+ var a = n.apply(t, e);
30
+ function _next(n) {
31
+ asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
32
+ }
33
+ function _throw(n) {
34
+ asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
35
+ }
36
+ _next(void 0);
37
+ });
38
+ };
39
+ }
15
40
  function _createForOfIteratorHelper(r, e) {
16
41
  var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
17
42
  if (!t) {
@@ -95,6 +120,114 @@ function _objectSpread2(e) {
95
120
  }
96
121
  return e;
97
122
  }
123
+ function _regenerator() {
124
+ /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
125
+ var e,
126
+ t,
127
+ r = "function" == typeof Symbol ? Symbol : {},
128
+ n = r.iterator || "@@iterator",
129
+ o = r.toStringTag || "@@toStringTag";
130
+ function i(r, n, o, i) {
131
+ var c = n && n.prototype instanceof Generator ? n : Generator,
132
+ u = Object.create(c.prototype);
133
+ return _regeneratorDefine(u, "_invoke", function (r, n, o) {
134
+ var i,
135
+ c,
136
+ u,
137
+ f = 0,
138
+ p = o || [],
139
+ y = false,
140
+ G = {
141
+ p: 0,
142
+ n: 0,
143
+ v: e,
144
+ a: d,
145
+ f: d.bind(e, 4),
146
+ d: function (t, r) {
147
+ return i = t, c = 0, u = e, G.n = r, a;
148
+ }
149
+ };
150
+ function d(r, n) {
151
+ for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) {
152
+ var o,
153
+ i = p[t],
154
+ d = G.p,
155
+ l = i[2];
156
+ r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0));
157
+ }
158
+ if (o || r > 1) return a;
159
+ throw y = true, n;
160
+ }
161
+ return function (o, p, l) {
162
+ if (f > 1) throw TypeError("Generator is already running");
163
+ for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) {
164
+ i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u);
165
+ try {
166
+ if (f = 2, i) {
167
+ if (c || (o = "next"), t = i[o]) {
168
+ if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object");
169
+ if (!t.done) return t;
170
+ u = t.value, c < 2 && (c = 0);
171
+ } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1);
172
+ i = e;
173
+ } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break;
174
+ } catch (t) {
175
+ i = e, c = 1, u = t;
176
+ } finally {
177
+ f = 1;
178
+ }
179
+ }
180
+ return {
181
+ value: t,
182
+ done: y
183
+ };
184
+ };
185
+ }(r, o, i), true), u;
186
+ }
187
+ var a = {};
188
+ function Generator() {}
189
+ function GeneratorFunction() {}
190
+ function GeneratorFunctionPrototype() {}
191
+ t = Object.getPrototypeOf;
192
+ var c = [][n] ? t(t([][n]())) : (_regeneratorDefine(t = {}, n, function () {
193
+ return this;
194
+ }), t),
195
+ u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c);
196
+ function f(e) {
197
+ return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e;
198
+ }
199
+ return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine(u), _regeneratorDefine(u, o, "Generator"), _regeneratorDefine(u, n, function () {
200
+ return this;
201
+ }), _regeneratorDefine(u, "toString", function () {
202
+ return "[object Generator]";
203
+ }), (_regenerator = function () {
204
+ return {
205
+ w: i,
206
+ m: f
207
+ };
208
+ })();
209
+ }
210
+ function _regeneratorDefine(e, r, n, t) {
211
+ var i = Object.defineProperty;
212
+ try {
213
+ i({}, "", {});
214
+ } catch (e) {
215
+ i = 0;
216
+ }
217
+ _regeneratorDefine = function (e, r, n, t) {
218
+ function o(r, n) {
219
+ _regeneratorDefine(e, r, function (e) {
220
+ return this._invoke(r, n, e);
221
+ });
222
+ }
223
+ r ? i ? i(e, r, {
224
+ value: n,
225
+ enumerable: !t,
226
+ configurable: !t,
227
+ writable: !t
228
+ }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2));
229
+ }, _regeneratorDefine(e, r, n, t);
230
+ }
98
231
  function _toConsumableArray(r) {
99
232
  return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
100
233
  }
@@ -649,7 +782,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
649
782
  var checkPointerDown = function checkPointerDown(e) {
650
783
  var target = getActualTarget(e);
651
784
  if (findContainerIndex(target, e) >= 0) {
652
- // allow the click since it ocurred inside the trap
785
+ // allow the click since it occurred inside the trap
653
786
  return;
654
787
  }
655
788
  if (valueOrHandler(config.clickOutsideDeactivates, e)) {
@@ -832,9 +965,15 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
832
965
  // EVENT LISTENERS
833
966
  //
834
967
 
968
+ /**
969
+ * Adds listeners to the document necessary for trapping focus and attempts to set focus
970
+ * to the configured initial focus node. Does nothing if the trap isn't active.
971
+ * @returns {Promise<void>} Resolved (always) once the initial focus node has been focused.
972
+ * Also resolved if the trap isn't active.
973
+ */
835
974
  var addListeners = function addListeners() {
836
975
  if (!state.active) {
837
- return;
976
+ return Promise.resolve();
838
977
  }
839
978
 
840
979
  // There can be only one listening focus trap at a time
@@ -842,9 +981,21 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
842
981
 
843
982
  // Delay ensures that the focused element doesn't capture the event
844
983
  // that caused the focus trap activation.
845
- state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () {
984
+ /** @type {Promise<void>} */
985
+ var promise;
986
+ if (config.delayInitialFocus) {
987
+ // NOTE: Promise constructor callback is called synchronously, which is what we want
988
+ // since we need to capture the timer ID immediately
989
+ promise = new Promise(function (resolve) {
990
+ state.delayInitialFocusTimer = delay(function () {
991
+ _tryFocus(getInitialFocusNode());
992
+ resolve();
993
+ });
994
+ });
995
+ } else {
996
+ promise = Promise.resolve();
846
997
  _tryFocus(getInitialFocusNode());
847
- }) : _tryFocus(getInitialFocusNode());
998
+ }
848
999
  doc.addEventListener('focusin', checkFocusIn, true);
849
1000
  doc.addEventListener('mousedown', checkPointerDown, {
850
1001
  capture: true,
@@ -863,7 +1014,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
863
1014
  passive: false
864
1015
  });
865
1016
  doc.addEventListener('keydown', checkEscapeKey);
866
- return trap;
1017
+ return promise;
867
1018
  };
868
1019
 
869
1020
  /**
@@ -1022,17 +1173,35 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1022
1173
  state.paused = false;
1023
1174
  state.nodeFocusedBeforeActivation = _getActiveElement(doc);
1024
1175
  onActivate === null || onActivate === void 0 || onActivate();
1025
- var finishActivation = function finishActivation() {
1026
- if (checkCanFocusTrap) {
1027
- updateTabbableNodes();
1028
- }
1029
- addListeners();
1030
- updateObservedNodes();
1031
- if (config.isolateSubtrees) {
1032
- trap._setSubtreeIsolation(true);
1033
- }
1034
- onPostActivate === null || onPostActivate === void 0 || onPostActivate();
1035
- };
1176
+ var finishActivation = /*#__PURE__*/function () {
1177
+ var _ref6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1178
+ return _regenerator().w(function (_context) {
1179
+ while (1) switch (_context.n) {
1180
+ case 0:
1181
+ if (checkCanFocusTrap) {
1182
+ updateTabbableNodes();
1183
+ }
1184
+
1185
+ // NOTE: wait for initial focus node to get focused before we potentially isolate
1186
+ // the subtrees with aria-hidden while focus is still in some other subtree and
1187
+ // not yet in the trap, resulting in some browsers (e.g. Chrome) logging to the
1188
+ // console that they, "Blocked aria-hidden on an element because its descendant
1189
+ // retained focus..."
1190
+ _context.n = 1;
1191
+ return addListeners();
1192
+ case 1:
1193
+ trap._setSubtreeIsolation(true);
1194
+ updateObservedNodes();
1195
+ onPostActivate === null || onPostActivate === void 0 || onPostActivate();
1196
+ case 2:
1197
+ return _context.a(2);
1198
+ }
1199
+ }, _callee);
1200
+ }));
1201
+ return function finishActivation() {
1202
+ return _ref6.apply(this, arguments);
1203
+ };
1204
+ }();
1036
1205
  if (checkCanFocusTrap) {
1037
1206
  checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
1038
1207
  return this;
@@ -1124,7 +1293,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1124
1293
  }
1125
1294
  if (state.active) {
1126
1295
  updateTabbableNodes();
1127
- if (config.isolateSubtrees && !state.paused) {
1296
+ if (!state.paused) {
1128
1297
  trap._setSubtreeIsolation(true);
1129
1298
  }
1130
1299
  }
@@ -1149,18 +1318,41 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1149
1318
  var onPostPause = getOption(options, 'onPostPause');
1150
1319
  onPause === null || onPause === void 0 || onPause();
1151
1320
  removeListeners();
1152
- updateObservedNodes();
1153
1321
  trap._setSubtreeIsolation(false);
1322
+ updateObservedNodes();
1154
1323
  onPostPause === null || onPostPause === void 0 || onPostPause();
1155
1324
  } else {
1156
1325
  var onUnpause = getOption(options, 'onUnpause');
1157
1326
  var onPostUnpause = getOption(options, 'onPostUnpause');
1158
1327
  onUnpause === null || onUnpause === void 0 || onUnpause();
1159
- trap._setSubtreeIsolation(true);
1160
- updateTabbableNodes();
1161
- addListeners();
1162
- updateObservedNodes();
1163
- onPostUnpause === null || onPostUnpause === void 0 || onPostUnpause();
1328
+ var finishUnpause = /*#__PURE__*/function () {
1329
+ var _ref7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1330
+ return _regenerator().w(function (_context2) {
1331
+ while (1) switch (_context2.n) {
1332
+ case 0:
1333
+ updateTabbableNodes();
1334
+
1335
+ // NOTE: wait for initial focus node to get focused before we potentially isolate
1336
+ // the subtrees with aria-hidden while focus is still in some other subtree and
1337
+ // not yet in the trap, resulting in some browsers (e.g. Chrome) logging to the
1338
+ // console that they, "Blocked aria-hidden on an element because its descendant
1339
+ // retained focus..."
1340
+ _context2.n = 1;
1341
+ return addListeners();
1342
+ case 1:
1343
+ trap._setSubtreeIsolation(true);
1344
+ updateObservedNodes();
1345
+ onPostUnpause === null || onPostUnpause === void 0 || onPostUnpause();
1346
+ case 2:
1347
+ return _context2.a(2);
1348
+ }
1349
+ }, _callee2);
1350
+ }));
1351
+ return function finishUnpause() {
1352
+ return _ref7.apply(this, arguments);
1353
+ };
1354
+ }();
1355
+ finishUnpause();
1164
1356
  }
1165
1357
  return this;
1166
1358
  }