focus-trap 7.0.0 → 7.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 +17 -0
- package/README.md +43 -31
- package/dist/focus-trap.esm.js +138 -166
- 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 +138 -166
- 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 +138 -166
- 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.d.ts +37 -5
- package/index.js +74 -45
- package/package.json +20 -20
package/index.js
CHANGED
|
@@ -1,38 +1,35 @@
|
|
|
1
1
|
import { tabbable, focusable, isFocusable, isTabbable } from 'tabbable';
|
|
2
2
|
|
|
3
|
-
const activeFocusTraps =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
if (activeTrap !== trap) {
|
|
10
|
-
activeTrap.pause();
|
|
11
|
-
}
|
|
3
|
+
const activeFocusTraps = {
|
|
4
|
+
activateTrap(trapStack, trap) {
|
|
5
|
+
if (trapStack.length > 0) {
|
|
6
|
+
const activeTrap = trapStack[trapStack.length - 1];
|
|
7
|
+
if (activeTrap !== trap) {
|
|
8
|
+
activeTrap.pause();
|
|
12
9
|
}
|
|
10
|
+
}
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
const trapIndex = trapStack.indexOf(trap);
|
|
13
|
+
if (trapIndex === -1) {
|
|
14
|
+
trapStack.push(trap);
|
|
15
|
+
} else {
|
|
16
|
+
// move this existing trap to the front of the queue
|
|
17
|
+
trapStack.splice(trapIndex, 1);
|
|
18
|
+
trapStack.push(trap);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
deactivateTrap(trapStack, trap) {
|
|
23
|
+
const trapIndex = trapStack.indexOf(trap);
|
|
24
|
+
if (trapIndex !== -1) {
|
|
25
|
+
trapStack.splice(trapIndex, 1);
|
|
26
|
+
}
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})();
|
|
28
|
+
if (trapStack.length > 0) {
|
|
29
|
+
trapStack[trapStack.length - 1].unpause();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
36
33
|
|
|
37
34
|
const isSelectableInput = function (node) {
|
|
38
35
|
return (
|
|
@@ -50,6 +47,16 @@ const isTabEvent = function (e) {
|
|
|
50
47
|
return e.key === 'Tab' || e.keyCode === 9;
|
|
51
48
|
};
|
|
52
49
|
|
|
50
|
+
// checks for TAB by default
|
|
51
|
+
const isKeyForward = function (e) {
|
|
52
|
+
return isTabEvent(e) && !e.shiftKey;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// checks for SHIFT+TAB by default
|
|
56
|
+
const isKeyBackward = function (e) {
|
|
57
|
+
return isTabEvent(e) && e.shiftKey;
|
|
58
|
+
};
|
|
59
|
+
|
|
53
60
|
const delay = function (fn) {
|
|
54
61
|
return setTimeout(fn, 0);
|
|
55
62
|
};
|
|
@@ -95,15 +102,23 @@ const getActualTarget = function (event) {
|
|
|
95
102
|
: event.target;
|
|
96
103
|
};
|
|
97
104
|
|
|
105
|
+
// NOTE: this must be _outside_ `createFocusTrap()` to make sure all traps in this
|
|
106
|
+
// current instance use the same stack if `userOptions.trapStack` isn't specified
|
|
107
|
+
const internalTrapStack = [];
|
|
108
|
+
|
|
98
109
|
const createFocusTrap = function (elements, userOptions) {
|
|
99
110
|
// SSR: a live trap shouldn't be created in this type of environment so this
|
|
100
111
|
// should be safe code to execute if the `document` option isn't specified
|
|
101
112
|
const doc = userOptions?.document || document;
|
|
102
113
|
|
|
114
|
+
const trapStack = userOptions?.trapStack || internalTrapStack;
|
|
115
|
+
|
|
103
116
|
const config = {
|
|
104
117
|
returnFocusOnDeactivate: true,
|
|
105
118
|
escapeDeactivates: true,
|
|
106
119
|
delayInitialFocus: true,
|
|
120
|
+
isKeyForward,
|
|
121
|
+
isKeyBackward,
|
|
107
122
|
...userOptions,
|
|
108
123
|
};
|
|
109
124
|
|
|
@@ -420,12 +435,12 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
420
435
|
}
|
|
421
436
|
};
|
|
422
437
|
|
|
423
|
-
// Hijack
|
|
438
|
+
// Hijack key nav events on the first and last focusable nodes of the trap,
|
|
424
439
|
// in order to prevent focus from escaping. If it escapes for even a
|
|
425
440
|
// moment it can end up scrolling the page and causing confusion so we
|
|
426
441
|
// kind of need to capture the action at the keydown phase.
|
|
427
|
-
const
|
|
428
|
-
const target = getActualTarget(
|
|
442
|
+
const checkKeyNav = function (event, isBackward = false) {
|
|
443
|
+
const target = getActualTarget(event);
|
|
429
444
|
updateTabbableNodes();
|
|
430
445
|
|
|
431
446
|
let destinationNode = null;
|
|
@@ -440,8 +455,8 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
440
455
|
|
|
441
456
|
if (containerIndex < 0) {
|
|
442
457
|
// target not found in any group: quite possible focus has escaped the trap,
|
|
443
|
-
// so bring it back
|
|
444
|
-
if (
|
|
458
|
+
// so bring it back into...
|
|
459
|
+
if (isBackward) {
|
|
445
460
|
// ...the last node in the last group
|
|
446
461
|
destinationNode =
|
|
447
462
|
state.tabbableGroups[state.tabbableGroups.length - 1]
|
|
@@ -450,7 +465,7 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
450
465
|
// ...the first node in the first group
|
|
451
466
|
destinationNode = state.tabbableGroups[0].firstTabbableNode;
|
|
452
467
|
}
|
|
453
|
-
} else if (
|
|
468
|
+
} else if (isBackward) {
|
|
454
469
|
// REVERSE
|
|
455
470
|
|
|
456
471
|
// is the target the first tabbable node in a group?
|
|
@@ -486,6 +501,10 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
486
501
|
|
|
487
502
|
const destinationGroup = state.tabbableGroups[destinationGroupIndex];
|
|
488
503
|
destinationNode = destinationGroup.lastTabbableNode;
|
|
504
|
+
} else if (!isTabEvent(event)) {
|
|
505
|
+
// user must have customized the nav keys so we have to move focus manually _within_
|
|
506
|
+
// the active group: do this based on the order determined by tabbable()
|
|
507
|
+
destinationNode = containerGroup.nextTabbableNode(target, false);
|
|
489
508
|
}
|
|
490
509
|
} else {
|
|
491
510
|
// FORWARD
|
|
@@ -523,33 +542,43 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
523
542
|
|
|
524
543
|
const destinationGroup = state.tabbableGroups[destinationGroupIndex];
|
|
525
544
|
destinationNode = destinationGroup.firstTabbableNode;
|
|
545
|
+
} else if (!isTabEvent(event)) {
|
|
546
|
+
// user must have customized the nav keys so we have to move focus manually _within_
|
|
547
|
+
// the active group: do this based on the order determined by tabbable()
|
|
548
|
+
destinationNode = containerGroup.nextTabbableNode(target);
|
|
526
549
|
}
|
|
527
550
|
}
|
|
528
551
|
} else {
|
|
552
|
+
// no groups available
|
|
529
553
|
// NOTE: the fallbackFocus option does not support returning false to opt-out
|
|
530
554
|
destinationNode = getNodeForOption('fallbackFocus');
|
|
531
555
|
}
|
|
532
556
|
|
|
533
557
|
if (destinationNode) {
|
|
534
|
-
|
|
558
|
+
if (isTabEvent(event)) {
|
|
559
|
+
// since tab natively moves focus, we wouldn't have a destination node unless we
|
|
560
|
+
// were on the edge of a container and had to move to the next/previous edge, in
|
|
561
|
+
// which case we want to prevent default to keep the browser from moving focus
|
|
562
|
+
// to where it normally would
|
|
563
|
+
event.preventDefault();
|
|
564
|
+
}
|
|
535
565
|
tryFocus(destinationNode);
|
|
536
566
|
}
|
|
537
567
|
// else, let the browser take care of [shift+]tab and move the focus
|
|
538
568
|
};
|
|
539
569
|
|
|
540
|
-
const checkKey = function (
|
|
570
|
+
const checkKey = function (event) {
|
|
541
571
|
if (
|
|
542
|
-
isEscapeEvent(
|
|
543
|
-
valueOrHandler(config.escapeDeactivates,
|
|
572
|
+
isEscapeEvent(event) &&
|
|
573
|
+
valueOrHandler(config.escapeDeactivates, event) !== false
|
|
544
574
|
) {
|
|
545
|
-
|
|
575
|
+
event.preventDefault();
|
|
546
576
|
trap.deactivate();
|
|
547
577
|
return;
|
|
548
578
|
}
|
|
549
579
|
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
return;
|
|
580
|
+
if (config.isKeyForward(event) || config.isKeyBackward(event)) {
|
|
581
|
+
checkKeyNav(event, config.isKeyBackward(event));
|
|
553
582
|
}
|
|
554
583
|
};
|
|
555
584
|
|
|
@@ -582,7 +611,7 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
582
611
|
}
|
|
583
612
|
|
|
584
613
|
// There can be only one listening focus trap at a time
|
|
585
|
-
activeFocusTraps.activateTrap(trap);
|
|
614
|
+
activeFocusTraps.activateTrap(trapStack, trap);
|
|
586
615
|
|
|
587
616
|
// Delay ensures that the focused element doesn't capture the event
|
|
588
617
|
// that caused the focus trap activation.
|
|
@@ -702,7 +731,7 @@ const createFocusTrap = function (elements, userOptions) {
|
|
|
702
731
|
state.active = false;
|
|
703
732
|
state.paused = false;
|
|
704
733
|
|
|
705
|
-
activeFocusTraps.deactivateTrap(trap);
|
|
734
|
+
activeFocusTraps.deactivateTrap(trapStack, trap);
|
|
706
735
|
|
|
707
736
|
const onDeactivate = getOption(options, 'onDeactivate');
|
|
708
737
|
const onPostDeactivate = getOption(options, 'onPostDeactivate');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "Trap focus within a DOM node.",
|
|
5
5
|
"main": "dist/focus-trap.js",
|
|
6
6
|
"module": "dist/focus-trap.esm.js",
|
|
@@ -63,37 +63,37 @@
|
|
|
63
63
|
},
|
|
64
64
|
"homepage": "https://github.com/focus-trap/focus-trap#readme",
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"tabbable": "^6.0.
|
|
66
|
+
"tabbable": "^6.0.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@babel/cli": "^7.
|
|
70
|
-
"@babel/core": "^7.
|
|
71
|
-
"@babel/eslint-parser": "^7.
|
|
72
|
-
"@babel/preset-env": "^7.
|
|
73
|
-
"@changesets/cli": "^2.
|
|
74
|
-
"@rollup/plugin-babel": "^
|
|
75
|
-
"@rollup/plugin-commonjs": "^
|
|
76
|
-
"@rollup/plugin-node-resolve": "^
|
|
77
|
-
"@testing-library/cypress": "^8.0.
|
|
69
|
+
"@babel/cli": "^7.19.3",
|
|
70
|
+
"@babel/core": "^7.20.5",
|
|
71
|
+
"@babel/eslint-parser": "^7.19.1",
|
|
72
|
+
"@babel/preset-env": "^7.20.2",
|
|
73
|
+
"@changesets/cli": "^2.25.2",
|
|
74
|
+
"@rollup/plugin-babel": "^6.0.3",
|
|
75
|
+
"@rollup/plugin-commonjs": "^23.0.3",
|
|
76
|
+
"@rollup/plugin-node-resolve": "^15.0.1",
|
|
77
|
+
"@testing-library/cypress": "^8.0.7",
|
|
78
78
|
"@types/jquery": "^3.5.14",
|
|
79
|
-
"all-contributors-cli": "^6.
|
|
80
|
-
"babel-loader": "^
|
|
79
|
+
"all-contributors-cli": "^6.24.0",
|
|
80
|
+
"babel-loader": "^9.1.0",
|
|
81
81
|
"cross-env": "^7.0.3",
|
|
82
|
-
"cypress": "^
|
|
82
|
+
"cypress": "^11.2.0",
|
|
83
83
|
"cypress-plugin-tab": "^1.0.5",
|
|
84
|
-
"eslint": "^8.
|
|
84
|
+
"eslint": "^8.28.0",
|
|
85
85
|
"eslint-config-prettier": "^8.5.0",
|
|
86
86
|
"eslint-plugin-cypress": "^2.12.1",
|
|
87
|
-
"eslint-plugin-jest": "^
|
|
87
|
+
"eslint-plugin-jest": "^27.1.6",
|
|
88
88
|
"onchange": "^7.1.0",
|
|
89
|
-
"prettier": "^2.
|
|
90
|
-
"rollup": "^2.
|
|
89
|
+
"prettier": "^2.8.0",
|
|
90
|
+
"rollup": "^2.79.1",
|
|
91
91
|
"rollup-plugin-inject-process-env": "^1.3.1",
|
|
92
92
|
"rollup-plugin-livereload": "^2.0.5",
|
|
93
|
-
"rollup-plugin-serve": "^2.0.
|
|
93
|
+
"rollup-plugin-serve": "^2.0.2",
|
|
94
94
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
95
95
|
"rollup-plugin-terser": "^7.0.1",
|
|
96
96
|
"start-server-and-test": "^1.14.0",
|
|
97
|
-
"typescript": "^4.
|
|
97
|
+
"typescript": "^4.9.3"
|
|
98
98
|
}
|
|
99
99
|
}
|