@y14e/roving-tabindex 3.1.10 → 3.1.12

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.10';
16
+ import { createRovingTabIndex } from 'https://esm.sh/@y14e/roving-tabindex@3.1.12';
17
17
  // or
18
- import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.1.10/+esm';
18
+ import { createRovingTabIndex } from 'https://cdn.jsdelivr.net/npm/@y14e/roving-tabindex@3.1.12/+esm';
19
19
  // or
20
- import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.1.10';
20
+ import { createRovingTabIndex } from 'https://esm.unpkg.com/@y14e/roving-tabindex@3.1.12';
21
21
  ```
22
22
 
23
23
  ## 📦 APIs
@@ -51,6 +51,10 @@ 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(
55
+ /(,\s*)?\[tabindex="-1"\]/g,
56
+ ""
57
+ );
54
58
  function getFocusables(container = document.body, options = {}) {
55
59
  if (!(container instanceof Element)) {
56
60
  console.warn("Invalid container element. Fallback: <body> element.");
@@ -90,21 +94,27 @@ function getFocusables(container = document.body, options = {}) {
90
94
  const elements = [];
91
95
  if (composed || include) {
92
96
  let traverse2 = function(node) {
93
- if (!(node instanceof Element)) {
94
- return;
95
- }
96
- if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
97
+ if (isFocusable(node, {
98
+ skipNegativeTabIndexCheck,
99
+ skipVisibilityCheck
100
+ }) || include?.(node)) {
97
101
  elements[elements.length] = node;
98
102
  }
99
- const children = getComposedChildren(node);
100
- for (let i = 0, l = children.length; i < l; i++) {
101
- const child = children[i];
103
+ const children2 = composed ? getComposedChildren(node) : getChildren(node);
104
+ for (let i = 0, l = children2.length; i < l; i++) {
105
+ const child = children2[i];
102
106
  child && traverse2(child);
103
107
  }
104
108
  };
105
- traverse2(container);
109
+ const children = composed ? getComposedChildren(container) : getChildren(container);
110
+ for (let i = 0, l = children.length; i < l; i++) {
111
+ const child = children[i];
112
+ child && traverse2(child);
113
+ }
106
114
  } else {
107
- const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
115
+ const candidates = container.querySelectorAll(
116
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
117
+ );
108
118
  for (let i = 0, l = candidates.length; i < l; i++) {
109
119
  const candidate = candidates[i];
110
120
  if (candidate && isFocusable(candidate, {
@@ -115,8 +125,8 @@ function getFocusables(container = document.body, options = {}) {
115
125
  }
116
126
  }
117
127
  }
118
- const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
119
- return filter ? unfiltered.filter(filter) : unfiltered;
128
+ const filtered = filter ? elements.filter(filter) : elements;
129
+ return normalizeRadioGroup(sortByTabIndex(filtered));
120
130
  }
121
131
  function isFocusable(element, options = {}) {
122
132
  if (!(element instanceof Element)) {
@@ -139,7 +149,7 @@ function isFocusable(element, options = {}) {
139
149
  return false;
140
150
  }
141
151
  if (!element.matches(
142
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
152
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
143
153
  )) {
144
154
  return false;
145
155
  }
@@ -185,7 +195,7 @@ function isDisabledDeep(element) {
185
195
  return false;
186
196
  }
187
197
  function normalizeRadioGroup(elements) {
188
- let map = null;
198
+ let rootMap = null;
189
199
  for (let i = 0, l = elements.length; i < l; i++) {
190
200
  const element = elements[i];
191
201
  if (!(element instanceof HTMLInputElement)) {
@@ -194,25 +204,45 @@ function normalizeRadioGroup(elements) {
194
204
  if (!isUngroupedRadio(element)) {
195
205
  continue;
196
206
  }
197
- if (!map) {
198
- map = /* @__PURE__ */ new Map();
207
+ if (!rootMap) {
208
+ rootMap = /* @__PURE__ */ new Map();
209
+ }
210
+ const root = element.getRootNode();
211
+ let formMap = rootMap.get(root);
212
+ if (!formMap) {
213
+ formMap = /* @__PURE__ */ new Map();
214
+ rootMap.set(root, formMap);
199
215
  }
200
- const key = `${element.form?.id ?? "no-form"}::${element.name}`;
201
- const group = map.get(key) ?? map.set(key, []).get(key);
202
- if (group) {
203
- group[group.length] = element;
216
+ let nameMap = formMap.get(element.form);
217
+ if (!nameMap) {
218
+ nameMap = /* @__PURE__ */ new Map();
219
+ formMap.set(element.form, nameMap);
204
220
  }
221
+ let group = nameMap.get(element.name);
222
+ if (!group) {
223
+ group = [];
224
+ nameMap.set(element.name, group);
225
+ }
226
+ group[group.length] = element;
205
227
  }
206
- if (!map) {
228
+ if (!rootMap) {
207
229
  return elements;
208
230
  }
209
231
  const placeholder = /* @__PURE__ */ new Set();
210
- for (const group of map.values()) {
211
- placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
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];
236
+ radio && placeholder.add(radio);
237
+ }
238
+ }
212
239
  }
213
- return elements.filter(
214
- (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
215
- );
240
+ return elements.filter((element) => {
241
+ if (!(element instanceof HTMLInputElement)) {
242
+ return true;
243
+ }
244
+ return !isUngroupedRadio(element) || placeholder.has(element);
245
+ });
216
246
  }
217
247
  function sortByTabIndex(elements) {
218
248
  const ordered = [];
@@ -284,7 +314,7 @@ function isInert(element) {
284
314
  return "inert" in element && !!element.inert;
285
315
  }
286
316
  function isUngroupedRadio(element) {
287
- return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
317
+ return element.type === "radio" && !!element.name;
288
318
  }
289
319
 
290
320
  // src/index.ts
@@ -339,7 +369,7 @@ var RovingTabIndex = class _RovingTabIndex {
339
369
  console.warn("Invalid noStart option. Fallback: false.");
340
370
  noStart = false;
341
371
  }
342
- if (selector && (typeof selector !== "string" || !selector.trim())) {
372
+ if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
343
373
  console.warn("Invalid selector. Fallback: no selector string.");
344
374
  selector = "";
345
375
  }
@@ -538,7 +568,7 @@ var RovingTabIndex = class _RovingTabIndex {
538
568
  * Lightweight roving tabindex utility with fully focus management.
539
569
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
540
570
  *
541
- * @version 3.1.10
571
+ * @version 3.1.12
542
572
  * @author Yusuke Kamiyamane
543
573
  * @license MIT
544
574
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -563,7 +593,7 @@ power-focusable/dist/index.js:
563
593
  * High-precision focus management utility with full composed tree support.
564
594
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
565
595
  *
566
- * @version 4.3.11
596
+ * @version 4.3.12
567
597
  * @author Yusuke Kamiyamane
568
598
  * @license MIT
569
599
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -49,6 +49,10 @@ 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(
53
+ /(,\s*)?\[tabindex="-1"\]/g,
54
+ ""
55
+ );
52
56
  function getFocusables(container = document.body, options = {}) {
53
57
  if (!(container instanceof Element)) {
54
58
  console.warn("Invalid container element. Fallback: <body> element.");
@@ -88,21 +92,27 @@ function getFocusables(container = document.body, options = {}) {
88
92
  const elements = [];
89
93
  if (composed || include) {
90
94
  let traverse2 = function(node) {
91
- if (!(node instanceof Element)) {
92
- return;
93
- }
94
- if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
95
+ if (isFocusable(node, {
96
+ skipNegativeTabIndexCheck,
97
+ skipVisibilityCheck
98
+ }) || include?.(node)) {
95
99
  elements[elements.length] = node;
96
100
  }
97
- const children = getComposedChildren(node);
98
- for (let i = 0, l = children.length; i < l; i++) {
99
- const child = children[i];
101
+ const children2 = composed ? getComposedChildren(node) : getChildren(node);
102
+ for (let i = 0, l = children2.length; i < l; i++) {
103
+ const child = children2[i];
100
104
  child && traverse2(child);
101
105
  }
102
106
  };
103
- traverse2(container);
107
+ const children = composed ? getComposedChildren(container) : getChildren(container);
108
+ for (let i = 0, l = children.length; i < l; i++) {
109
+ const child = children[i];
110
+ child && traverse2(child);
111
+ }
104
112
  } else {
105
- const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
113
+ const candidates = container.querySelectorAll(
114
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
115
+ );
106
116
  for (let i = 0, l = candidates.length; i < l; i++) {
107
117
  const candidate = candidates[i];
108
118
  if (candidate && isFocusable(candidate, {
@@ -113,8 +123,8 @@ function getFocusables(container = document.body, options = {}) {
113
123
  }
114
124
  }
115
125
  }
116
- const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
117
- return filter ? unfiltered.filter(filter) : unfiltered;
126
+ const filtered = filter ? elements.filter(filter) : elements;
127
+ return normalizeRadioGroup(sortByTabIndex(filtered));
118
128
  }
119
129
  function isFocusable(element, options = {}) {
120
130
  if (!(element instanceof Element)) {
@@ -137,7 +147,7 @@ function isFocusable(element, options = {}) {
137
147
  return false;
138
148
  }
139
149
  if (!element.matches(
140
- skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
150
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
141
151
  )) {
142
152
  return false;
143
153
  }
@@ -183,7 +193,7 @@ function isDisabledDeep(element) {
183
193
  return false;
184
194
  }
185
195
  function normalizeRadioGroup(elements) {
186
- let map = null;
196
+ let rootMap = null;
187
197
  for (let i = 0, l = elements.length; i < l; i++) {
188
198
  const element = elements[i];
189
199
  if (!(element instanceof HTMLInputElement)) {
@@ -192,25 +202,45 @@ function normalizeRadioGroup(elements) {
192
202
  if (!isUngroupedRadio(element)) {
193
203
  continue;
194
204
  }
195
- if (!map) {
196
- map = /* @__PURE__ */ new Map();
205
+ if (!rootMap) {
206
+ rootMap = /* @__PURE__ */ new Map();
207
+ }
208
+ const root = element.getRootNode();
209
+ let formMap = rootMap.get(root);
210
+ if (!formMap) {
211
+ formMap = /* @__PURE__ */ new Map();
212
+ rootMap.set(root, formMap);
197
213
  }
198
- const key = `${element.form?.id ?? "no-form"}::${element.name}`;
199
- const group = map.get(key) ?? map.set(key, []).get(key);
200
- if (group) {
201
- group[group.length] = element;
214
+ let nameMap = formMap.get(element.form);
215
+ if (!nameMap) {
216
+ nameMap = /* @__PURE__ */ new Map();
217
+ formMap.set(element.form, nameMap);
202
218
  }
219
+ let group = nameMap.get(element.name);
220
+ if (!group) {
221
+ group = [];
222
+ nameMap.set(element.name, group);
223
+ }
224
+ group[group.length] = element;
203
225
  }
204
- if (!map) {
226
+ if (!rootMap) {
205
227
  return elements;
206
228
  }
207
229
  const placeholder = /* @__PURE__ */ new Set();
208
- for (const group of map.values()) {
209
- placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
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];
234
+ radio && placeholder.add(radio);
235
+ }
236
+ }
210
237
  }
211
- return elements.filter(
212
- (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
213
- );
238
+ return elements.filter((element) => {
239
+ if (!(element instanceof HTMLInputElement)) {
240
+ return true;
241
+ }
242
+ return !isUngroupedRadio(element) || placeholder.has(element);
243
+ });
214
244
  }
215
245
  function sortByTabIndex(elements) {
216
246
  const ordered = [];
@@ -282,7 +312,7 @@ function isInert(element) {
282
312
  return "inert" in element && !!element.inert;
283
313
  }
284
314
  function isUngroupedRadio(element) {
285
- return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
315
+ return element.type === "radio" && !!element.name;
286
316
  }
287
317
 
288
318
  // src/index.ts
@@ -337,7 +367,7 @@ var RovingTabIndex = class _RovingTabIndex {
337
367
  console.warn("Invalid noStart option. Fallback: false.");
338
368
  noStart = false;
339
369
  }
340
- if (selector && (typeof selector !== "string" || !selector.trim())) {
370
+ if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
341
371
  console.warn("Invalid selector. Fallback: no selector string.");
342
372
  selector = "";
343
373
  }
@@ -536,7 +566,7 @@ var RovingTabIndex = class _RovingTabIndex {
536
566
  * Lightweight roving tabindex utility with fully focus management.
537
567
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
538
568
  *
539
- * @version 3.1.10
569
+ * @version 3.1.12
540
570
  * @author Yusuke Kamiyamane
541
571
  * @license MIT
542
572
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -561,7 +591,7 @@ power-focusable/dist/index.js:
561
591
  * High-precision focus management utility with full composed tree support.
562
592
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
563
593
  *
564
- * @version 4.3.11
594
+ * @version 4.3.12
565
595
  * @author Yusuke Kamiyamane
566
596
  * @license MIT
567
597
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.cjs CHANGED
@@ -55,7 +55,7 @@ var RovingTabIndex = class _RovingTabIndex {
55
55
  console.warn("Invalid noStart option. Fallback: false.");
56
56
  noStart = false;
57
57
  }
58
- if (selector && (typeof selector !== "string" || !selector.trim())) {
58
+ if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
59
59
  console.warn("Invalid selector. Fallback: no selector string.");
60
60
  selector = "";
61
61
  }
@@ -254,7 +254,7 @@ var RovingTabIndex = class _RovingTabIndex {
254
254
  * Lightweight roving tabindex utility with fully focus management.
255
255
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
256
256
  *
257
- * @version 3.1.10
257
+ * @version 3.1.12
258
258
  * @author Yusuke Kamiyamane
259
259
  * @license MIT
260
260
  * @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.10
6
+ * @version 3.1.12
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.10
6
+ * @version 3.1.12
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ var RovingTabIndex = class _RovingTabIndex {
53
53
  console.warn("Invalid noStart option. Fallback: false.");
54
54
  noStart = false;
55
55
  }
56
- if (selector && (typeof selector !== "string" || !selector.trim())) {
56
+ if (selector !== "" && (typeof selector !== "string" || !selector.trim())) {
57
57
  console.warn("Invalid selector. Fallback: no selector string.");
58
58
  selector = "";
59
59
  }
@@ -252,7 +252,7 @@ var RovingTabIndex = class _RovingTabIndex {
252
252
  * Lightweight roving tabindex utility with fully focus management.
253
253
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
254
254
  *
255
- * @version 3.1.10
255
+ * @version 3.1.12
256
256
  * @author Yusuke Kamiyamane
257
257
  * @license MIT
258
258
  * @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.10",
3
+ "version": "3.1.12",
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.11"
60
+ "power-focusable": "^4.3.12"
61
61
  }
62
62
  }