focus-trap 6.7.2 → 6.7.3
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 +6 -0
- package/README.md +2 -1
- package/dist/focus-trap.esm.js +55 -9
- package/dist/focus-trap.esm.js.map +1 -1
- package/dist/focus-trap.esm.min.js +2 -2
- package/dist/focus-trap.esm.min.js.map +1 -1
- package/dist/focus-trap.js +54 -8
- package/dist/focus-trap.js.map +1 -1
- package/dist/focus-trap.min.js +2 -2
- package/dist/focus-trap.min.js.map +1 -1
- package/dist/focus-trap.umd.js +54 -8
- package/dist/focus-trap.umd.js.map +1 -1
- package/dist/focus-trap.umd.min.js +2 -2
- package/dist/focus-trap.umd.min.js.map +1 -1
- package/index.js +56 -8
- package/package.json +10 -10
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tabbable, isFocusable, isTabbable } from 'tabbable';
|
|
1
|
+
import { tabbable, focusable, isFocusable, isTabbable } from 'tabbable';
|
|
2
2
|
|
|
3
3
|
const activeFocusTraps = (function () {
|
|
4
4
|
const trapQueue = [];
|
|
@@ -117,7 +117,12 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
117
117
|
// is active, but the trap should never get to a state where there isn't at least one group
|
|
118
118
|
// with at least one tabbable node in it (that would lead to an error condition that would
|
|
119
119
|
// result in an error being thrown)
|
|
120
|
-
// @type {Array<{
|
|
120
|
+
// @type {Array<{
|
|
121
|
+
// container: HTMLElement,
|
|
122
|
+
// firstTabbableNode: HTMLElement|null,
|
|
123
|
+
// lastTabbableNode: HTMLElement|null,
|
|
124
|
+
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
|
|
125
|
+
// }>}
|
|
121
126
|
tabbableGroups: [],
|
|
122
127
|
|
|
123
128
|
nodeFocusedBeforeActivation: null,
|
|
@@ -227,11 +232,46 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
227
232
|
.map((container) => {
|
|
228
233
|
const tabbableNodes = tabbable(container);
|
|
229
234
|
|
|
235
|
+
// NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes
|
|
236
|
+
// are a superset of tabbable nodes
|
|
237
|
+
const focusableNodes = focusable(container);
|
|
238
|
+
|
|
230
239
|
if (tabbableNodes.length > 0) {
|
|
231
240
|
return {
|
|
232
241
|
container,
|
|
233
242
|
firstTabbableNode: tabbableNodes[0],
|
|
234
243
|
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1],
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Finds the __tabbable__ node that follows the given node in the specified direction,
|
|
247
|
+
* in this container, if any.
|
|
248
|
+
* @param {HTMLElement} node
|
|
249
|
+
* @param {boolean} [forward] True if going in forward tab order; false if going
|
|
250
|
+
* in reverse.
|
|
251
|
+
* @returns {HTMLElement|undefined} The next tabbable node, if any.
|
|
252
|
+
*/
|
|
253
|
+
nextTabbableNode(node, forward = true) {
|
|
254
|
+
// NOTE: If tabindex is positive (in order to manipulate the tab order separate
|
|
255
|
+
// from the DOM order), this __will not work__ because the list of focusableNodes,
|
|
256
|
+
// while it contains tabbable nodes, does not sort its nodes in any order other
|
|
257
|
+
// than DOM order, because it can't: Where would you place focusable (but not
|
|
258
|
+
// tabbable) nodes in that order? They have no order, because they aren't tabbale...
|
|
259
|
+
// Support for positive tabindex is already broken and hard to manage (possibly
|
|
260
|
+
// not supportable, TBD), so this isn't going to make things worse than they
|
|
261
|
+
// already are, and at least makes things better for the majority of cases where
|
|
262
|
+
// tabindex is either 0/unset or negative.
|
|
263
|
+
// FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375
|
|
264
|
+
const nodeIdx = focusableNodes.findIndex((n) => n === node);
|
|
265
|
+
if (forward) {
|
|
266
|
+
return focusableNodes
|
|
267
|
+
.slice(nodeIdx + 1)
|
|
268
|
+
.find((n) => isTabbable(n));
|
|
269
|
+
}
|
|
270
|
+
return focusableNodes
|
|
271
|
+
.slice(0, nodeIdx)
|
|
272
|
+
.reverse()
|
|
273
|
+
.find((n) => isTabbable(n));
|
|
274
|
+
},
|
|
235
275
|
};
|
|
236
276
|
}
|
|
237
277
|
|
|
@@ -352,6 +392,8 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
352
392
|
const containerIndex = findIndex(state.tabbableGroups, ({ container }) =>
|
|
353
393
|
container.contains(target)
|
|
354
394
|
);
|
|
395
|
+
const containerGroup =
|
|
396
|
+
containerIndex >= 0 ? state.tabbableGroups[containerIndex] : undefined;
|
|
355
397
|
|
|
356
398
|
if (containerIndex < 0) {
|
|
357
399
|
// target not found in any group: quite possible focus has escaped the trap,
|
|
@@ -376,12 +418,15 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
376
418
|
|
|
377
419
|
if (
|
|
378
420
|
startOfGroupIndex < 0 &&
|
|
379
|
-
(
|
|
380
|
-
(isFocusable(target) &&
|
|
421
|
+
(containerGroup.container === target ||
|
|
422
|
+
(isFocusable(target) &&
|
|
423
|
+
!isTabbable(target) &&
|
|
424
|
+
!containerGroup.nextTabbableNode(target, false)))
|
|
381
425
|
) {
|
|
382
426
|
// an exception case where the target is either the container itself, or
|
|
383
427
|
// a non-tabbable node that was given focus (i.e. tabindex is negative
|
|
384
|
-
// and user clicked on it or node was programmatically given focus)
|
|
428
|
+
// and user clicked on it or node was programmatically given focus)
|
|
429
|
+
// and is not followed by any other tabbable node, in which
|
|
385
430
|
// case, we should handle shift+tab as if focus were on the container's
|
|
386
431
|
// first tabbable node, and go to the last tabbable node of the LAST group
|
|
387
432
|
startOfGroupIndex = containerIndex;
|
|
@@ -410,12 +455,15 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
410
455
|
|
|
411
456
|
if (
|
|
412
457
|
lastOfGroupIndex < 0 &&
|
|
413
|
-
(
|
|
414
|
-
(isFocusable(target) &&
|
|
458
|
+
(containerGroup.container === target ||
|
|
459
|
+
(isFocusable(target) &&
|
|
460
|
+
!isTabbable(target) &&
|
|
461
|
+
!containerGroup.nextTabbableNode(target)))
|
|
415
462
|
) {
|
|
416
463
|
// an exception case where the target is the container itself, or
|
|
417
464
|
// a non-tabbable node that was given focus (i.e. tabindex is negative
|
|
418
|
-
// and user clicked on it or node was programmatically given focus)
|
|
465
|
+
// and user clicked on it or node was programmatically given focus)
|
|
466
|
+
// and is not followed by any other tabbable node, in which
|
|
419
467
|
// case, we should handle tab as if focus were on the container's
|
|
420
468
|
// last tabbable node, and go to the first tabbable node of the FIRST group
|
|
421
469
|
lastOfGroupIndex = containerIndex;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap",
|
|
3
|
-
"version": "6.7.
|
|
3
|
+
"version": "6.7.3",
|
|
4
4
|
"description": "Trap focus within a DOM node.",
|
|
5
5
|
"main": "dist/focus-trap.js",
|
|
6
6
|
"module": "dist/focus-trap.esm.js",
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"tabbable": "^5.2.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@babel/cli": "^7.
|
|
69
|
-
"@babel/core": "^7.
|
|
70
|
-
"@babel/eslint-parser": "^7.
|
|
71
|
-
"@babel/preset-env": "^7.16.
|
|
72
|
-
"@changesets/cli": "^2.
|
|
68
|
+
"@babel/cli": "^7.17.0",
|
|
69
|
+
"@babel/core": "^7.17.2",
|
|
70
|
+
"@babel/eslint-parser": "^7.17.0",
|
|
71
|
+
"@babel/preset-env": "^7.16.11",
|
|
72
|
+
"@changesets/cli": "^2.20.0",
|
|
73
73
|
"@rollup/plugin-babel": "^5.3.0",
|
|
74
74
|
"@rollup/plugin-commonjs": "^21.0.1",
|
|
75
75
|
"@rollup/plugin-node-resolve": "^13.1.3",
|
|
@@ -78,20 +78,20 @@
|
|
|
78
78
|
"all-contributors-cli": "^6.20.0",
|
|
79
79
|
"babel-loader": "^8.2.3",
|
|
80
80
|
"cross-env": "^7.0.3",
|
|
81
|
-
"cypress": "^9.
|
|
81
|
+
"cypress": "^9.4.1",
|
|
82
82
|
"cypress-plugin-tab": "^1.0.5",
|
|
83
|
-
"eslint": "^8.
|
|
83
|
+
"eslint": "^8.8.0",
|
|
84
84
|
"eslint-config-prettier": "^8.3.0",
|
|
85
85
|
"eslint-plugin-cypress": "^2.12.1",
|
|
86
86
|
"onchange": "^7.1.0",
|
|
87
87
|
"prettier": "^2.5.1",
|
|
88
|
-
"rollup": "^2.
|
|
88
|
+
"rollup": "^2.67.1",
|
|
89
89
|
"rollup-plugin-inject-process-env": "^1.3.1",
|
|
90
90
|
"rollup-plugin-livereload": "^2.0.5",
|
|
91
91
|
"rollup-plugin-serve": "^1.1.0",
|
|
92
92
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
93
93
|
"rollup-plugin-terser": "^7.0.1",
|
|
94
94
|
"start-server-and-test": "^1.14.0",
|
|
95
|
-
"typescript": "^4.5.
|
|
95
|
+
"typescript": "^4.5.5"
|
|
96
96
|
}
|
|
97
97
|
}
|