focus-trap 8.0.1 → 8.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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 567dbe1: Add new `delayReturnFocus` option (default true) to control whether return focus and onPostDeactivate are deferred by one frame (#1689).
8
+
9
+ ### Patch Changes
10
+
11
+ - b70e8d9: Fix bug where removing the ancestor of a focused node within a trap would result in focus landing on the body instead of remaining in the trap (#1660).
12
+
13
+ ## 8.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 642f7f2: Update lifecycle hooks to include the associated focus trap as a parameter.
18
+
3
19
  ## 8.0.1
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -94,7 +94,7 @@ Returns a new focus trap on `element` (one or more "containers" of tabbable node
94
94
  ##### onActivate
95
95
 
96
96
  ```typescript
97
- () => void
97
+ (params: LifecycleParameters) => void
98
98
  ```
99
99
 
100
100
  A function that will be called **before** sending focus to the target element upon activation.
@@ -102,7 +102,7 @@ A function that will be called **before** sending focus to the target element up
102
102
  ##### onPostActivate
103
103
 
104
104
  ```typescript
105
- () => void
105
+ (params: LifecycleParameters) => void
106
106
  ```
107
107
 
108
108
  A function that will be called **after** sending focus to the target element upon activation **unless** initial focus is delayed because the [delayInitialFocus](#delayinitialfocus) is true (default).
@@ -110,7 +110,7 @@ A function that will be called **after** sending focus to the target element upo
110
110
  ##### onPause
111
111
 
112
112
  ```typescript
113
- () => void
113
+ (params: LifecycleParameters) => void
114
114
  ```
115
115
 
116
116
  A function that will be called immediately after the trap's state is updated to be paused.
@@ -118,7 +118,7 @@ A function that will be called immediately after the trap's state is updated to
118
118
  ##### onPostPause
119
119
 
120
120
  ```typescript
121
- () => void
121
+ (params: LifecycleParameters) => void
122
122
  ```
123
123
 
124
124
  A function that will be called after the trap has been completely paused and is no longer managing/trapping focus.
@@ -126,7 +126,7 @@ A function that will be called after the trap has been completely paused and is
126
126
  ##### onUnpause
127
127
 
128
128
  ```typescript
129
- () => void
129
+ (params: LifecycleParameters) => void
130
130
  ```
131
131
 
132
132
  A function that will be called immediately after the trap's state is updated to be active again, but prior to updating its knowledge of what nodes are tabbable within its containers, and prior to actively managing/trapping focus.
@@ -134,7 +134,7 @@ A function that will be called immediately after the trap's state is updated to
134
134
  ##### onPostUnpause
135
135
 
136
136
  ```typescript
137
- () => void
137
+ (params: LifecycleParameters) => void
138
138
  ```
139
139
 
140
140
  A function that will be called after the trap has been completely unpaused and is once again managing/trapping focus.
@@ -150,11 +150,12 @@ Note that if [delayInitialFocus](#delayinitialfocus) is true, this handler will
150
150
  Animated dialogs have a small delay between when `onActivate` is called and when the focus trap is focusable. `checkCanFocusTrap` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to the first tabbable node (in tab order) in the focus trap (or the node configured in the `initialFocus` option).
151
151
 
152
152
  🔺 It does not matter whether the Promise resolves or rejects, only that it settles. A rejected Promise will not result in cancellation of trap activation.
153
+ 🔺 This controls **when activation may proceed**. The separate [delayInitialFocus](#delayinitialfocus) option controls whether there is an additional one-frame delay before focus is sent once activation can proceed.
153
154
 
154
155
  ##### onDeactivate
155
156
 
156
157
  ```typescript
157
- () => void
158
+ (params: LifecycleParameters) => void
158
159
  ```
159
160
 
160
161
  A function that will be called **before** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation.
@@ -162,11 +163,13 @@ A function that will be called **before** returning focus to the node that had f
162
163
  ##### onPostDeactivate
163
164
 
164
165
  ```typescript
165
- () => void
166
+ (params: LifecycleParameters) => void
166
167
  ```
167
168
 
168
169
  A function that will be called after the trap is deactivated, after `onDeactivate`. If the `returnFocus` deactivation option was set, it will be called **after** returning focus to the node that had focus prior to activation (or configured with the `setReturnFocus` option) upon deactivation; otherwise, it will be called after deactivation completes.
169
170
 
171
+ By default, this callback is delayed by one frame along with return-focus timing; set [delayReturnFocus](#delayreturnfocus) to `false` to remove that extra delay.
172
+
170
173
  ##### checkCanReturnFocus
171
174
 
172
175
  ```typescript
@@ -175,6 +178,8 @@ A function that will be called after the trap is deactivated, after `onDeactivat
175
178
 
176
179
  An animated trigger button will have a small delay between when `onDeactivate` is called and when the focus is able to be sent back to the trigger. `checkCanReturnFocus` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to to the node that had focus prior to the activation of the trap (or the node configured in the `setReturnFocus` option).
177
180
 
181
+ 🔺 If [delayReturnFocus](#delayreturnfocus) is `true` (default), there is still an additional one-frame delay after this Promise settles before focus is returned and `onPostDeactivate` is called.
182
+
178
183
  ##### initialFocus
179
184
 
180
185
  ```typescript
@@ -272,6 +277,19 @@ boolean
272
277
  Default: `true`. Delays the autofocus to the next execution frame when the focus trap is activated. This prevents elements within the focusable element from capturing the event that triggered the focus trap activation.
273
278
 
274
279
  🔺 Note that when this option is `true` (default), it means the initial element to be focused will not be focused until **after** [onPostActivate](#onpostactivate) or [onPostUnpause](#onpostunpause) are called.
280
+ 🔺 This option controls the extra frame delay on activation. Use [checkCanFocusTrap](#checkcanfocustrap) to gate activation readiness, and [delayReturnFocus](#delayreturnfocus) plus [checkCanReturnFocus](#checkcanreturnfocus) for the equivalent deactivation timing controls.
281
+
282
+ ##### delayReturnFocus
283
+
284
+ ```typescript
285
+ boolean
286
+ ```
287
+
288
+ Default: `true`. Delays return-focus to the next execution frame when the focus trap is deactivated.
289
+
290
+ - This same delay also applies to [onPostDeactivate](#onpostdeactivate).
291
+ - Set to `false` to skip the extra frame delay.
292
+ - If [checkCanReturnFocus](#checkcanreturnfocus) is configured, that Promise must still settle first.
275
293
 
276
294
  ##### isolateSubtrees
277
295
 
@@ -389,8 +407,8 @@ Returns the `trap`.
389
407
 
390
408
  These options are used to override the focus trap's default behavior for this particular activation.
391
409
 
392
- - **onActivate** `{() => void}`: Default: whatever you chose for `createOptions.onActivate`. `null` or `false` are the equivalent of a `noop`.
393
- - **onPostActivate** `{() => void}`: Default: whatever you chose for `createOptions.onPostActivate`. `null` or `false` are the equivalent of a `noop`.
410
+ - **onActivate** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onActivate`. `null` or `false` are the equivalent of a `noop`.
411
+ - **onPostActivate** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onPostActivate`. `null` or `false` are the equivalent of a `noop`.
394
412
  - **checkCanFocusTrap** `{(containers: Array<HTMLElement | SVGElement>) => Promise<unknown>}`: Default: whatever you chose for `createOptions.checkCanFocusTrap`.
395
413
 
396
414
  ### trap.deactivate()
@@ -408,9 +426,9 @@ Returns the `trap`.
408
426
  These options are used to override the focus trap's default behavior for this particular deactivation.
409
427
 
410
428
  - **returnFocus** `{boolean}`: Default: whatever you set for `createOptions.returnFocusOnDeactivate`. If `true`, then the `setReturnFocus` option (specified when the trap was created) is used to determine where focus will be returned.
411
- - **onDeactivate** `{() => void}`: Default: whatever you set for `createOptions.onDeactivate`. `null` or `false` are the equivalent of a `noop`.
412
- - **onPostDeactivate** `{() => void}`: Default: whatever you set for `createOptions.onPostDeactivate`. `null` or `false` are the equivalent of a `noop`.
413
- - **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: Default: whatever you set for `createOptions.checkCanReturnFocus`. Not called if the `returnFocus` option is falsy. `trigger` is either the originally focused node prior to activation, or the result of the `setReturnFocus` configuration option.
429
+ - **onDeactivate** `{(params: LifecycleParameters) => void}`: Default: whatever you set for `createOptions.onDeactivate`. `null` or `false` are the equivalent of a `noop`.
430
+ - **onPostDeactivate** `{(params: LifecycleParameters) => void}`: Default: whatever you set for `createOptions.onPostDeactivate`. `null` or `false` are the equivalent of a `noop`.
431
+ - **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: Default: whatever you set for `createOptions.checkCanReturnFocus`. Not called if the `returnFocus` option is falsy. `trigger` is either the originally focused node prior to activation, or the result of the `setReturnFocus` configuration option. After this Promise settles, `createOptions.delayReturnFocus` determines whether return-focus and `onPostDeactivate` are immediate (`false`) or delayed by one frame (`true`, default).
414
432
 
415
433
  ### trap.pause()
416
434
 
@@ -436,8 +454,8 @@ This is useful in various cases, one of which is when you want one focus trap wi
436
454
 
437
455
  These options are used to override the focus trap's default behavior for this particular pausing.
438
456
 
439
- - **onPause** `{() => void}`: Default: whatever you chose for `createOptions.onPause`. `null` or `false` are the equivalent of a `noop`.
440
- - **onPostPause** `{() => void}`: Default: whatever you chose for `createOptions.onPostPause`. `null` or `false` are the equivalent of a `noop`.
457
+ - **onPause** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onPause`. `null` or `false` are the equivalent of a `noop`.
458
+ - **onPostPause** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onPostPause`. `null` or `false` are the equivalent of a `noop`.
441
459
 
442
460
  ### trap.unpause()
443
461
 
@@ -461,8 +479,8 @@ Returns the `trap`.
461
479
 
462
480
  These options are used to override the focus trap's default behavior for this particular unpausing.
463
481
 
464
- - **onUnpause** `{() => void}`: Default: whatever you chose for `createOptions.onUnpause`. `null` or `false` are the equivalent of a `noop`.
465
- - **onPostUnpause** `{() => void}`: Default: whatever you chose for `createOptions.onPostUnpause`. `null` or `false` are the equivalent of a `noop`.
482
+ - **onUnpause** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onUnpause`. `null` or `false` are the equivalent of a `noop`.
483
+ - **onPostUnpause** `{(params: LifecycleParameters) => void}`: Default: whatever you chose for `createOptions.onPostUnpause`. `null` or `false` are the equivalent of a `noop`.
466
484
 
467
485
  ### trap.updateContainerElements()
468
486
 
@@ -1,8 +1,8 @@
1
1
  /*!
2
- * focus-trap 8.0.1
2
+ * focus-trap 8.2.0
3
3
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
4
4
  */
5
- import { isFocusable, tabbable, focusable, isTabbable, getTabIndex } from 'tabbable';
5
+ import { tabbable, focusable, isTabbable, getTabIndex, isFocusable } from 'tabbable';
6
6
 
7
7
  function _arrayLikeToArray(r, a) {
8
8
  (null == a || a > r.length) && (a = r.length);
@@ -356,6 +356,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
356
356
  returnFocusOnDeactivate: true,
357
357
  escapeDeactivates: true,
358
358
  delayInitialFocus: true,
359
+ delayReturnFocus: true,
359
360
  isolateSubtrees: false,
360
361
  isKeyForward: isKeyForward,
361
362
  isKeyBackward: isKeyBackward
@@ -1102,17 +1103,27 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1102
1103
  //
1103
1104
 
1104
1105
  var checkDomRemoval = function checkDomRemoval(mutations) {
1106
+ var focusedNode = state.mostRecentlyFocusedNode;
1107
+ if (!focusedNode) {
1108
+ return;
1109
+ }
1105
1110
  var isFocusedNodeRemoved = mutations.some(function (mutation) {
1106
1111
  var removedNodes = Array.from(mutation.removedNodes);
1107
1112
  return removedNodes.some(function (node) {
1108
- return node === state.mostRecentlyFocusedNode;
1113
+ return node === focusedNode || typeof node.contains === 'function' && node.contains(focusedNode);
1109
1114
  });
1110
1115
  });
1111
1116
 
1112
- // If the currently focused is removed then browsers will move focus to the
1117
+ // If the currently focused node is removed then browsers will move focus to the
1113
1118
  // <body> element. If this happens, try to move focus back into the trap.
1114
- if (isFocusedNodeRemoved) {
1115
- _tryFocus(getInitialFocusNode());
1119
+ if (isFocusedNodeRemoved && state.containers.some(function (container) {
1120
+ return container === null || container === void 0 ? void 0 : container.isConnected;
1121
+ })) {
1122
+ // Refresh tabbable state before resolving initial focus because
1123
+ // getInitialFocusNode() may fall back to the first tabbable node in the trap.
1124
+ updateTabbableNodes();
1125
+ var initialFocusNode = getInitialFocusNode();
1126
+ _tryFocus(initialFocusNode);
1116
1127
  }
1117
1128
  };
1118
1129
 
@@ -1172,7 +1183,9 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1172
1183
  state.active = true;
1173
1184
  state.paused = false;
1174
1185
  state.nodeFocusedBeforeActivation = _getActiveElement(doc);
1175
- onActivate === null || onActivate === void 0 || onActivate();
1186
+ onActivate === null || onActivate === void 0 || onActivate({
1187
+ trap: trap
1188
+ });
1176
1189
  var finishActivation = /*#__PURE__*/function () {
1177
1190
  var _ref6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1178
1191
  return _regenerator().w(function (_context) {
@@ -1192,7 +1205,9 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1192
1205
  case 1:
1193
1206
  trap._setSubtreeIsolation(true);
1194
1207
  updateObservedNodes();
1195
- onPostActivate === null || onPostActivate === void 0 || onPostActivate();
1208
+ onPostActivate === null || onPostActivate === void 0 || onPostActivate({
1209
+ trap: trap
1210
+ });
1196
1211
  case 2:
1197
1212
  return _context.a(2);
1198
1213
  }
@@ -1249,16 +1264,26 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1249
1264
  var onDeactivate = getOption(options, 'onDeactivate');
1250
1265
  var onPostDeactivate = getOption(options, 'onPostDeactivate');
1251
1266
  var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus');
1267
+ var delayReturnFocus = getOption(options, 'delayReturnFocus');
1252
1268
  var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate');
1253
- onDeactivate === null || onDeactivate === void 0 || onDeactivate();
1254
- var finishDeactivation = function finishDeactivation() {
1255
- delay(function () {
1256
- if (returnFocus) {
1257
- _tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
1258
- }
1259
- onPostDeactivate === null || onPostDeactivate === void 0 || onPostDeactivate();
1269
+ onDeactivate === null || onDeactivate === void 0 || onDeactivate({
1270
+ trap: trap
1271
+ });
1272
+ var completeDeactivation = function completeDeactivation() {
1273
+ if (returnFocus) {
1274
+ _tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
1275
+ }
1276
+ onPostDeactivate === null || onPostDeactivate === void 0 || onPostDeactivate({
1277
+ trap: trap
1260
1278
  });
1261
1279
  };
1280
+ var finishDeactivation = function finishDeactivation() {
1281
+ if (delayReturnFocus && returnFocus) {
1282
+ delay(completeDeactivation);
1283
+ } else {
1284
+ completeDeactivation();
1285
+ }
1286
+ };
1262
1287
  if (returnFocus && checkCanReturnFocus) {
1263
1288
  checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation);
1264
1289
  return this;
@@ -1316,15 +1341,21 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1316
1341
  if (paused) {
1317
1342
  var onPause = getOption(options, 'onPause');
1318
1343
  var onPostPause = getOption(options, 'onPostPause');
1319
- onPause === null || onPause === void 0 || onPause();
1344
+ onPause === null || onPause === void 0 || onPause({
1345
+ trap: trap
1346
+ });
1320
1347
  removeListeners();
1321
1348
  trap._setSubtreeIsolation(false);
1322
1349
  updateObservedNodes();
1323
- onPostPause === null || onPostPause === void 0 || onPostPause();
1350
+ onPostPause === null || onPostPause === void 0 || onPostPause({
1351
+ trap: trap
1352
+ });
1324
1353
  } else {
1325
1354
  var onUnpause = getOption(options, 'onUnpause');
1326
1355
  var onPostUnpause = getOption(options, 'onPostUnpause');
1327
- onUnpause === null || onUnpause === void 0 || onUnpause();
1356
+ onUnpause === null || onUnpause === void 0 || onUnpause({
1357
+ trap: trap
1358
+ });
1328
1359
  var finishUnpause = /*#__PURE__*/function () {
1329
1360
  var _ref7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1330
1361
  return _regenerator().w(function (_context2) {
@@ -1342,7 +1373,9 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
1342
1373
  case 1:
1343
1374
  trap._setSubtreeIsolation(true);
1344
1375
  updateObservedNodes();
1345
- onPostUnpause === null || onPostUnpause === void 0 || onPostUnpause();
1376
+ onPostUnpause === null || onPostUnpause === void 0 || onPostUnpause({
1377
+ trap: trap
1378
+ });
1346
1379
  case 2:
1347
1380
  return _context2.a(2);
1348
1381
  }