@y14e/roving-tabindex 1.2.10 → 1.3.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/README.md +6 -1
- package/dist/index.cjs +21 -7
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +21 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Roving Tabindex
|
|
2
2
|
|
|
3
|
-
Lightweight roving tabindex utility with fully focus management. Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
3
|
+
Lightweight roving `tabindex` utility with fully focus management. Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
6
|
> Focus traversal works across shadow DOM boundaries using composed-tree-aware focus detection powered by [Power Focusable](https://github.com/y14e/power-focusable).
|
|
@@ -42,12 +42,17 @@ const cleanup = createRovingTabIndex(container, options);
|
|
|
42
42
|
```ts
|
|
43
43
|
interface RovingTabIndexOptions {
|
|
44
44
|
direction?: 'horizontal' | 'vertical' | undefined; // default: both (undefined)
|
|
45
|
+
navigationOnly?: boolean; // default: false
|
|
45
46
|
selector?: string | undefined;
|
|
46
47
|
typeahead?: boolean; // default: false
|
|
47
48
|
wrap?: boolean; // default: false
|
|
48
49
|
}
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
### `navigationOnly`
|
|
53
|
+
|
|
54
|
+
If `true`, enables keyboard navigation without modifying `tabindex`. Useful for widgets like accordions where all items should remain tabbable while still supporting arrow key navigation.
|
|
55
|
+
|
|
51
56
|
### `typeahead`
|
|
52
57
|
|
|
53
58
|
If `true`, enables character-based focus navigation. Typing a character moves focus to the next matching element.
|
package/dist/index.cjs
CHANGED
|
@@ -266,11 +266,21 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
266
266
|
return () => {
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
|
-
let {
|
|
269
|
+
let {
|
|
270
|
+
direction,
|
|
271
|
+
navigationOnly = false,
|
|
272
|
+
selector,
|
|
273
|
+
typeahead = false,
|
|
274
|
+
wrap = false
|
|
275
|
+
} = options;
|
|
270
276
|
if (typeof direction !== "undefined" && !["horizontal", "vertical"].includes(direction)) {
|
|
271
277
|
console.warn("Invalid direction option. Fallback: both (undefined).");
|
|
272
278
|
direction = void 0;
|
|
273
279
|
}
|
|
280
|
+
if (typeof navigationOnly !== "boolean") {
|
|
281
|
+
console.warn("Invalid navigationOnly option. Fallback: false.");
|
|
282
|
+
navigationOnly = false;
|
|
283
|
+
}
|
|
274
284
|
if (typeof selector !== "undefined" && typeof selector !== "string") {
|
|
275
285
|
console.warn(
|
|
276
286
|
"Invalid selector. Fallback: all focusable elements (undefined)."
|
|
@@ -287,6 +297,7 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
287
297
|
}
|
|
288
298
|
const roving = new RovingTabIndex(container, {
|
|
289
299
|
direction,
|
|
300
|
+
navigationOnly,
|
|
290
301
|
selector,
|
|
291
302
|
typeahead,
|
|
292
303
|
wrap
|
|
@@ -381,9 +392,6 @@ var RovingTabIndex = class {
|
|
|
381
392
|
newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
|
|
382
393
|
break;
|
|
383
394
|
default: {
|
|
384
|
-
if (!typeahead) {
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
395
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
388
396
|
const foundIndex = target.findIndex(
|
|
389
397
|
(focusable2) => current.indexOf(focusable2) > currentIndex
|
|
@@ -417,13 +425,16 @@ var RovingTabIndex = class {
|
|
|
417
425
|
index >= 0 && focusables.splice(index, 1);
|
|
418
426
|
});
|
|
419
427
|
}
|
|
428
|
+
const { navigationOnly } = this.#options;
|
|
420
429
|
for (const focusable of current) {
|
|
421
430
|
if (this.#focusables.has(focusable)) {
|
|
422
431
|
continue;
|
|
423
432
|
}
|
|
424
433
|
this.#focusables.add(focusable);
|
|
425
|
-
|
|
426
|
-
|
|
434
|
+
if (!navigationOnly) {
|
|
435
|
+
saveAttributes([focusable], ["tabindex"]);
|
|
436
|
+
focusable.setAttribute("tabindex", "-1");
|
|
437
|
+
}
|
|
427
438
|
if (!this.#options.typeahead) {
|
|
428
439
|
continue;
|
|
429
440
|
}
|
|
@@ -445,6 +456,9 @@ var RovingTabIndex = class {
|
|
|
445
456
|
this.#focusablesByFirstChar.set(key, focusables);
|
|
446
457
|
});
|
|
447
458
|
}
|
|
459
|
+
if (navigationOnly) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
448
462
|
if (active && this.#focusables.has(active)) {
|
|
449
463
|
this.#focusables.forEach((focusable) => {
|
|
450
464
|
focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
|
|
@@ -482,7 +496,7 @@ function getActiveElement() {
|
|
|
482
496
|
* Lightweight roving tabindex utility with fully focus management.
|
|
483
497
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
484
498
|
*
|
|
485
|
-
* @version 1.
|
|
499
|
+
* @version 1.3.0
|
|
486
500
|
* @author Yusuke Kamiyamane
|
|
487
501
|
* @license MIT
|
|
488
502
|
* @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 1.
|
|
6
|
+
* @version 1.3.0
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
interface RovingTabIndexOptions {
|
|
13
13
|
readonly direction?: 'horizontal' | 'vertical' | undefined;
|
|
14
|
+
readonly navigationOnly?: boolean;
|
|
14
15
|
readonly selector?: string | undefined;
|
|
15
16
|
readonly typeahead?: boolean;
|
|
16
17
|
readonly wrap?: boolean;
|
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 1.
|
|
6
|
+
* @version 1.3.0
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
interface RovingTabIndexOptions {
|
|
13
13
|
readonly direction?: 'horizontal' | 'vertical' | undefined;
|
|
14
|
+
readonly navigationOnly?: boolean;
|
|
14
15
|
readonly selector?: string | undefined;
|
|
15
16
|
readonly typeahead?: boolean;
|
|
16
17
|
readonly wrap?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -264,11 +264,21 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
264
264
|
return () => {
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
|
-
let {
|
|
267
|
+
let {
|
|
268
|
+
direction,
|
|
269
|
+
navigationOnly = false,
|
|
270
|
+
selector,
|
|
271
|
+
typeahead = false,
|
|
272
|
+
wrap = false
|
|
273
|
+
} = options;
|
|
268
274
|
if (typeof direction !== "undefined" && !["horizontal", "vertical"].includes(direction)) {
|
|
269
275
|
console.warn("Invalid direction option. Fallback: both (undefined).");
|
|
270
276
|
direction = void 0;
|
|
271
277
|
}
|
|
278
|
+
if (typeof navigationOnly !== "boolean") {
|
|
279
|
+
console.warn("Invalid navigationOnly option. Fallback: false.");
|
|
280
|
+
navigationOnly = false;
|
|
281
|
+
}
|
|
272
282
|
if (typeof selector !== "undefined" && typeof selector !== "string") {
|
|
273
283
|
console.warn(
|
|
274
284
|
"Invalid selector. Fallback: all focusable elements (undefined)."
|
|
@@ -285,6 +295,7 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
285
295
|
}
|
|
286
296
|
const roving = new RovingTabIndex(container, {
|
|
287
297
|
direction,
|
|
298
|
+
navigationOnly,
|
|
288
299
|
selector,
|
|
289
300
|
typeahead,
|
|
290
301
|
wrap
|
|
@@ -379,9 +390,6 @@ var RovingTabIndex = class {
|
|
|
379
390
|
newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
|
|
380
391
|
break;
|
|
381
392
|
default: {
|
|
382
|
-
if (!typeahead) {
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
393
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
386
394
|
const foundIndex = target.findIndex(
|
|
387
395
|
(focusable2) => current.indexOf(focusable2) > currentIndex
|
|
@@ -415,13 +423,16 @@ var RovingTabIndex = class {
|
|
|
415
423
|
index >= 0 && focusables.splice(index, 1);
|
|
416
424
|
});
|
|
417
425
|
}
|
|
426
|
+
const { navigationOnly } = this.#options;
|
|
418
427
|
for (const focusable of current) {
|
|
419
428
|
if (this.#focusables.has(focusable)) {
|
|
420
429
|
continue;
|
|
421
430
|
}
|
|
422
431
|
this.#focusables.add(focusable);
|
|
423
|
-
|
|
424
|
-
|
|
432
|
+
if (!navigationOnly) {
|
|
433
|
+
saveAttributes([focusable], ["tabindex"]);
|
|
434
|
+
focusable.setAttribute("tabindex", "-1");
|
|
435
|
+
}
|
|
425
436
|
if (!this.#options.typeahead) {
|
|
426
437
|
continue;
|
|
427
438
|
}
|
|
@@ -443,6 +454,9 @@ var RovingTabIndex = class {
|
|
|
443
454
|
this.#focusablesByFirstChar.set(key, focusables);
|
|
444
455
|
});
|
|
445
456
|
}
|
|
457
|
+
if (navigationOnly) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
446
460
|
if (active && this.#focusables.has(active)) {
|
|
447
461
|
this.#focusables.forEach((focusable) => {
|
|
448
462
|
focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
|
|
@@ -480,7 +494,7 @@ function getActiveElement() {
|
|
|
480
494
|
* Lightweight roving tabindex utility with fully focus management.
|
|
481
495
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
482
496
|
*
|
|
483
|
-
* @version 1.
|
|
497
|
+
* @version 1.3.0
|
|
484
498
|
* @author Yusuke Kamiyamane
|
|
485
499
|
* @license MIT
|
|
486
500
|
* @copyright Copyright (c) Yusuke Kamiyamane
|