@y14e/roving-tabindex 3.0.18 → 3.1.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 +4 -4
- package/dist/index.bundle.cjs +120 -7
- package/dist/index.bundle.js +120 -7
- package/dist/index.cjs +25 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -8
- package/package.json +1 -1
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
|
|
13
|
+
import { createRovingTabIndex } from '@y14e/roving-tabindex@3.1.0';
|
|
14
14
|
|
|
15
15
|
// CDNs
|
|
16
|
-
import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.0
|
|
16
|
+
import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.1.0';
|
|
17
17
|
// or
|
|
18
|
-
import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.0
|
|
18
|
+
import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.1.0/+esm';
|
|
19
19
|
// or
|
|
20
|
-
import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.0
|
|
20
|
+
import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.1.0';
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## 📦 APIs
|
package/dist/index.bundle.cjs
CHANGED
|
@@ -118,6 +118,12 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
118
118
|
const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
|
|
119
119
|
return filter ? unfiltered.filter(filter) : unfiltered;
|
|
120
120
|
}
|
|
121
|
+
function getNextFocusable(container = document.body, options = {}) {
|
|
122
|
+
return getRelativeFocusable(container, 1, options);
|
|
123
|
+
}
|
|
124
|
+
function getPreviousFocusable(container = document.body, options = {}) {
|
|
125
|
+
return getRelativeFocusable(container, -1, options);
|
|
126
|
+
}
|
|
121
127
|
function isFocusable(element, options = {}) {
|
|
122
128
|
if (!(element instanceof Element)) {
|
|
123
129
|
console.warn("Invalid element");
|
|
@@ -155,6 +161,84 @@ function isFocusable(element, options = {}) {
|
|
|
155
161
|
}
|
|
156
162
|
return true;
|
|
157
163
|
}
|
|
164
|
+
function getRelativeFocusable(container, offset, options) {
|
|
165
|
+
if (!(container instanceof Element)) {
|
|
166
|
+
console.warn("Invalid container element. Fallback: <body> element.");
|
|
167
|
+
container = document.body;
|
|
168
|
+
}
|
|
169
|
+
let {
|
|
170
|
+
anchor = getActiveElement(),
|
|
171
|
+
composed = false,
|
|
172
|
+
filter,
|
|
173
|
+
include,
|
|
174
|
+
skipNegativeTabIndexCheck = false,
|
|
175
|
+
skipVisibilityCheck = false,
|
|
176
|
+
wrap = false
|
|
177
|
+
} = options;
|
|
178
|
+
if (!(anchor instanceof Element)) {
|
|
179
|
+
const active = getActiveElement();
|
|
180
|
+
if (active instanceof Element) {
|
|
181
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
182
|
+
anchor = active;
|
|
183
|
+
} else {
|
|
184
|
+
console.warn("Invalid anchor element");
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!containsComposed(container, anchor)) {
|
|
189
|
+
console.warn("Anchor (active) element not within container");
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (typeof composed !== "boolean") {
|
|
193
|
+
console.warn("Invalid composed option. Fallback: false.");
|
|
194
|
+
composed = false;
|
|
195
|
+
}
|
|
196
|
+
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
197
|
+
console.warn(
|
|
198
|
+
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
199
|
+
);
|
|
200
|
+
filter = void 0;
|
|
201
|
+
}
|
|
202
|
+
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
203
|
+
console.warn(
|
|
204
|
+
"Invalid include function. Fallback: no include function (undefined)."
|
|
205
|
+
);
|
|
206
|
+
include = void 0;
|
|
207
|
+
}
|
|
208
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
209
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
210
|
+
skipNegativeTabIndexCheck = false;
|
|
211
|
+
}
|
|
212
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
213
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
214
|
+
skipVisibilityCheck = false;
|
|
215
|
+
}
|
|
216
|
+
if (typeof wrap !== "boolean") {
|
|
217
|
+
console.warn("Invalid wrap option. Fallback: false.");
|
|
218
|
+
wrap = false;
|
|
219
|
+
}
|
|
220
|
+
const settings = { composed, skipNegativeTabIndexCheck, skipVisibilityCheck };
|
|
221
|
+
if (filter !== void 0) {
|
|
222
|
+
Object.assign(settings, { filter });
|
|
223
|
+
}
|
|
224
|
+
if (include !== void 0) {
|
|
225
|
+
Object.assign(settings, { include });
|
|
226
|
+
}
|
|
227
|
+
const focusables = getFocusables(container, settings);
|
|
228
|
+
const { length } = focusables;
|
|
229
|
+
if (!length) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const currentIndex = focusables.indexOf(anchor);
|
|
233
|
+
if (currentIndex === -1) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const offsetIndex = currentIndex + offset;
|
|
237
|
+
if ((offsetIndex < 0 || offsetIndex >= length) && !wrap) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return focusables[(offsetIndex + length) % length] ?? null;
|
|
241
|
+
}
|
|
158
242
|
function isDisabledDeep(element) {
|
|
159
243
|
let current = element;
|
|
160
244
|
while (current) {
|
|
@@ -235,6 +319,17 @@ function sortByTabIndex(elements) {
|
|
|
235
319
|
}
|
|
236
320
|
return sorted;
|
|
237
321
|
}
|
|
322
|
+
function containsComposed(container, element) {
|
|
323
|
+
let current = element;
|
|
324
|
+
while (current) {
|
|
325
|
+
if (current === container) {
|
|
326
|
+
return true;
|
|
327
|
+
} else {
|
|
328
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
238
333
|
function getComposedChildren(node) {
|
|
239
334
|
if (node instanceof ShadowRoot) {
|
|
240
335
|
return getChildren(node);
|
|
@@ -411,13 +506,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
411
506
|
};
|
|
412
507
|
#onKeyDown = (event) => {
|
|
413
508
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
414
|
-
if (altKey || ctrlKey || metaKey
|
|
509
|
+
if (shiftKey && key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
415
510
|
return;
|
|
416
511
|
}
|
|
417
512
|
const { direction, typeahead, wrap } = this.#settings;
|
|
418
513
|
const isBoth = !direction;
|
|
419
514
|
const isHorizontal = direction === "horizontal";
|
|
420
515
|
if (![
|
|
516
|
+
"Tab",
|
|
421
517
|
"End",
|
|
422
518
|
"Home",
|
|
423
519
|
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
@@ -435,11 +531,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
435
531
|
if (!current.includes(active)) {
|
|
436
532
|
return;
|
|
437
533
|
}
|
|
438
|
-
|
|
534
|
+
let shouldPrevent = true;
|
|
439
535
|
const currentIndex = current.indexOf(active);
|
|
440
|
-
let newIndex;
|
|
536
|
+
let newIndex = 0;
|
|
441
537
|
let target = current;
|
|
442
538
|
switch (key) {
|
|
539
|
+
case "Tab":
|
|
540
|
+
shouldPrevent = this.#moveFocus(shiftKey ? "previous" : "next");
|
|
541
|
+
break;
|
|
443
542
|
case "End":
|
|
444
543
|
newIndex = -1;
|
|
445
544
|
break;
|
|
@@ -461,13 +560,16 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
461
560
|
default: {
|
|
462
561
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
463
562
|
const foundIndex = target.findIndex(
|
|
464
|
-
(
|
|
563
|
+
(focusable) => current.indexOf(focusable) > currentIndex
|
|
465
564
|
);
|
|
466
565
|
newIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
467
566
|
}
|
|
468
567
|
}
|
|
469
|
-
|
|
470
|
-
|
|
568
|
+
shouldPrevent && event.preventDefault();
|
|
569
|
+
if (key !== "Tab") {
|
|
570
|
+
const focusable = target.at(newIndex);
|
|
571
|
+
focusable && focusElement(focusable);
|
|
572
|
+
}
|
|
471
573
|
};
|
|
472
574
|
#update(active) {
|
|
473
575
|
const current = new Set(this.#getFocusables());
|
|
@@ -540,13 +642,24 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
540
642
|
skipVisibilityCheck: true
|
|
541
643
|
});
|
|
542
644
|
}
|
|
645
|
+
#moveFocus(direction) {
|
|
646
|
+
const options = {
|
|
647
|
+
composed: true
|
|
648
|
+
};
|
|
649
|
+
const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
|
|
650
|
+
if (focusable) {
|
|
651
|
+
focusElement(focusable);
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
543
656
|
};
|
|
544
657
|
/**
|
|
545
658
|
* Roving Tabindex
|
|
546
659
|
* Lightweight roving tabindex utility with fully focus management.
|
|
547
660
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
548
661
|
*
|
|
549
|
-
* @version 3.0
|
|
662
|
+
* @version 3.1.0
|
|
550
663
|
* @author Yusuke Kamiyamane
|
|
551
664
|
* @license MIT
|
|
552
665
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.bundle.js
CHANGED
|
@@ -116,6 +116,12 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
116
116
|
const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
|
|
117
117
|
return filter ? unfiltered.filter(filter) : unfiltered;
|
|
118
118
|
}
|
|
119
|
+
function getNextFocusable(container = document.body, options = {}) {
|
|
120
|
+
return getRelativeFocusable(container, 1, options);
|
|
121
|
+
}
|
|
122
|
+
function getPreviousFocusable(container = document.body, options = {}) {
|
|
123
|
+
return getRelativeFocusable(container, -1, options);
|
|
124
|
+
}
|
|
119
125
|
function isFocusable(element, options = {}) {
|
|
120
126
|
if (!(element instanceof Element)) {
|
|
121
127
|
console.warn("Invalid element");
|
|
@@ -153,6 +159,84 @@ function isFocusable(element, options = {}) {
|
|
|
153
159
|
}
|
|
154
160
|
return true;
|
|
155
161
|
}
|
|
162
|
+
function getRelativeFocusable(container, offset, options) {
|
|
163
|
+
if (!(container instanceof Element)) {
|
|
164
|
+
console.warn("Invalid container element. Fallback: <body> element.");
|
|
165
|
+
container = document.body;
|
|
166
|
+
}
|
|
167
|
+
let {
|
|
168
|
+
anchor = getActiveElement(),
|
|
169
|
+
composed = false,
|
|
170
|
+
filter,
|
|
171
|
+
include,
|
|
172
|
+
skipNegativeTabIndexCheck = false,
|
|
173
|
+
skipVisibilityCheck = false,
|
|
174
|
+
wrap = false
|
|
175
|
+
} = options;
|
|
176
|
+
if (!(anchor instanceof Element)) {
|
|
177
|
+
const active = getActiveElement();
|
|
178
|
+
if (active instanceof Element) {
|
|
179
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
180
|
+
anchor = active;
|
|
181
|
+
} else {
|
|
182
|
+
console.warn("Invalid anchor element");
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!containsComposed(container, anchor)) {
|
|
187
|
+
console.warn("Anchor (active) element not within container");
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (typeof composed !== "boolean") {
|
|
191
|
+
console.warn("Invalid composed option. Fallback: false.");
|
|
192
|
+
composed = false;
|
|
193
|
+
}
|
|
194
|
+
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
195
|
+
console.warn(
|
|
196
|
+
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
197
|
+
);
|
|
198
|
+
filter = void 0;
|
|
199
|
+
}
|
|
200
|
+
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
201
|
+
console.warn(
|
|
202
|
+
"Invalid include function. Fallback: no include function (undefined)."
|
|
203
|
+
);
|
|
204
|
+
include = void 0;
|
|
205
|
+
}
|
|
206
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
207
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
208
|
+
skipNegativeTabIndexCheck = false;
|
|
209
|
+
}
|
|
210
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
211
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
212
|
+
skipVisibilityCheck = false;
|
|
213
|
+
}
|
|
214
|
+
if (typeof wrap !== "boolean") {
|
|
215
|
+
console.warn("Invalid wrap option. Fallback: false.");
|
|
216
|
+
wrap = false;
|
|
217
|
+
}
|
|
218
|
+
const settings = { composed, skipNegativeTabIndexCheck, skipVisibilityCheck };
|
|
219
|
+
if (filter !== void 0) {
|
|
220
|
+
Object.assign(settings, { filter });
|
|
221
|
+
}
|
|
222
|
+
if (include !== void 0) {
|
|
223
|
+
Object.assign(settings, { include });
|
|
224
|
+
}
|
|
225
|
+
const focusables = getFocusables(container, settings);
|
|
226
|
+
const { length } = focusables;
|
|
227
|
+
if (!length) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const currentIndex = focusables.indexOf(anchor);
|
|
231
|
+
if (currentIndex === -1) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const offsetIndex = currentIndex + offset;
|
|
235
|
+
if ((offsetIndex < 0 || offsetIndex >= length) && !wrap) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return focusables[(offsetIndex + length) % length] ?? null;
|
|
239
|
+
}
|
|
156
240
|
function isDisabledDeep(element) {
|
|
157
241
|
let current = element;
|
|
158
242
|
while (current) {
|
|
@@ -233,6 +317,17 @@ function sortByTabIndex(elements) {
|
|
|
233
317
|
}
|
|
234
318
|
return sorted;
|
|
235
319
|
}
|
|
320
|
+
function containsComposed(container, element) {
|
|
321
|
+
let current = element;
|
|
322
|
+
while (current) {
|
|
323
|
+
if (current === container) {
|
|
324
|
+
return true;
|
|
325
|
+
} else {
|
|
326
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
236
331
|
function getComposedChildren(node) {
|
|
237
332
|
if (node instanceof ShadowRoot) {
|
|
238
333
|
return getChildren(node);
|
|
@@ -409,13 +504,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
409
504
|
};
|
|
410
505
|
#onKeyDown = (event) => {
|
|
411
506
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
412
|
-
if (altKey || ctrlKey || metaKey
|
|
507
|
+
if (shiftKey && key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
413
508
|
return;
|
|
414
509
|
}
|
|
415
510
|
const { direction, typeahead, wrap } = this.#settings;
|
|
416
511
|
const isBoth = !direction;
|
|
417
512
|
const isHorizontal = direction === "horizontal";
|
|
418
513
|
if (![
|
|
514
|
+
"Tab",
|
|
419
515
|
"End",
|
|
420
516
|
"Home",
|
|
421
517
|
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
@@ -433,11 +529,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
433
529
|
if (!current.includes(active)) {
|
|
434
530
|
return;
|
|
435
531
|
}
|
|
436
|
-
|
|
532
|
+
let shouldPrevent = true;
|
|
437
533
|
const currentIndex = current.indexOf(active);
|
|
438
|
-
let newIndex;
|
|
534
|
+
let newIndex = 0;
|
|
439
535
|
let target = current;
|
|
440
536
|
switch (key) {
|
|
537
|
+
case "Tab":
|
|
538
|
+
shouldPrevent = this.#moveFocus(shiftKey ? "previous" : "next");
|
|
539
|
+
break;
|
|
441
540
|
case "End":
|
|
442
541
|
newIndex = -1;
|
|
443
542
|
break;
|
|
@@ -459,13 +558,16 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
459
558
|
default: {
|
|
460
559
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
461
560
|
const foundIndex = target.findIndex(
|
|
462
|
-
(
|
|
561
|
+
(focusable) => current.indexOf(focusable) > currentIndex
|
|
463
562
|
);
|
|
464
563
|
newIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
465
564
|
}
|
|
466
565
|
}
|
|
467
|
-
|
|
468
|
-
|
|
566
|
+
shouldPrevent && event.preventDefault();
|
|
567
|
+
if (key !== "Tab") {
|
|
568
|
+
const focusable = target.at(newIndex);
|
|
569
|
+
focusable && focusElement(focusable);
|
|
570
|
+
}
|
|
469
571
|
};
|
|
470
572
|
#update(active) {
|
|
471
573
|
const current = new Set(this.#getFocusables());
|
|
@@ -538,13 +640,24 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
538
640
|
skipVisibilityCheck: true
|
|
539
641
|
});
|
|
540
642
|
}
|
|
643
|
+
#moveFocus(direction) {
|
|
644
|
+
const options = {
|
|
645
|
+
composed: true
|
|
646
|
+
};
|
|
647
|
+
const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
|
|
648
|
+
if (focusable) {
|
|
649
|
+
focusElement(focusable);
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
541
654
|
};
|
|
542
655
|
/**
|
|
543
656
|
* Roving Tabindex
|
|
544
657
|
* Lightweight roving tabindex utility with fully focus management.
|
|
545
658
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
546
659
|
*
|
|
547
|
-
* @version 3.0
|
|
660
|
+
* @version 3.1.0
|
|
548
661
|
* @author Yusuke Kamiyamane
|
|
549
662
|
* @license MIT
|
|
550
663
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.cjs
CHANGED
|
@@ -127,13 +127,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
127
127
|
};
|
|
128
128
|
#onKeyDown = (event) => {
|
|
129
129
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
130
|
-
if (altKey || ctrlKey || metaKey
|
|
130
|
+
if (shiftKey && key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
133
|
const { direction, typeahead, wrap } = this.#settings;
|
|
134
134
|
const isBoth = !direction;
|
|
135
135
|
const isHorizontal = direction === "horizontal";
|
|
136
136
|
if (![
|
|
137
|
+
"Tab",
|
|
137
138
|
"End",
|
|
138
139
|
"Home",
|
|
139
140
|
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
@@ -151,11 +152,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
151
152
|
if (!current.includes(active)) {
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
+
let shouldPrevent = true;
|
|
155
156
|
const currentIndex = current.indexOf(active);
|
|
156
|
-
let newIndex;
|
|
157
|
+
let newIndex = 0;
|
|
157
158
|
let target = current;
|
|
158
159
|
switch (key) {
|
|
160
|
+
case "Tab":
|
|
161
|
+
shouldPrevent = this.#moveFocus(shiftKey ? "previous" : "next");
|
|
162
|
+
break;
|
|
159
163
|
case "End":
|
|
160
164
|
newIndex = -1;
|
|
161
165
|
break;
|
|
@@ -177,13 +181,16 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
177
181
|
default: {
|
|
178
182
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
179
183
|
const foundIndex = target.findIndex(
|
|
180
|
-
(
|
|
184
|
+
(focusable) => current.indexOf(focusable) > currentIndex
|
|
181
185
|
);
|
|
182
186
|
newIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
shouldPrevent && event.preventDefault();
|
|
190
|
+
if (key !== "Tab") {
|
|
191
|
+
const focusable = target.at(newIndex);
|
|
192
|
+
focusable && powerFocusable.focusElement(focusable);
|
|
193
|
+
}
|
|
187
194
|
};
|
|
188
195
|
#update(active) {
|
|
189
196
|
const current = new Set(this.#getFocusables());
|
|
@@ -256,13 +263,24 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
256
263
|
skipVisibilityCheck: true
|
|
257
264
|
});
|
|
258
265
|
}
|
|
266
|
+
#moveFocus(direction) {
|
|
267
|
+
const options = {
|
|
268
|
+
composed: true
|
|
269
|
+
};
|
|
270
|
+
const focusable = direction === "previous" ? powerFocusable.getPreviousFocusable(document.body, options) : powerFocusable.getNextFocusable(document.body, options);
|
|
271
|
+
if (focusable) {
|
|
272
|
+
powerFocusable.focusElement(focusable);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
259
277
|
};
|
|
260
278
|
/**
|
|
261
279
|
* Roving Tabindex
|
|
262
280
|
* Lightweight roving tabindex utility with fully focus management.
|
|
263
281
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
264
282
|
*
|
|
265
|
-
* @version 3.0
|
|
283
|
+
* @version 3.1.0
|
|
266
284
|
* @author Yusuke Kamiyamane
|
|
267
285
|
* @license MIT
|
|
268
286
|
* @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
|
|
6
|
+
* @version 3.1.0
|
|
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
|
|
6
|
+
* @version 3.1.0
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { restoreAttributes, saveAttributes, addTokenToAttribute } from '@y14e/attributes-utils';
|
|
2
|
-
import { getActiveElement, focusElement, getFocusables } from 'power-focusable';
|
|
2
|
+
import { getActiveElement, focusElement, getFocusables, getPreviousFocusable, getNextFocusable } from 'power-focusable';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
function createRovingTabIndex(container, options = {}) {
|
|
@@ -125,13 +125,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
125
125
|
};
|
|
126
126
|
#onKeyDown = (event) => {
|
|
127
127
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
128
|
-
if (altKey || ctrlKey || metaKey
|
|
128
|
+
if (shiftKey && key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
131
|
const { direction, typeahead, wrap } = this.#settings;
|
|
132
132
|
const isBoth = !direction;
|
|
133
133
|
const isHorizontal = direction === "horizontal";
|
|
134
134
|
if (![
|
|
135
|
+
"Tab",
|
|
135
136
|
"End",
|
|
136
137
|
"Home",
|
|
137
138
|
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
@@ -149,11 +150,14 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
149
150
|
if (!current.includes(active)) {
|
|
150
151
|
return;
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
+
let shouldPrevent = true;
|
|
153
154
|
const currentIndex = current.indexOf(active);
|
|
154
|
-
let newIndex;
|
|
155
|
+
let newIndex = 0;
|
|
155
156
|
let target = current;
|
|
156
157
|
switch (key) {
|
|
158
|
+
case "Tab":
|
|
159
|
+
shouldPrevent = this.#moveFocus(shiftKey ? "previous" : "next");
|
|
160
|
+
break;
|
|
157
161
|
case "End":
|
|
158
162
|
newIndex = -1;
|
|
159
163
|
break;
|
|
@@ -175,13 +179,16 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
175
179
|
default: {
|
|
176
180
|
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
177
181
|
const foundIndex = target.findIndex(
|
|
178
|
-
(
|
|
182
|
+
(focusable) => current.indexOf(focusable) > currentIndex
|
|
179
183
|
);
|
|
180
184
|
newIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
shouldPrevent && event.preventDefault();
|
|
188
|
+
if (key !== "Tab") {
|
|
189
|
+
const focusable = target.at(newIndex);
|
|
190
|
+
focusable && focusElement(focusable);
|
|
191
|
+
}
|
|
185
192
|
};
|
|
186
193
|
#update(active) {
|
|
187
194
|
const current = new Set(this.#getFocusables());
|
|
@@ -254,13 +261,24 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
254
261
|
skipVisibilityCheck: true
|
|
255
262
|
});
|
|
256
263
|
}
|
|
264
|
+
#moveFocus(direction) {
|
|
265
|
+
const options = {
|
|
266
|
+
composed: true
|
|
267
|
+
};
|
|
268
|
+
const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
|
|
269
|
+
if (focusable) {
|
|
270
|
+
focusElement(focusable);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
257
275
|
};
|
|
258
276
|
/**
|
|
259
277
|
* Roving Tabindex
|
|
260
278
|
* Lightweight roving tabindex utility with fully focus management.
|
|
261
279
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
262
280
|
*
|
|
263
|
-
* @version 3.0
|
|
281
|
+
* @version 3.1.0
|
|
264
282
|
* @author Yusuke Kamiyamane
|
|
265
283
|
* @license MIT
|
|
266
284
|
* @copyright Copyright (c) Yusuke Kamiyamane
|