@y14e/roving-tabindex 3.1.12 → 3.1.14

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
@@ -13,11 +13,11 @@ npm i @y14e/roving-tabindex
13
13
  import { createRovingTabIndex } from '@y14e/roving-tabindex';
14
14
 
15
15
  // CDNs
16
- import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.1.12';
16
+ import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.1.14';
17
17
  // or
18
- import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.1.12/+esm';
18
+ import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.1.14/+esm';
19
19
  // or
20
- import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.1.12';
20
+ import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.1.14';
21
21
  ```
22
22
 
23
23
  ## 📦 APIs
@@ -51,7 +51,7 @@ function saveAttributes(elements, attributes) {
51
51
 
52
52
  // node_modules/power-focusable/dist/index.js
53
53
  var FOCUSABLE_SELECTOR = `:is(a[href], area[href], button, embed, iframe, input:not([type="hidden" i]), object, select, details > summary:first-of-type, textarea, [contenteditable]:not([contenteditable="false" i]), [controls], [tabindex]):not(:disabled, [hidden], [inert], [tabindex="-1"])`;
54
- var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX = FOCUSABLE_SELECTOR.replace(
54
+ var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX = FOCUSABLE_SELECTOR.replace(
55
55
  /(,\s*)?\[tabindex="-1"\]/g,
56
56
  ""
57
57
  );
@@ -113,7 +113,7 @@ function getFocusables(container = document.body, options = {}) {
113
113
  }
114
114
  } else {
115
115
  const candidates = container.querySelectorAll(
116
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
116
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
117
117
  );
118
118
  for (let i = 0, l = candidates.length; i < l; i++) {
119
119
  const candidate = candidates[i];
@@ -149,7 +149,7 @@ function isFocusable(element, options = {}) {
149
149
  return false;
150
150
  }
151
151
  if (!element.matches(
152
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
152
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
153
153
  )) {
154
154
  return false;
155
155
  }
@@ -195,7 +195,7 @@ function isDisabledDeep(element) {
195
195
  return false;
196
196
  }
197
197
  function normalizeRadioGroup(elements) {
198
- let rootMap = null;
198
+ let map = null;
199
199
  for (let i = 0, l = elements.length; i < l; i++) {
200
200
  const element = elements[i];
201
201
  if (!(element instanceof HTMLInputElement)) {
@@ -204,35 +204,35 @@ function normalizeRadioGroup(elements) {
204
204
  if (!isUngroupedRadio(element)) {
205
205
  continue;
206
206
  }
207
- if (!rootMap) {
208
- rootMap = /* @__PURE__ */ new Map();
207
+ if (!map) {
208
+ map = /* @__PURE__ */ new Map();
209
209
  }
210
210
  const root = element.getRootNode();
211
- let formMap = rootMap.get(root);
212
- if (!formMap) {
213
- formMap = /* @__PURE__ */ new Map();
214
- rootMap.set(root, formMap);
211
+ let value = map.get(root);
212
+ if (!value) {
213
+ value = /* @__PURE__ */ new Map();
214
+ map.set(root, value);
215
215
  }
216
- let nameMap = formMap.get(element.form);
217
- if (!nameMap) {
218
- nameMap = /* @__PURE__ */ new Map();
219
- formMap.set(element.form, nameMap);
216
+ let v = value.get(element.form);
217
+ if (!v) {
218
+ v = /* @__PURE__ */ new Map();
219
+ value.set(element.form, v);
220
220
  }
221
- let group = nameMap.get(element.name);
222
- if (!group) {
223
- group = [];
224
- nameMap.set(element.name, group);
221
+ let vv = v.get(element.name);
222
+ if (!vv) {
223
+ vv = [];
224
+ v.set(element.name, vv);
225
225
  }
226
- group[group.length] = element;
226
+ vv[vv.length] = element;
227
227
  }
228
- if (!rootMap) {
228
+ if (!map) {
229
229
  return elements;
230
230
  }
231
231
  const placeholder = /* @__PURE__ */ new Set();
232
- for (const formMap of rootMap.values()) {
233
- for (const nameMap of formMap.values()) {
234
- for (const group of nameMap.values()) {
235
- const radio = group.find((element) => element.checked) ?? group[0];
232
+ for (const value of map.values()) {
233
+ for (const v of value.values()) {
234
+ for (const vv of v.values()) {
235
+ const radio = vv.find((element) => element.checked) ?? vv[0];
236
236
  radio && placeholder.add(radio);
237
237
  }
238
238
  }
@@ -324,14 +324,8 @@ function createRovingTabIndex(container, options = {}) {
324
324
  return () => {
325
325
  };
326
326
  }
327
- try {
328
- const roving = new RovingTabIndex(container, options);
329
- return () => roving.destroy();
330
- } catch (error) {
331
- error instanceof Error && console.warn(error.message || error);
332
- return () => {
333
- };
334
- }
327
+ const roving = new RovingTabIndex(container, options);
328
+ return () => roving.destroy();
335
329
  }
336
330
  var RovingTabIndex = class _RovingTabIndex {
337
331
  static #initialized = /* @__PURE__ */ new Set();
@@ -369,9 +363,18 @@ var RovingTabIndex = class _RovingTabIndex {
369
363
  console.warn("Invalid noStart option. Fallback: false.");
370
364
  noStart = false;
371
365
  }
372
- if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
373
- console.warn("Invalid selector. Fallback: no selector string.");
374
- selector = "";
366
+ if (selector !== "") {
367
+ if (typeof selector !== "string" || !selector.trim()) {
368
+ console.warn("Invalid selector. Fallback: no selector string.");
369
+ selector = "";
370
+ } else {
371
+ try {
372
+ container.querySelector(selector);
373
+ } catch {
374
+ console.warn("Invalid selector. Fallback: no selector string.");
375
+ selector = "";
376
+ }
377
+ }
375
378
  }
376
379
  if (typeof typeahead !== "boolean") {
377
380
  console.warn("Invalid typeahead option. Fallback: false.");
@@ -400,7 +403,10 @@ var RovingTabIndex = class _RovingTabIndex {
400
403
  this.#isDestroyed = true;
401
404
  this.#controller?.abort();
402
405
  this.#controller = null;
403
- restoreAttributes([...this.#focusables]);
406
+ this.#focusables.forEach((focusable) => {
407
+ _RovingTabIndex.#initialized.delete(focusable);
408
+ restoreAttributes([focusable]);
409
+ });
404
410
  this.#focusables.clear();
405
411
  this.#focusablesByFirstChar.clear();
406
412
  }
@@ -453,14 +459,16 @@ var RovingTabIndex = class _RovingTabIndex {
453
459
  if (!(active instanceof Element)) {
454
460
  return;
455
461
  }
456
- const current = this.#getFocusables();
457
- if (!current.includes(active)) {
462
+ const candidates = this.#getFocusables().filter(
463
+ (focusable2) => this.#focusables.has(focusable2)
464
+ );
465
+ if (!candidates.includes(active)) {
458
466
  return;
459
467
  }
460
468
  event.preventDefault();
461
- const currentIndex = current.indexOf(active);
469
+ const activeIndex = candidates.indexOf(active);
462
470
  let newIndex;
463
- let target = current;
471
+ let target = candidates;
464
472
  switch (key) {
465
473
  case "End":
466
474
  newIndex = -1;
@@ -470,46 +478,53 @@ var RovingTabIndex = class _RovingTabIndex {
470
478
  break;
471
479
  case "ArrowLeft":
472
480
  case "ArrowUp": {
473
- const rawIndex = currentIndex - 1;
481
+ const rawIndex = activeIndex - 1;
474
482
  newIndex = wrap ? rawIndex : Math.max(rawIndex, 0);
475
483
  break;
476
484
  }
477
485
  case "ArrowRight":
478
486
  case "ArrowDown": {
479
- const rawIndex = currentIndex + 1;
480
- newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
487
+ const rawIndex = activeIndex + 1;
488
+ newIndex = wrap ? rawIndex % candidates.length : Math.min(rawIndex, candidates.length - 1);
481
489
  break;
482
490
  }
483
491
  default: {
484
- target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
485
- const foundIndex = target.findIndex(
486
- (focusable2) => current.indexOf(focusable2) > currentIndex
492
+ const focusables = new Set(
493
+ this.#focusablesByFirstChar.get(key.toUpperCase()) ?? []
494
+ );
495
+ target = candidates.filter((focusable2) => focusables.has(focusable2));
496
+ const afterIndex = target.findIndex(
497
+ (focusable2) => candidates.indexOf(focusable2) > activeIndex
487
498
  );
488
- newIndex = foundIndex >= 0 ? foundIndex : 0;
499
+ newIndex = afterIndex >= 0 ? afterIndex : 0;
489
500
  }
490
501
  }
491
502
  const focusable = target.at(newIndex);
492
503
  focusable && focusElement(focusable);
493
504
  };
494
505
  #update(active) {
495
- const current = new Set(this.#getFocusables());
506
+ const focusables = new Set(this.#getFocusables());
496
507
  for (const focusable of this.#focusables) {
497
- if (!current.has(focusable)) {
498
- focusable.isConnected && restoreAttributes([focusable]);
508
+ if (!focusables.has(focusable)) {
509
+ _RovingTabIndex.#initialized.delete(focusable);
510
+ restoreAttributes([focusable]);
499
511
  this.#focusables.delete(focusable);
500
- this.#focusablesByFirstChar.forEach((focusables) => {
501
- const index = focusables.indexOf(focusable);
502
- index >= 0 && focusables.splice(index, 1);
503
- });
512
+ for (const [key, focusables2] of this.#focusablesByFirstChar) {
513
+ const index = focusables2.indexOf(focusable);
514
+ if (index >= 0) {
515
+ focusables2.splice(index, 1);
516
+ !focusables2.length && this.#focusablesByFirstChar.delete(key);
517
+ }
518
+ }
504
519
  }
505
520
  }
506
521
  const { navigationOnly, noStart, typeahead } = this.#settings;
507
- for (const focusable of current) {
522
+ for (const focusable of focusables) {
508
523
  if (this.#focusables.has(focusable)) {
509
524
  continue;
510
525
  }
511
526
  if (_RovingTabIndex.#initialized.has(focusable)) {
512
- throw new TypeError("Already initialized");
527
+ continue;
513
528
  }
514
529
  this.#focusables.add(focusable);
515
530
  _RovingTabIndex.#initialized.add(focusable);
@@ -533,9 +548,9 @@ var RovingTabIndex = class _RovingTabIndex {
533
548
  });
534
549
  }
535
550
  keys.forEach((key) => {
536
- const focusables = this.#focusablesByFirstChar.get(key) ?? [];
537
- focusables.push(focusable);
538
- this.#focusablesByFirstChar.set(key, focusables);
551
+ const focusables2 = this.#focusablesByFirstChar.get(key) ?? [];
552
+ focusables2.push(focusable);
553
+ this.#focusablesByFirstChar.set(key, focusables2);
539
554
  });
540
555
  }
541
556
  if (!navigationOnly) {
@@ -552,7 +567,7 @@ var RovingTabIndex = class _RovingTabIndex {
552
567
  }
553
568
  #createSelectorFilter() {
554
569
  const { selector } = this.#settings;
555
- return (element) => !selector || [...this.#container.querySelectorAll(selector)].includes(element);
570
+ return (element) => !selector || element.matches(selector);
556
571
  }
557
572
  #getFocusables() {
558
573
  return getFocusables(this.#container, {
@@ -568,7 +583,7 @@ var RovingTabIndex = class _RovingTabIndex {
568
583
  * Lightweight roving tabindex utility with fully focus management.
569
584
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
570
585
  *
571
- * @version 3.1.12
586
+ * @version 3.1.14
572
587
  * @author Yusuke Kamiyamane
573
588
  * @license MIT
574
589
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -593,7 +608,7 @@ power-focusable/dist/index.js:
593
608
  * High-precision focus management utility with full composed tree support.
594
609
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
595
610
  *
596
- * @version 4.3.12
611
+ * @version 4.3.18
597
612
  * @author Yusuke Kamiyamane
598
613
  * @license MIT
599
614
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -49,7 +49,7 @@ function saveAttributes(elements, attributes) {
49
49
 
50
50
  // node_modules/power-focusable/dist/index.js
51
51
  var FOCUSABLE_SELECTOR = `:is(a[href], area[href], button, embed, iframe, input:not([type="hidden" i]), object, select, details > summary:first-of-type, textarea, [contenteditable]:not([contenteditable="false" i]), [controls], [tabindex]):not(:disabled, [hidden], [inert], [tabindex="-1"])`;
52
- var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX = FOCUSABLE_SELECTOR.replace(
52
+ var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX = FOCUSABLE_SELECTOR.replace(
53
53
  /(,\s*)?\[tabindex="-1"\]/g,
54
54
  ""
55
55
  );
@@ -111,7 +111,7 @@ function getFocusables(container = document.body, options = {}) {
111
111
  }
112
112
  } else {
113
113
  const candidates = container.querySelectorAll(
114
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
114
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
115
115
  );
116
116
  for (let i = 0, l = candidates.length; i < l; i++) {
117
117
  const candidate = candidates[i];
@@ -147,7 +147,7 @@ function isFocusable(element, options = {}) {
147
147
  return false;
148
148
  }
149
149
  if (!element.matches(
150
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
150
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
151
151
  )) {
152
152
  return false;
153
153
  }
@@ -193,7 +193,7 @@ function isDisabledDeep(element) {
193
193
  return false;
194
194
  }
195
195
  function normalizeRadioGroup(elements) {
196
- let rootMap = null;
196
+ let map = null;
197
197
  for (let i = 0, l = elements.length; i < l; i++) {
198
198
  const element = elements[i];
199
199
  if (!(element instanceof HTMLInputElement)) {
@@ -202,35 +202,35 @@ function normalizeRadioGroup(elements) {
202
202
  if (!isUngroupedRadio(element)) {
203
203
  continue;
204
204
  }
205
- if (!rootMap) {
206
- rootMap = /* @__PURE__ */ new Map();
205
+ if (!map) {
206
+ map = /* @__PURE__ */ new Map();
207
207
  }
208
208
  const root = element.getRootNode();
209
- let formMap = rootMap.get(root);
210
- if (!formMap) {
211
- formMap = /* @__PURE__ */ new Map();
212
- rootMap.set(root, formMap);
209
+ let value = map.get(root);
210
+ if (!value) {
211
+ value = /* @__PURE__ */ new Map();
212
+ map.set(root, value);
213
213
  }
214
- let nameMap = formMap.get(element.form);
215
- if (!nameMap) {
216
- nameMap = /* @__PURE__ */ new Map();
217
- formMap.set(element.form, nameMap);
214
+ let v = value.get(element.form);
215
+ if (!v) {
216
+ v = /* @__PURE__ */ new Map();
217
+ value.set(element.form, v);
218
218
  }
219
- let group = nameMap.get(element.name);
220
- if (!group) {
221
- group = [];
222
- nameMap.set(element.name, group);
219
+ let vv = v.get(element.name);
220
+ if (!vv) {
221
+ vv = [];
222
+ v.set(element.name, vv);
223
223
  }
224
- group[group.length] = element;
224
+ vv[vv.length] = element;
225
225
  }
226
- if (!rootMap) {
226
+ if (!map) {
227
227
  return elements;
228
228
  }
229
229
  const placeholder = /* @__PURE__ */ new Set();
230
- for (const formMap of rootMap.values()) {
231
- for (const nameMap of formMap.values()) {
232
- for (const group of nameMap.values()) {
233
- const radio = group.find((element) => element.checked) ?? group[0];
230
+ for (const value of map.values()) {
231
+ for (const v of value.values()) {
232
+ for (const vv of v.values()) {
233
+ const radio = vv.find((element) => element.checked) ?? vv[0];
234
234
  radio && placeholder.add(radio);
235
235
  }
236
236
  }
@@ -322,14 +322,8 @@ function createRovingTabIndex(container, options = {}) {
322
322
  return () => {
323
323
  };
324
324
  }
325
- try {
326
- const roving = new RovingTabIndex(container, options);
327
- return () => roving.destroy();
328
- } catch (error) {
329
- error instanceof Error && console.warn(error.message || error);
330
- return () => {
331
- };
332
- }
325
+ const roving = new RovingTabIndex(container, options);
326
+ return () => roving.destroy();
333
327
  }
334
328
  var RovingTabIndex = class _RovingTabIndex {
335
329
  static #initialized = /* @__PURE__ */ new Set();
@@ -367,9 +361,18 @@ var RovingTabIndex = class _RovingTabIndex {
367
361
  console.warn("Invalid noStart option. Fallback: false.");
368
362
  noStart = false;
369
363
  }
370
- if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
371
- console.warn("Invalid selector. Fallback: no selector string.");
372
- selector = "";
364
+ if (selector !== "") {
365
+ if (typeof selector !== "string" || !selector.trim()) {
366
+ console.warn("Invalid selector. Fallback: no selector string.");
367
+ selector = "";
368
+ } else {
369
+ try {
370
+ container.querySelector(selector);
371
+ } catch {
372
+ console.warn("Invalid selector. Fallback: no selector string.");
373
+ selector = "";
374
+ }
375
+ }
373
376
  }
374
377
  if (typeof typeahead !== "boolean") {
375
378
  console.warn("Invalid typeahead option. Fallback: false.");
@@ -398,7 +401,10 @@ var RovingTabIndex = class _RovingTabIndex {
398
401
  this.#isDestroyed = true;
399
402
  this.#controller?.abort();
400
403
  this.#controller = null;
401
- restoreAttributes([...this.#focusables]);
404
+ this.#focusables.forEach((focusable) => {
405
+ _RovingTabIndex.#initialized.delete(focusable);
406
+ restoreAttributes([focusable]);
407
+ });
402
408
  this.#focusables.clear();
403
409
  this.#focusablesByFirstChar.clear();
404
410
  }
@@ -451,14 +457,16 @@ var RovingTabIndex = class _RovingTabIndex {
451
457
  if (!(active instanceof Element)) {
452
458
  return;
453
459
  }
454
- const current = this.#getFocusables();
455
- if (!current.includes(active)) {
460
+ const candidates = this.#getFocusables().filter(
461
+ (focusable2) => this.#focusables.has(focusable2)
462
+ );
463
+ if (!candidates.includes(active)) {
456
464
  return;
457
465
  }
458
466
  event.preventDefault();
459
- const currentIndex = current.indexOf(active);
467
+ const activeIndex = candidates.indexOf(active);
460
468
  let newIndex;
461
- let target = current;
469
+ let target = candidates;
462
470
  switch (key) {
463
471
  case "End":
464
472
  newIndex = -1;
@@ -468,46 +476,53 @@ var RovingTabIndex = class _RovingTabIndex {
468
476
  break;
469
477
  case "ArrowLeft":
470
478
  case "ArrowUp": {
471
- const rawIndex = currentIndex - 1;
479
+ const rawIndex = activeIndex - 1;
472
480
  newIndex = wrap ? rawIndex : Math.max(rawIndex, 0);
473
481
  break;
474
482
  }
475
483
  case "ArrowRight":
476
484
  case "ArrowDown": {
477
- const rawIndex = currentIndex + 1;
478
- newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
485
+ const rawIndex = activeIndex + 1;
486
+ newIndex = wrap ? rawIndex % candidates.length : Math.min(rawIndex, candidates.length - 1);
479
487
  break;
480
488
  }
481
489
  default: {
482
- target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
483
- const foundIndex = target.findIndex(
484
- (focusable2) => current.indexOf(focusable2) > currentIndex
490
+ const focusables = new Set(
491
+ this.#focusablesByFirstChar.get(key.toUpperCase()) ?? []
492
+ );
493
+ target = candidates.filter((focusable2) => focusables.has(focusable2));
494
+ const afterIndex = target.findIndex(
495
+ (focusable2) => candidates.indexOf(focusable2) > activeIndex
485
496
  );
486
- newIndex = foundIndex >= 0 ? foundIndex : 0;
497
+ newIndex = afterIndex >= 0 ? afterIndex : 0;
487
498
  }
488
499
  }
489
500
  const focusable = target.at(newIndex);
490
501
  focusable && focusElement(focusable);
491
502
  };
492
503
  #update(active) {
493
- const current = new Set(this.#getFocusables());
504
+ const focusables = new Set(this.#getFocusables());
494
505
  for (const focusable of this.#focusables) {
495
- if (!current.has(focusable)) {
496
- focusable.isConnected && restoreAttributes([focusable]);
506
+ if (!focusables.has(focusable)) {
507
+ _RovingTabIndex.#initialized.delete(focusable);
508
+ restoreAttributes([focusable]);
497
509
  this.#focusables.delete(focusable);
498
- this.#focusablesByFirstChar.forEach((focusables) => {
499
- const index = focusables.indexOf(focusable);
500
- index >= 0 && focusables.splice(index, 1);
501
- });
510
+ for (const [key, focusables2] of this.#focusablesByFirstChar) {
511
+ const index = focusables2.indexOf(focusable);
512
+ if (index >= 0) {
513
+ focusables2.splice(index, 1);
514
+ !focusables2.length && this.#focusablesByFirstChar.delete(key);
515
+ }
516
+ }
502
517
  }
503
518
  }
504
519
  const { navigationOnly, noStart, typeahead } = this.#settings;
505
- for (const focusable of current) {
520
+ for (const focusable of focusables) {
506
521
  if (this.#focusables.has(focusable)) {
507
522
  continue;
508
523
  }
509
524
  if (_RovingTabIndex.#initialized.has(focusable)) {
510
- throw new TypeError("Already initialized");
525
+ continue;
511
526
  }
512
527
  this.#focusables.add(focusable);
513
528
  _RovingTabIndex.#initialized.add(focusable);
@@ -531,9 +546,9 @@ var RovingTabIndex = class _RovingTabIndex {
531
546
  });
532
547
  }
533
548
  keys.forEach((key) => {
534
- const focusables = this.#focusablesByFirstChar.get(key) ?? [];
535
- focusables.push(focusable);
536
- this.#focusablesByFirstChar.set(key, focusables);
549
+ const focusables2 = this.#focusablesByFirstChar.get(key) ?? [];
550
+ focusables2.push(focusable);
551
+ this.#focusablesByFirstChar.set(key, focusables2);
537
552
  });
538
553
  }
539
554
  if (!navigationOnly) {
@@ -550,7 +565,7 @@ var RovingTabIndex = class _RovingTabIndex {
550
565
  }
551
566
  #createSelectorFilter() {
552
567
  const { selector } = this.#settings;
553
- return (element) => !selector || [...this.#container.querySelectorAll(selector)].includes(element);
568
+ return (element) => !selector || element.matches(selector);
554
569
  }
555
570
  #getFocusables() {
556
571
  return getFocusables(this.#container, {
@@ -566,7 +581,7 @@ var RovingTabIndex = class _RovingTabIndex {
566
581
  * Lightweight roving tabindex utility with fully focus management.
567
582
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
568
583
  *
569
- * @version 3.1.12
584
+ * @version 3.1.14
570
585
  * @author Yusuke Kamiyamane
571
586
  * @license MIT
572
587
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -591,7 +606,7 @@ power-focusable/dist/index.js:
591
606
  * High-precision focus management utility with full composed tree support.
592
607
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
593
608
  *
594
- * @version 4.3.12
609
+ * @version 4.3.18
595
610
  * @author Yusuke Kamiyamane
596
611
  * @license MIT
597
612
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.cjs CHANGED
@@ -10,14 +10,8 @@ function createRovingTabIndex(container, options = {}) {
10
10
  return () => {
11
11
  };
12
12
  }
13
- try {
14
- const roving = new RovingTabIndex(container, options);
15
- return () => roving.destroy();
16
- } catch (error) {
17
- error instanceof Error && console.warn(error.message || error);
18
- return () => {
19
- };
20
- }
13
+ const roving = new RovingTabIndex(container, options);
14
+ return () => roving.destroy();
21
15
  }
22
16
  var RovingTabIndex = class _RovingTabIndex {
23
17
  static #initialized = /* @__PURE__ */ new Set();
@@ -55,9 +49,18 @@ var RovingTabIndex = class _RovingTabIndex {
55
49
  console.warn("Invalid noStart option. Fallback: false.");
56
50
  noStart = false;
57
51
  }
58
- if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
59
- console.warn("Invalid selector. Fallback: no selector string.");
60
- selector = "";
52
+ if (selector !== "") {
53
+ if (typeof selector !== "string" || !selector.trim()) {
54
+ console.warn("Invalid selector. Fallback: no selector string.");
55
+ selector = "";
56
+ } else {
57
+ try {
58
+ container.querySelector(selector);
59
+ } catch {
60
+ console.warn("Invalid selector. Fallback: no selector string.");
61
+ selector = "";
62
+ }
63
+ }
61
64
  }
62
65
  if (typeof typeahead !== "boolean") {
63
66
  console.warn("Invalid typeahead option. Fallback: false.");
@@ -86,7 +89,10 @@ var RovingTabIndex = class _RovingTabIndex {
86
89
  this.#isDestroyed = true;
87
90
  this.#controller?.abort();
88
91
  this.#controller = null;
89
- attributesUtils.restoreAttributes([...this.#focusables]);
92
+ this.#focusables.forEach((focusable) => {
93
+ _RovingTabIndex.#initialized.delete(focusable);
94
+ attributesUtils.restoreAttributes([focusable]);
95
+ });
90
96
  this.#focusables.clear();
91
97
  this.#focusablesByFirstChar.clear();
92
98
  }
@@ -139,14 +145,16 @@ var RovingTabIndex = class _RovingTabIndex {
139
145
  if (!(active instanceof Element)) {
140
146
  return;
141
147
  }
142
- const current = this.#getFocusables();
143
- if (!current.includes(active)) {
148
+ const candidates = this.#getFocusables().filter(
149
+ (focusable2) => this.#focusables.has(focusable2)
150
+ );
151
+ if (!candidates.includes(active)) {
144
152
  return;
145
153
  }
146
154
  event.preventDefault();
147
- const currentIndex = current.indexOf(active);
155
+ const activeIndex = candidates.indexOf(active);
148
156
  let newIndex;
149
- let target = current;
157
+ let target = candidates;
150
158
  switch (key) {
151
159
  case "End":
152
160
  newIndex = -1;
@@ -156,46 +164,53 @@ var RovingTabIndex = class _RovingTabIndex {
156
164
  break;
157
165
  case "ArrowLeft":
158
166
  case "ArrowUp": {
159
- const rawIndex = currentIndex - 1;
167
+ const rawIndex = activeIndex - 1;
160
168
  newIndex = wrap ? rawIndex : Math.max(rawIndex, 0);
161
169
  break;
162
170
  }
163
171
  case "ArrowRight":
164
172
  case "ArrowDown": {
165
- const rawIndex = currentIndex + 1;
166
- newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
173
+ const rawIndex = activeIndex + 1;
174
+ newIndex = wrap ? rawIndex % candidates.length : Math.min(rawIndex, candidates.length - 1);
167
175
  break;
168
176
  }
169
177
  default: {
170
- target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
171
- const foundIndex = target.findIndex(
172
- (focusable2) => current.indexOf(focusable2) > currentIndex
178
+ const focusables = new Set(
179
+ this.#focusablesByFirstChar.get(key.toUpperCase()) ?? []
180
+ );
181
+ target = candidates.filter((focusable2) => focusables.has(focusable2));
182
+ const afterIndex = target.findIndex(
183
+ (focusable2) => candidates.indexOf(focusable2) > activeIndex
173
184
  );
174
- newIndex = foundIndex >= 0 ? foundIndex : 0;
185
+ newIndex = afterIndex >= 0 ? afterIndex : 0;
175
186
  }
176
187
  }
177
188
  const focusable = target.at(newIndex);
178
189
  focusable && powerFocusable.focusElement(focusable);
179
190
  };
180
191
  #update(active) {
181
- const current = new Set(this.#getFocusables());
192
+ const focusables = new Set(this.#getFocusables());
182
193
  for (const focusable of this.#focusables) {
183
- if (!current.has(focusable)) {
184
- focusable.isConnected && attributesUtils.restoreAttributes([focusable]);
194
+ if (!focusables.has(focusable)) {
195
+ _RovingTabIndex.#initialized.delete(focusable);
196
+ attributesUtils.restoreAttributes([focusable]);
185
197
  this.#focusables.delete(focusable);
186
- this.#focusablesByFirstChar.forEach((focusables) => {
187
- const index = focusables.indexOf(focusable);
188
- index >= 0 && focusables.splice(index, 1);
189
- });
198
+ for (const [key, focusables2] of this.#focusablesByFirstChar) {
199
+ const index = focusables2.indexOf(focusable);
200
+ if (index >= 0) {
201
+ focusables2.splice(index, 1);
202
+ !focusables2.length && this.#focusablesByFirstChar.delete(key);
203
+ }
204
+ }
190
205
  }
191
206
  }
192
207
  const { navigationOnly, noStart, typeahead } = this.#settings;
193
- for (const focusable of current) {
208
+ for (const focusable of focusables) {
194
209
  if (this.#focusables.has(focusable)) {
195
210
  continue;
196
211
  }
197
212
  if (_RovingTabIndex.#initialized.has(focusable)) {
198
- throw new TypeError("Already initialized");
213
+ continue;
199
214
  }
200
215
  this.#focusables.add(focusable);
201
216
  _RovingTabIndex.#initialized.add(focusable);
@@ -219,9 +234,9 @@ var RovingTabIndex = class _RovingTabIndex {
219
234
  });
220
235
  }
221
236
  keys.forEach((key) => {
222
- const focusables = this.#focusablesByFirstChar.get(key) ?? [];
223
- focusables.push(focusable);
224
- this.#focusablesByFirstChar.set(key, focusables);
237
+ const focusables2 = this.#focusablesByFirstChar.get(key) ?? [];
238
+ focusables2.push(focusable);
239
+ this.#focusablesByFirstChar.set(key, focusables2);
225
240
  });
226
241
  }
227
242
  if (!navigationOnly) {
@@ -238,7 +253,7 @@ var RovingTabIndex = class _RovingTabIndex {
238
253
  }
239
254
  #createSelectorFilter() {
240
255
  const { selector } = this.#settings;
241
- return (element) => !selector || [...this.#container.querySelectorAll(selector)].includes(element);
256
+ return (element) => !selector || element.matches(selector);
242
257
  }
243
258
  #getFocusables() {
244
259
  return powerFocusable.getFocusables(this.#container, {
@@ -254,7 +269,7 @@ var RovingTabIndex = class _RovingTabIndex {
254
269
  * Lightweight roving tabindex utility with fully focus management.
255
270
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
256
271
  *
257
- * @version 3.1.12
272
+ * @version 3.1.14
258
273
  * @author Yusuke Kamiyamane
259
274
  * @license MIT
260
275
  * @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.1.12
6
+ * @version 3.1.14
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.1.12
6
+ * @version 3.1.14
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -8,14 +8,8 @@ function createRovingTabIndex(container, options = {}) {
8
8
  return () => {
9
9
  };
10
10
  }
11
- try {
12
- const roving = new RovingTabIndex(container, options);
13
- return () => roving.destroy();
14
- } catch (error) {
15
- error instanceof Error && console.warn(error.message || error);
16
- return () => {
17
- };
18
- }
11
+ const roving = new RovingTabIndex(container, options);
12
+ return () => roving.destroy();
19
13
  }
20
14
  var RovingTabIndex = class _RovingTabIndex {
21
15
  static #initialized = /* @__PURE__ */ new Set();
@@ -53,9 +47,18 @@ var RovingTabIndex = class _RovingTabIndex {
53
47
  console.warn("Invalid noStart option. Fallback: false.");
54
48
  noStart = false;
55
49
  }
56
- if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
57
- console.warn("Invalid selector. Fallback: no selector string.");
58
- selector = "";
50
+ if (selector !== "") {
51
+ if (typeof selector !== "string" || !selector.trim()) {
52
+ console.warn("Invalid selector. Fallback: no selector string.");
53
+ selector = "";
54
+ } else {
55
+ try {
56
+ container.querySelector(selector);
57
+ } catch {
58
+ console.warn("Invalid selector. Fallback: no selector string.");
59
+ selector = "";
60
+ }
61
+ }
59
62
  }
60
63
  if (typeof typeahead !== "boolean") {
61
64
  console.warn("Invalid typeahead option. Fallback: false.");
@@ -84,7 +87,10 @@ var RovingTabIndex = class _RovingTabIndex {
84
87
  this.#isDestroyed = true;
85
88
  this.#controller?.abort();
86
89
  this.#controller = null;
87
- restoreAttributes([...this.#focusables]);
90
+ this.#focusables.forEach((focusable) => {
91
+ _RovingTabIndex.#initialized.delete(focusable);
92
+ restoreAttributes([focusable]);
93
+ });
88
94
  this.#focusables.clear();
89
95
  this.#focusablesByFirstChar.clear();
90
96
  }
@@ -137,14 +143,16 @@ var RovingTabIndex = class _RovingTabIndex {
137
143
  if (!(active instanceof Element)) {
138
144
  return;
139
145
  }
140
- const current = this.#getFocusables();
141
- if (!current.includes(active)) {
146
+ const candidates = this.#getFocusables().filter(
147
+ (focusable2) => this.#focusables.has(focusable2)
148
+ );
149
+ if (!candidates.includes(active)) {
142
150
  return;
143
151
  }
144
152
  event.preventDefault();
145
- const currentIndex = current.indexOf(active);
153
+ const activeIndex = candidates.indexOf(active);
146
154
  let newIndex;
147
- let target = current;
155
+ let target = candidates;
148
156
  switch (key) {
149
157
  case "End":
150
158
  newIndex = -1;
@@ -154,46 +162,53 @@ var RovingTabIndex = class _RovingTabIndex {
154
162
  break;
155
163
  case "ArrowLeft":
156
164
  case "ArrowUp": {
157
- const rawIndex = currentIndex - 1;
165
+ const rawIndex = activeIndex - 1;
158
166
  newIndex = wrap ? rawIndex : Math.max(rawIndex, 0);
159
167
  break;
160
168
  }
161
169
  case "ArrowRight":
162
170
  case "ArrowDown": {
163
- const rawIndex = currentIndex + 1;
164
- newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
171
+ const rawIndex = activeIndex + 1;
172
+ newIndex = wrap ? rawIndex % candidates.length : Math.min(rawIndex, candidates.length - 1);
165
173
  break;
166
174
  }
167
175
  default: {
168
- target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
169
- const foundIndex = target.findIndex(
170
- (focusable2) => current.indexOf(focusable2) > currentIndex
176
+ const focusables = new Set(
177
+ this.#focusablesByFirstChar.get(key.toUpperCase()) ?? []
178
+ );
179
+ target = candidates.filter((focusable2) => focusables.has(focusable2));
180
+ const afterIndex = target.findIndex(
181
+ (focusable2) => candidates.indexOf(focusable2) > activeIndex
171
182
  );
172
- newIndex = foundIndex >= 0 ? foundIndex : 0;
183
+ newIndex = afterIndex >= 0 ? afterIndex : 0;
173
184
  }
174
185
  }
175
186
  const focusable = target.at(newIndex);
176
187
  focusable && focusElement(focusable);
177
188
  };
178
189
  #update(active) {
179
- const current = new Set(this.#getFocusables());
190
+ const focusables = new Set(this.#getFocusables());
180
191
  for (const focusable of this.#focusables) {
181
- if (!current.has(focusable)) {
182
- focusable.isConnected && restoreAttributes([focusable]);
192
+ if (!focusables.has(focusable)) {
193
+ _RovingTabIndex.#initialized.delete(focusable);
194
+ restoreAttributes([focusable]);
183
195
  this.#focusables.delete(focusable);
184
- this.#focusablesByFirstChar.forEach((focusables) => {
185
- const index = focusables.indexOf(focusable);
186
- index >= 0 && focusables.splice(index, 1);
187
- });
196
+ for (const [key, focusables2] of this.#focusablesByFirstChar) {
197
+ const index = focusables2.indexOf(focusable);
198
+ if (index >= 0) {
199
+ focusables2.splice(index, 1);
200
+ !focusables2.length && this.#focusablesByFirstChar.delete(key);
201
+ }
202
+ }
188
203
  }
189
204
  }
190
205
  const { navigationOnly, noStart, typeahead } = this.#settings;
191
- for (const focusable of current) {
206
+ for (const focusable of focusables) {
192
207
  if (this.#focusables.has(focusable)) {
193
208
  continue;
194
209
  }
195
210
  if (_RovingTabIndex.#initialized.has(focusable)) {
196
- throw new TypeError("Already initialized");
211
+ continue;
197
212
  }
198
213
  this.#focusables.add(focusable);
199
214
  _RovingTabIndex.#initialized.add(focusable);
@@ -217,9 +232,9 @@ var RovingTabIndex = class _RovingTabIndex {
217
232
  });
218
233
  }
219
234
  keys.forEach((key) => {
220
- const focusables = this.#focusablesByFirstChar.get(key) ?? [];
221
- focusables.push(focusable);
222
- this.#focusablesByFirstChar.set(key, focusables);
235
+ const focusables2 = this.#focusablesByFirstChar.get(key) ?? [];
236
+ focusables2.push(focusable);
237
+ this.#focusablesByFirstChar.set(key, focusables2);
223
238
  });
224
239
  }
225
240
  if (!navigationOnly) {
@@ -236,7 +251,7 @@ var RovingTabIndex = class _RovingTabIndex {
236
251
  }
237
252
  #createSelectorFilter() {
238
253
  const { selector } = this.#settings;
239
- return (element) => !selector || [...this.#container.querySelectorAll(selector)].includes(element);
254
+ return (element) => !selector || element.matches(selector);
240
255
  }
241
256
  #getFocusables() {
242
257
  return getFocusables(this.#container, {
@@ -252,7 +267,7 @@ var RovingTabIndex = class _RovingTabIndex {
252
267
  * Lightweight roving tabindex utility with fully focus management.
253
268
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
254
269
  *
255
- * @version 3.1.12
270
+ * @version 3.1.14
256
271
  * @author Yusuke Kamiyamane
257
272
  * @license MIT
258
273
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/roving-tabindex",
3
- "version": "3.1.12",
3
+ "version": "3.1.14",
4
4
  "description": "Lightweight roving tabindex utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -57,6 +57,6 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@y14e/attributes-utils": "^1.1.3",
60
- "power-focusable": "^4.3.12"
60
+ "power-focusable": "^4.3.18"
61
61
  }
62
62
  }