@y14e/roving-tabindex 3.0.1 → 3.0.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/README.md CHANGED
@@ -10,14 +10,14 @@ npm i @y14e/roving-tabindex
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import { createRovingTabIndex } from '@y14e/roving-tabindex@3.0.1';
13
+ import { createRovingTabIndex } from '@y14e/roving-tabindex@3.0.3';
14
14
 
15
15
  // CDNs
16
- import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.0.1';
16
+ import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.0.3';
17
17
  // or
18
- import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.0.1/+esm';
18
+ import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.0.3/+esm';
19
19
  // or
20
- import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.0.1';
20
+ import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.0.3';
21
21
  ```
22
22
 
23
23
  ## 📦 APIs
package/dist/index.cjs CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  // node_modules/@y14e/attributes-utils/dist/index.js
4
- var defaultParser = (value) => value.split(/\s+/);
5
- var defaultSerializer = (tokens) => tokens.join(" ");
4
+ var DEFAULT_PARSER = (value) => value.split(/\s+/);
5
+ var DEFAULT_SERIALIZER = (tokens) => tokens.join(" ");
6
6
  function addTokenToAttribute(element, attribute, token, options = {}) {
7
7
  const {
8
8
  caseInsensitive = false,
9
- parse = defaultParser,
10
- serialize = defaultSerializer
9
+ parse = DEFAULT_PARSER,
10
+ serialize = DEFAULT_SERIALIZER
11
11
  } = options;
12
12
  const value = element.getAttribute(attribute)?.trim();
13
13
  const tokens = value ? parse(value).filter(Boolean) : [];
@@ -17,11 +17,11 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
17
17
  tokens.push(token);
18
18
  element.setAttribute(attribute, serialize(tokens));
19
19
  }
20
- return;
20
+ } else {
21
+ const set = new Set(tokens);
22
+ set.add(token);
23
+ element.setAttribute(attribute, serialize([...set]));
21
24
  }
22
- const set = new Set(tokens);
23
- set.add(token);
24
- element.setAttribute(attribute, serialize([...set]));
25
25
  }
26
26
  var snapshots = /* @__PURE__ */ new WeakMap();
27
27
  function restoreAttributes(elements) {
@@ -90,21 +90,16 @@ function getFocusables(container = document.body, options = {}) {
90
90
  const elements = [];
91
91
  if (composed || include) {
92
92
  let traverse2 = function(node) {
93
- if (node instanceof Element) {
94
- if (isFocusable(node, {
95
- skipNegativeTabIndexCheck,
96
- skipVisibilityCheck
97
- }) || include?.(node)) {
98
- elements[elements.length] = node;
99
- }
93
+ if (!(node instanceof Element)) {
94
+ return;
95
+ }
96
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
97
+ elements[elements.length] = node;
100
98
  }
101
99
  const children = getComposedChildren(node);
102
100
  for (let i = 0, l = children.length; i < l; i++) {
103
101
  const child = children[i];
104
- if (!child) {
105
- continue;
106
- }
107
- traverse2(child);
102
+ child && traverse2(child);
108
103
  }
109
104
  };
110
105
  traverse2(container);
@@ -112,10 +107,7 @@ function getFocusables(container = document.body, options = {}) {
112
107
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
113
108
  for (let i = 0, l = candidates.length; i < l; i++) {
114
109
  const candidate = candidates[i];
115
- if (!(candidate instanceof Element)) {
116
- continue;
117
- }
118
- if (isFocusable(candidate, {
110
+ if (candidate && isFocusable(candidate, {
119
111
  skipNegativeTabIndexCheck,
120
112
  skipVisibilityCheck
121
113
  })) {
@@ -218,23 +210,19 @@ function normalizeRadioGroup(elements) {
218
210
  for (const group of map.values()) {
219
211
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
220
212
  }
221
- return elements.filter((element) => {
222
- if (isUngroupedRadio(element)) {
223
- return placeholder.has(element);
224
- }
225
- return true;
226
- });
213
+ return elements.filter(
214
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
215
+ );
227
216
  }
228
217
  function sortByTabIndex(elements) {
229
218
  const ordered = [];
230
219
  const natural = [];
231
220
  for (let i = 0, l = elements.length; i < l; i++) {
232
221
  const element = elements[i];
233
- if (!element) {
234
- continue;
222
+ if (element) {
223
+ const target = getTabIndex(element) > 0 ? ordered : natural;
224
+ target[target.length] = element;
235
225
  }
236
- const target = getTabIndex(element) > 0 ? ordered : natural;
237
- target[target.length] = element;
238
226
  }
239
227
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
240
228
  let count = 0;
@@ -393,11 +381,7 @@ var RovingTabIndex = class {
393
381
  return;
394
382
  }
395
383
  const isFocusable2 = this.#focusables.has(target);
396
- if (this.#options.noMemory && !isFocusable2) {
397
- this.#update(null);
398
- return;
399
- }
400
- isFocusable2 && this.#update(target);
384
+ this.#options.noMemory && !isFocusable2 ? this.#update(null) : isFocusable2 && this.#update(target);
401
385
  };
402
386
  #onKeyDown = (event) => {
403
387
  if (!event.composedPath().includes(this.#container)) {
@@ -465,15 +449,14 @@ var RovingTabIndex = class {
465
449
  #update(active) {
466
450
  const current = new Set(this.#getFocusables());
467
451
  for (const focusable of this.#focusables) {
468
- if (current.has(focusable)) {
469
- continue;
452
+ if (!current.has(focusable)) {
453
+ focusable.isConnected && restoreAttributes([focusable]);
454
+ this.#focusables.delete(focusable);
455
+ this.#focusablesByFirstChar.forEach((focusables) => {
456
+ const index = focusables.indexOf(focusable);
457
+ index >= 0 && focusables.splice(index, 1);
458
+ });
470
459
  }
471
- focusable.isConnected && restoreAttributes([focusable]);
472
- this.#focusables.delete(focusable);
473
- this.#focusablesByFirstChar.forEach((focusables) => {
474
- const index = focusables.indexOf(focusable);
475
- index >= 0 && focusables.splice(index, 1);
476
- });
477
460
  }
478
461
  const { navigationOnly, noStart, typeahead } = this.#options;
479
462
  for (const focusable of current) {
@@ -488,11 +471,11 @@ var RovingTabIndex = class {
488
471
  if (!typeahead) {
489
472
  continue;
490
473
  }
474
+ const char = focusable.textContent?.trim()?.at(0)?.toUpperCase();
491
475
  const value = focusable.ariaKeyShortcuts?.trim();
492
476
  const keys = new Set(
493
477
  value ? value.split(/\s+/).filter((key) => /^\S$/i.test(key)).map((key) => key.toUpperCase()) : []
494
478
  );
495
- const char = focusable.textContent?.trim()?.at(0)?.toUpperCase();
496
479
  if (char) {
497
480
  keys.add(char);
498
481
  saveAttributes([focusable], ["aria-keyshortcuts"]);
@@ -506,18 +489,17 @@ var RovingTabIndex = class {
506
489
  this.#focusablesByFirstChar.set(key, focusables);
507
490
  });
508
491
  }
509
- if (navigationOnly) {
510
- return;
511
- }
512
- if (active && this.#focusables.has(active)) {
513
- this.#focusables.forEach((focusable) => {
514
- focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
515
- });
516
- return;
492
+ if (!navigationOnly) {
493
+ if (active && this.#focusables.has(active)) {
494
+ this.#focusables.forEach((focusable) => {
495
+ focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
496
+ });
497
+ } else {
498
+ [...this.#focusables].forEach((focusable, i) => {
499
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
500
+ });
501
+ }
517
502
  }
518
- [...this.#focusables].forEach((focusable, i) => {
519
- focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
520
- });
521
503
  }
522
504
  #createSelectorFilter() {
523
505
  const { selector } = this.#options;
@@ -547,7 +529,7 @@ function getActiveElement() {
547
529
  * Lightweight roving tabindex utility with fully focus management.
548
530
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
549
531
  *
550
- * @version 3.0.1
532
+ * @version 3.0.3
551
533
  * @author Yusuke Kamiyamane
552
534
  * @license MIT
553
535
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -559,7 +541,7 @@ function getActiveElement() {
559
541
  (**
560
542
  * Attributes Utils
561
543
  *
562
- * @version 1.1.1
544
+ * @version 1.1.2
563
545
  * @author Yusuke Kamiyamane
564
546
  * @license MIT
565
547
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -572,7 +554,7 @@ power-focusable/dist/index.js:
572
554
  * High-precision focus management utility with full composed tree support.
573
555
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
574
556
  *
575
- * @version 4.3.2
557
+ * @version 4.3.3
576
558
  * @author Yusuke Kamiyamane
577
559
  * @license MIT
578
560
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Lightweight roving tabindex utility with fully focus management.
4
4
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
5
5
  *
6
- * @version 3.0.1
6
+ * @version 3.0.3
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Lightweight roving tabindex utility with fully focus management.
4
4
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
5
5
  *
6
- * @version 3.0.1
6
+ * @version 3.0.3
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // node_modules/@y14e/attributes-utils/dist/index.js
2
- var defaultParser = (value) => value.split(/\s+/);
3
- var defaultSerializer = (tokens) => tokens.join(" ");
2
+ var DEFAULT_PARSER = (value) => value.split(/\s+/);
3
+ var DEFAULT_SERIALIZER = (tokens) => tokens.join(" ");
4
4
  function addTokenToAttribute(element, attribute, token, options = {}) {
5
5
  const {
6
6
  caseInsensitive = false,
7
- parse = defaultParser,
8
- serialize = defaultSerializer
7
+ parse = DEFAULT_PARSER,
8
+ serialize = DEFAULT_SERIALIZER
9
9
  } = options;
10
10
  const value = element.getAttribute(attribute)?.trim();
11
11
  const tokens = value ? parse(value).filter(Boolean) : [];
@@ -15,11 +15,11 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
15
15
  tokens.push(token);
16
16
  element.setAttribute(attribute, serialize(tokens));
17
17
  }
18
- return;
18
+ } else {
19
+ const set = new Set(tokens);
20
+ set.add(token);
21
+ element.setAttribute(attribute, serialize([...set]));
19
22
  }
20
- const set = new Set(tokens);
21
- set.add(token);
22
- element.setAttribute(attribute, serialize([...set]));
23
23
  }
24
24
  var snapshots = /* @__PURE__ */ new WeakMap();
25
25
  function restoreAttributes(elements) {
@@ -88,21 +88,16 @@ function getFocusables(container = document.body, options = {}) {
88
88
  const elements = [];
89
89
  if (composed || include) {
90
90
  let traverse2 = function(node) {
91
- if (node instanceof Element) {
92
- if (isFocusable(node, {
93
- skipNegativeTabIndexCheck,
94
- skipVisibilityCheck
95
- }) || include?.(node)) {
96
- elements[elements.length] = node;
97
- }
91
+ if (!(node instanceof Element)) {
92
+ return;
93
+ }
94
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
95
+ elements[elements.length] = node;
98
96
  }
99
97
  const children = getComposedChildren(node);
100
98
  for (let i = 0, l = children.length; i < l; i++) {
101
99
  const child = children[i];
102
- if (!child) {
103
- continue;
104
- }
105
- traverse2(child);
100
+ child && traverse2(child);
106
101
  }
107
102
  };
108
103
  traverse2(container);
@@ -110,10 +105,7 @@ function getFocusables(container = document.body, options = {}) {
110
105
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
111
106
  for (let i = 0, l = candidates.length; i < l; i++) {
112
107
  const candidate = candidates[i];
113
- if (!(candidate instanceof Element)) {
114
- continue;
115
- }
116
- if (isFocusable(candidate, {
108
+ if (candidate && isFocusable(candidate, {
117
109
  skipNegativeTabIndexCheck,
118
110
  skipVisibilityCheck
119
111
  })) {
@@ -216,23 +208,19 @@ function normalizeRadioGroup(elements) {
216
208
  for (const group of map.values()) {
217
209
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
218
210
  }
219
- return elements.filter((element) => {
220
- if (isUngroupedRadio(element)) {
221
- return placeholder.has(element);
222
- }
223
- return true;
224
- });
211
+ return elements.filter(
212
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
213
+ );
225
214
  }
226
215
  function sortByTabIndex(elements) {
227
216
  const ordered = [];
228
217
  const natural = [];
229
218
  for (let i = 0, l = elements.length; i < l; i++) {
230
219
  const element = elements[i];
231
- if (!element) {
232
- continue;
220
+ if (element) {
221
+ const target = getTabIndex(element) > 0 ? ordered : natural;
222
+ target[target.length] = element;
233
223
  }
234
- const target = getTabIndex(element) > 0 ? ordered : natural;
235
- target[target.length] = element;
236
224
  }
237
225
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
238
226
  let count = 0;
@@ -391,11 +379,7 @@ var RovingTabIndex = class {
391
379
  return;
392
380
  }
393
381
  const isFocusable2 = this.#focusables.has(target);
394
- if (this.#options.noMemory && !isFocusable2) {
395
- this.#update(null);
396
- return;
397
- }
398
- isFocusable2 && this.#update(target);
382
+ this.#options.noMemory && !isFocusable2 ? this.#update(null) : isFocusable2 && this.#update(target);
399
383
  };
400
384
  #onKeyDown = (event) => {
401
385
  if (!event.composedPath().includes(this.#container)) {
@@ -463,15 +447,14 @@ var RovingTabIndex = class {
463
447
  #update(active) {
464
448
  const current = new Set(this.#getFocusables());
465
449
  for (const focusable of this.#focusables) {
466
- if (current.has(focusable)) {
467
- continue;
450
+ if (!current.has(focusable)) {
451
+ focusable.isConnected && restoreAttributes([focusable]);
452
+ this.#focusables.delete(focusable);
453
+ this.#focusablesByFirstChar.forEach((focusables) => {
454
+ const index = focusables.indexOf(focusable);
455
+ index >= 0 && focusables.splice(index, 1);
456
+ });
468
457
  }
469
- focusable.isConnected && restoreAttributes([focusable]);
470
- this.#focusables.delete(focusable);
471
- this.#focusablesByFirstChar.forEach((focusables) => {
472
- const index = focusables.indexOf(focusable);
473
- index >= 0 && focusables.splice(index, 1);
474
- });
475
458
  }
476
459
  const { navigationOnly, noStart, typeahead } = this.#options;
477
460
  for (const focusable of current) {
@@ -486,11 +469,11 @@ var RovingTabIndex = class {
486
469
  if (!typeahead) {
487
470
  continue;
488
471
  }
472
+ const char = focusable.textContent?.trim()?.at(0)?.toUpperCase();
489
473
  const value = focusable.ariaKeyShortcuts?.trim();
490
474
  const keys = new Set(
491
475
  value ? value.split(/\s+/).filter((key) => /^\S$/i.test(key)).map((key) => key.toUpperCase()) : []
492
476
  );
493
- const char = focusable.textContent?.trim()?.at(0)?.toUpperCase();
494
477
  if (char) {
495
478
  keys.add(char);
496
479
  saveAttributes([focusable], ["aria-keyshortcuts"]);
@@ -504,18 +487,17 @@ var RovingTabIndex = class {
504
487
  this.#focusablesByFirstChar.set(key, focusables);
505
488
  });
506
489
  }
507
- if (navigationOnly) {
508
- return;
509
- }
510
- if (active && this.#focusables.has(active)) {
511
- this.#focusables.forEach((focusable) => {
512
- focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
513
- });
514
- return;
490
+ if (!navigationOnly) {
491
+ if (active && this.#focusables.has(active)) {
492
+ this.#focusables.forEach((focusable) => {
493
+ focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
494
+ });
495
+ } else {
496
+ [...this.#focusables].forEach((focusable, i) => {
497
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
498
+ });
499
+ }
515
500
  }
516
- [...this.#focusables].forEach((focusable, i) => {
517
- focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
518
- });
519
501
  }
520
502
  #createSelectorFilter() {
521
503
  const { selector } = this.#options;
@@ -545,7 +527,7 @@ function getActiveElement() {
545
527
  * Lightweight roving tabindex utility with fully focus management.
546
528
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
547
529
  *
548
- * @version 3.0.1
530
+ * @version 3.0.3
549
531
  * @author Yusuke Kamiyamane
550
532
  * @license MIT
551
533
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -557,7 +539,7 @@ function getActiveElement() {
557
539
  (**
558
540
  * Attributes Utils
559
541
  *
560
- * @version 1.1.1
542
+ * @version 1.1.2
561
543
  * @author Yusuke Kamiyamane
562
544
  * @license MIT
563
545
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -570,7 +552,7 @@ power-focusable/dist/index.js:
570
552
  * High-precision focus management utility with full composed tree support.
571
553
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
572
554
  *
573
- * @version 4.3.2
555
+ * @version 4.3.3
574
556
  * @author Yusuke Kamiyamane
575
557
  * @license MIT
576
558
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/roving-tabindex",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Lightweight roving tabindex utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "homepage": "https://github.com/y14e/roving-tabindex#readme",
50
50
  "devDependencies": {
51
- "@y14e/attributes-utils": "^1.1.1",
51
+ "@y14e/attributes-utils": "^1.1.2",
52
52
  "bun-types": "latest",
53
- "power-focusable": "^4.3.2",
53
+ "power-focusable": "^4.3.3",
54
54
  "tsup": "^8.0.0",
55
55
  "typescript": "^5.6.0"
56
56
  },