focus-trap 7.1.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/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { tabbable, focusable, isFocusable, isTabbable } from 'tabbable';
2
2
 
3
- const rooTrapStack = [];
4
-
5
3
  const activeFocusTraps = {
6
4
  activateTrap(trapStack, trap) {
7
5
  if (trapStack.length > 0) {
@@ -49,6 +47,16 @@ const isTabEvent = function (e) {
49
47
  return e.key === 'Tab' || e.keyCode === 9;
50
48
  };
51
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
+
52
60
  const delay = function (fn) {
53
61
  return setTimeout(fn, 0);
54
62
  };
@@ -94,17 +102,23 @@ const getActualTarget = function (event) {
94
102
  : event.target;
95
103
  };
96
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
+
97
109
  const createFocusTrap = function (elements, userOptions) {
98
110
  // SSR: a live trap shouldn't be created in this type of environment so this
99
111
  // should be safe code to execute if the `document` option isn't specified
100
112
  const doc = userOptions?.document || document;
101
113
 
102
- const trapStack = userOptions?.trapStack || rooTrapStack;
114
+ const trapStack = userOptions?.trapStack || internalTrapStack;
103
115
 
104
116
  const config = {
105
117
  returnFocusOnDeactivate: true,
106
118
  escapeDeactivates: true,
107
119
  delayInitialFocus: true,
120
+ isKeyForward,
121
+ isKeyBackward,
108
122
  ...userOptions,
109
123
  };
110
124
 
@@ -421,12 +435,12 @@ const createFocusTrap = function (elements, userOptions) {
421
435
  }
422
436
  };
423
437
 
424
- // Hijack Tab events on the first and last focusable nodes of the trap,
438
+ // Hijack key nav events on the first and last focusable nodes of the trap,
425
439
  // in order to prevent focus from escaping. If it escapes for even a
426
440
  // moment it can end up scrolling the page and causing confusion so we
427
441
  // kind of need to capture the action at the keydown phase.
428
- const checkTab = function (e) {
429
- const target = getActualTarget(e);
442
+ const checkKeyNav = function (event, isBackward = false) {
443
+ const target = getActualTarget(event);
430
444
  updateTabbableNodes();
431
445
 
432
446
  let destinationNode = null;
@@ -441,8 +455,8 @@ const createFocusTrap = function (elements, userOptions) {
441
455
 
442
456
  if (containerIndex < 0) {
443
457
  // target not found in any group: quite possible focus has escaped the trap,
444
- // so bring it back in to...
445
- if (e.shiftKey) {
458
+ // so bring it back into...
459
+ if (isBackward) {
446
460
  // ...the last node in the last group
447
461
  destinationNode =
448
462
  state.tabbableGroups[state.tabbableGroups.length - 1]
@@ -451,7 +465,7 @@ const createFocusTrap = function (elements, userOptions) {
451
465
  // ...the first node in the first group
452
466
  destinationNode = state.tabbableGroups[0].firstTabbableNode;
453
467
  }
454
- } else if (e.shiftKey) {
468
+ } else if (isBackward) {
455
469
  // REVERSE
456
470
 
457
471
  // is the target the first tabbable node in a group?
@@ -487,6 +501,10 @@ const createFocusTrap = function (elements, userOptions) {
487
501
 
488
502
  const destinationGroup = state.tabbableGroups[destinationGroupIndex];
489
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);
490
508
  }
491
509
  } else {
492
510
  // FORWARD
@@ -524,33 +542,43 @@ const createFocusTrap = function (elements, userOptions) {
524
542
 
525
543
  const destinationGroup = state.tabbableGroups[destinationGroupIndex];
526
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);
527
549
  }
528
550
  }
529
551
  } else {
552
+ // no groups available
530
553
  // NOTE: the fallbackFocus option does not support returning false to opt-out
531
554
  destinationNode = getNodeForOption('fallbackFocus');
532
555
  }
533
556
 
534
557
  if (destinationNode) {
535
- e.preventDefault();
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
+ }
536
565
  tryFocus(destinationNode);
537
566
  }
538
567
  // else, let the browser take care of [shift+]tab and move the focus
539
568
  };
540
569
 
541
- const checkKey = function (e) {
570
+ const checkKey = function (event) {
542
571
  if (
543
- isEscapeEvent(e) &&
544
- valueOrHandler(config.escapeDeactivates, e) !== false
572
+ isEscapeEvent(event) &&
573
+ valueOrHandler(config.escapeDeactivates, event) !== false
545
574
  ) {
546
- e.preventDefault();
575
+ event.preventDefault();
547
576
  trap.deactivate();
548
577
  return;
549
578
  }
550
579
 
551
- if (isTabEvent(e)) {
552
- checkTab(e);
553
- return;
580
+ if (config.isKeyForward(event) || config.isKeyBackward(event)) {
581
+ checkKeyNav(event, config.isKeyBackward(event));
554
582
  }
555
583
  };
556
584
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "focus-trap",
3
- "version": "7.1.0",
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",
@@ -67,33 +67,33 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@babel/cli": "^7.19.3",
70
- "@babel/core": "^7.20.2",
70
+ "@babel/core": "^7.20.5",
71
71
  "@babel/eslint-parser": "^7.19.1",
72
72
  "@babel/preset-env": "^7.20.2",
73
73
  "@changesets/cli": "^2.25.2",
74
- "@rollup/plugin-babel": "^6.0.2",
75
- "@rollup/plugin-commonjs": "^23.0.2",
74
+ "@rollup/plugin-babel": "^6.0.3",
75
+ "@rollup/plugin-commonjs": "^23.0.3",
76
76
  "@rollup/plugin-node-resolve": "^15.0.1",
77
- "@testing-library/cypress": "^8.0.3",
77
+ "@testing-library/cypress": "^8.0.7",
78
78
  "@types/jquery": "^3.5.14",
79
79
  "all-contributors-cli": "^6.24.0",
80
80
  "babel-loader": "^9.1.0",
81
81
  "cross-env": "^7.0.3",
82
- "cypress": "^10.11.0",
82
+ "cypress": "^11.2.0",
83
83
  "cypress-plugin-tab": "^1.0.5",
84
- "eslint": "^8.27.0",
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": "^27.1.4",
87
+ "eslint-plugin-jest": "^27.1.6",
88
88
  "onchange": "^7.1.0",
89
- "prettier": "^2.7.1",
89
+ "prettier": "^2.8.0",
90
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.1",
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.8.4"
97
+ "typescript": "^4.9.3"
98
98
  }
99
99
  }