@y14e/accordion 1.4.0 → 1.4.2

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/accordion
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import Accordion from '@y14e/accordion';
13
+ import Accordion from '@y14e/accordion@1.4.2';
14
14
 
15
15
  // CDNs
16
- import Accordion from 'https://esm.sh/@y14e/accordion'
16
+ import Accordion from 'https://esm.sh/@y14e/accordion@1.4.2';
17
17
  // or
18
- import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion/+esm';
18
+ import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@1.4.2/+esm';
19
19
  // or
20
- import Accordion from 'https://unpkg.com/@y14e/accordion/dist/index.js';
20
+ import Accordion from 'https://esm.unpkg.com/@y14e/accordion@1.4.2';
21
21
  ```
22
22
 
23
23
  ## Usage
package/dist/index.cjs CHANGED
@@ -165,6 +165,7 @@ function getFocusables(container = document.body, options = {}) {
165
165
  composed = false,
166
166
  filter,
167
167
  include,
168
+ skipNegativeTabIndexCheck = false,
168
169
  skipVisibilityCheck = false
169
170
  } = options;
170
171
  if (typeof composed !== "boolean") {
@@ -183,11 +184,22 @@ function getFocusables(container = document.body, options = {}) {
183
184
  );
184
185
  include = void 0;
185
186
  }
187
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
188
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
189
+ skipNegativeTabIndexCheck = false;
190
+ }
191
+ if (typeof skipVisibilityCheck !== "boolean") {
192
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
193
+ skipVisibilityCheck = false;
194
+ }
186
195
  const elements = [];
187
196
  if (composed || include) {
188
197
  let traverse2 = function(node) {
189
198
  if (node instanceof Element) {
190
- if (isFocusable(node, { skipVisibilityCheck }) || include?.(node)) {
199
+ if (isFocusable(node, {
200
+ skipNegativeTabIndexCheck,
201
+ skipVisibilityCheck
202
+ }) || include?.(node)) {
191
203
  elements[elements.length] = node;
192
204
  }
193
205
  }
@@ -208,7 +220,10 @@ function getFocusables(container = document.body, options = {}) {
208
220
  if (!(candidate instanceof Element)) {
209
221
  continue;
210
222
  }
211
- if (isFocusable(candidate, { skipVisibilityCheck })) {
223
+ if (isFocusable(candidate, {
224
+ skipNegativeTabIndexCheck,
225
+ skipVisibilityCheck
226
+ })) {
212
227
  elements[elements.length] = candidate;
213
228
  }
214
229
  }
@@ -221,7 +236,11 @@ function isFocusable(element, options = {}) {
221
236
  console.warn("Invalid element");
222
237
  return false;
223
238
  }
224
- let { skipVisibilityCheck = false } = options;
239
+ let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
240
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
241
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
242
+ skipNegativeTabIndexCheck = false;
243
+ }
225
244
  if (typeof skipVisibilityCheck !== "boolean") {
226
245
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
227
246
  skipVisibilityCheck = false;
@@ -229,10 +248,12 @@ function isFocusable(element, options = {}) {
229
248
  if (element.hasAttribute("hidden") || isInert(element)) {
230
249
  return false;
231
250
  }
232
- if (getTabIndex(element) < 0) {
251
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
233
252
  return false;
234
253
  }
235
- if (!element.matches(FOCUSABLE_SELECTOR)) {
254
+ if (!element.matches(
255
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
256
+ )) {
236
257
  return false;
237
258
  }
238
259
  if (isDisabledDeep(element)) {
@@ -393,7 +414,7 @@ function createRovingTabIndex(container, options = {}) {
393
414
  console.warn("Invalid navigationOnly option. Fallback: false.");
394
415
  navigationOnly = false;
395
416
  }
396
- if (typeof selector !== "undefined" && typeof selector !== "string") {
417
+ if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
397
418
  console.warn(
398
419
  "Invalid selector. Fallback: all focusable elements (undefined)."
399
420
  );
@@ -407,13 +428,14 @@ function createRovingTabIndex(container, options = {}) {
407
428
  console.warn("Invalid wrap option. Fallback: false.");
408
429
  wrap = false;
409
430
  }
410
- const roving = new RovingTabIndex(container, {
411
- direction,
412
- navigationOnly,
413
- selector,
414
- typeahead,
415
- wrap
416
- });
431
+ const settings = { navigationOnly, typeahead, wrap };
432
+ if (direction) {
433
+ Object.assign(settings, { direction });
434
+ }
435
+ if (selector) {
436
+ Object.assign(settings, { selector });
437
+ }
438
+ const roving = new RovingTabIndex(container, settings);
417
439
  return () => roving.destroy();
418
440
  }
419
441
  var RovingTabIndex = class {
@@ -455,8 +477,8 @@ var RovingTabIndex = class {
455
477
  if (!event.composedPath().includes(this.#container)) {
456
478
  return;
457
479
  }
458
- const { key, altKey, ctrlKey, metaKey } = event;
459
- if (altKey || ctrlKey || metaKey) {
480
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
481
+ if (altKey || ctrlKey || metaKey || shiftKey) {
460
482
  return;
461
483
  }
462
484
  const { direction, typeahead, wrap } = this.#options;
@@ -519,14 +541,7 @@ var RovingTabIndex = class {
519
541
  focusElement(focusable);
520
542
  };
521
543
  #update(active) {
522
- const current = /* @__PURE__ */ new Set([
523
- ...this.#getFocusables(),
524
- ...getFocusables(this.#container, {
525
- composed: true,
526
- filter: this.#selectorFilter,
527
- skipVisibilityCheck: true
528
- })
529
- ]);
544
+ const current = new Set(this.#getFocusables());
530
545
  for (const focusable of this.#focusables) {
531
546
  if (current.has(focusable)) {
532
547
  continue;
@@ -590,8 +605,7 @@ var RovingTabIndex = class {
590
605
  return getFocusables(this.#container, {
591
606
  composed: true,
592
607
  filter: this.#selectorFilter,
593
- include: (element) => this.#focusables.has(element),
594
- skipVisibilityCheck: true
608
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly
595
609
  });
596
610
  }
597
611
  };
@@ -715,8 +729,6 @@ var Accordion = class _Accordion {
715
729
  this.#rootElement.removeAttribute("data-accordion-initialized");
716
730
  }
717
731
  #initialize() {
718
- this.#eventController = new AbortController();
719
- const { signal } = this.#eventController;
720
732
  saveAttributes(this.#triggerElements, [
721
733
  "aria-controls",
722
734
  "aria-disabled",
@@ -724,13 +736,15 @@ var Accordion = class _Accordion {
724
736
  "style",
725
737
  "tabindex"
726
738
  ]);
739
+ saveAttributes(this.#contentElements, ["aria-labelledby", "id", "role"]);
740
+ this.#eventController = new AbortController();
741
+ const { signal } = this.#eventController;
727
742
  this.#triggerElements.forEach((trigger2, i) => {
728
743
  const id = Math.random().toString(36).slice(-8);
729
744
  const content2 = this.#contentElements[i];
730
745
  if (!content2) {
731
746
  return;
732
747
  }
733
- saveAttributes([content2], ["aria-labelledby", "id", "role"]);
734
748
  content2.id ||= `accordion-content-${id}`;
735
749
  addTokenToAttribute(trigger2, "aria-controls", content2.id);
736
750
  trigger2.setAttribute(
@@ -880,7 +894,7 @@ function waitAnimationFinish(animation) {
880
894
  * Accordion
881
895
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
882
896
  *
883
- * @version 1.4.0
897
+ * @version 1.4.2
884
898
  * @author Yusuke Kamiyamane
885
899
  * @license MIT
886
900
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -892,7 +906,7 @@ function waitAnimationFinish(animation) {
892
906
  (**
893
907
  * Attributes Utils
894
908
  *
895
- * @version 1.0.5
909
+ * @version 1.1.0
896
910
  * @author Yusuke Kamiyamane
897
911
  * @license MIT
898
912
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -916,7 +930,7 @@ function waitAnimationFinish(animation) {
916
930
  * Lightweight roving tabindex utility with fully focus management.
917
931
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
918
932
  *
919
- * @version 1.3.1
933
+ * @version 2.0.4
920
934
  * @author Yusuke Kamiyamane
921
935
  * @license MIT
922
936
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -928,7 +942,7 @@ function waitAnimationFinish(animation) {
928
942
  (**
929
943
  * Attributes Utils
930
944
  *
931
- * @version 1.0.5
945
+ * @version 1.1.0
932
946
  * @author Yusuke Kamiyamane
933
947
  * @license MIT
934
948
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -941,7 +955,7 @@ function waitAnimationFinish(animation) {
941
955
  * High-precision focus management utility with full composed tree support.
942
956
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
943
957
  *
944
- * @version 4.2.0
958
+ * @version 4.3.1
945
959
  * @author Yusuke Kamiyamane
946
960
  * @license MIT
947
961
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 1.4.0
5
+ * @version 1.4.2
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 1.4.0
5
+ * @version 1.4.2
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -163,6 +163,7 @@ function getFocusables(container = document.body, options = {}) {
163
163
  composed = false,
164
164
  filter,
165
165
  include,
166
+ skipNegativeTabIndexCheck = false,
166
167
  skipVisibilityCheck = false
167
168
  } = options;
168
169
  if (typeof composed !== "boolean") {
@@ -181,11 +182,22 @@ function getFocusables(container = document.body, options = {}) {
181
182
  );
182
183
  include = void 0;
183
184
  }
185
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
186
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
187
+ skipNegativeTabIndexCheck = false;
188
+ }
189
+ if (typeof skipVisibilityCheck !== "boolean") {
190
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
191
+ skipVisibilityCheck = false;
192
+ }
184
193
  const elements = [];
185
194
  if (composed || include) {
186
195
  let traverse2 = function(node) {
187
196
  if (node instanceof Element) {
188
- if (isFocusable(node, { skipVisibilityCheck }) || include?.(node)) {
197
+ if (isFocusable(node, {
198
+ skipNegativeTabIndexCheck,
199
+ skipVisibilityCheck
200
+ }) || include?.(node)) {
189
201
  elements[elements.length] = node;
190
202
  }
191
203
  }
@@ -206,7 +218,10 @@ function getFocusables(container = document.body, options = {}) {
206
218
  if (!(candidate instanceof Element)) {
207
219
  continue;
208
220
  }
209
- if (isFocusable(candidate, { skipVisibilityCheck })) {
221
+ if (isFocusable(candidate, {
222
+ skipNegativeTabIndexCheck,
223
+ skipVisibilityCheck
224
+ })) {
210
225
  elements[elements.length] = candidate;
211
226
  }
212
227
  }
@@ -219,7 +234,11 @@ function isFocusable(element, options = {}) {
219
234
  console.warn("Invalid element");
220
235
  return false;
221
236
  }
222
- let { skipVisibilityCheck = false } = options;
237
+ let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
238
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
239
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
240
+ skipNegativeTabIndexCheck = false;
241
+ }
223
242
  if (typeof skipVisibilityCheck !== "boolean") {
224
243
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
225
244
  skipVisibilityCheck = false;
@@ -227,10 +246,12 @@ function isFocusable(element, options = {}) {
227
246
  if (element.hasAttribute("hidden") || isInert(element)) {
228
247
  return false;
229
248
  }
230
- if (getTabIndex(element) < 0) {
249
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
231
250
  return false;
232
251
  }
233
- if (!element.matches(FOCUSABLE_SELECTOR)) {
252
+ if (!element.matches(
253
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
254
+ )) {
234
255
  return false;
235
256
  }
236
257
  if (isDisabledDeep(element)) {
@@ -391,7 +412,7 @@ function createRovingTabIndex(container, options = {}) {
391
412
  console.warn("Invalid navigationOnly option. Fallback: false.");
392
413
  navigationOnly = false;
393
414
  }
394
- if (typeof selector !== "undefined" && typeof selector !== "string") {
415
+ if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
395
416
  console.warn(
396
417
  "Invalid selector. Fallback: all focusable elements (undefined)."
397
418
  );
@@ -405,13 +426,14 @@ function createRovingTabIndex(container, options = {}) {
405
426
  console.warn("Invalid wrap option. Fallback: false.");
406
427
  wrap = false;
407
428
  }
408
- const roving = new RovingTabIndex(container, {
409
- direction,
410
- navigationOnly,
411
- selector,
412
- typeahead,
413
- wrap
414
- });
429
+ const settings = { navigationOnly, typeahead, wrap };
430
+ if (direction) {
431
+ Object.assign(settings, { direction });
432
+ }
433
+ if (selector) {
434
+ Object.assign(settings, { selector });
435
+ }
436
+ const roving = new RovingTabIndex(container, settings);
415
437
  return () => roving.destroy();
416
438
  }
417
439
  var RovingTabIndex = class {
@@ -453,8 +475,8 @@ var RovingTabIndex = class {
453
475
  if (!event.composedPath().includes(this.#container)) {
454
476
  return;
455
477
  }
456
- const { key, altKey, ctrlKey, metaKey } = event;
457
- if (altKey || ctrlKey || metaKey) {
478
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
479
+ if (altKey || ctrlKey || metaKey || shiftKey) {
458
480
  return;
459
481
  }
460
482
  const { direction, typeahead, wrap } = this.#options;
@@ -517,14 +539,7 @@ var RovingTabIndex = class {
517
539
  focusElement(focusable);
518
540
  };
519
541
  #update(active) {
520
- const current = /* @__PURE__ */ new Set([
521
- ...this.#getFocusables(),
522
- ...getFocusables(this.#container, {
523
- composed: true,
524
- filter: this.#selectorFilter,
525
- skipVisibilityCheck: true
526
- })
527
- ]);
542
+ const current = new Set(this.#getFocusables());
528
543
  for (const focusable of this.#focusables) {
529
544
  if (current.has(focusable)) {
530
545
  continue;
@@ -588,8 +603,7 @@ var RovingTabIndex = class {
588
603
  return getFocusables(this.#container, {
589
604
  composed: true,
590
605
  filter: this.#selectorFilter,
591
- include: (element) => this.#focusables.has(element),
592
- skipVisibilityCheck: true
606
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly
593
607
  });
594
608
  }
595
609
  };
@@ -713,8 +727,6 @@ var Accordion = class _Accordion {
713
727
  this.#rootElement.removeAttribute("data-accordion-initialized");
714
728
  }
715
729
  #initialize() {
716
- this.#eventController = new AbortController();
717
- const { signal } = this.#eventController;
718
730
  saveAttributes(this.#triggerElements, [
719
731
  "aria-controls",
720
732
  "aria-disabled",
@@ -722,13 +734,15 @@ var Accordion = class _Accordion {
722
734
  "style",
723
735
  "tabindex"
724
736
  ]);
737
+ saveAttributes(this.#contentElements, ["aria-labelledby", "id", "role"]);
738
+ this.#eventController = new AbortController();
739
+ const { signal } = this.#eventController;
725
740
  this.#triggerElements.forEach((trigger2, i) => {
726
741
  const id = Math.random().toString(36).slice(-8);
727
742
  const content2 = this.#contentElements[i];
728
743
  if (!content2) {
729
744
  return;
730
745
  }
731
- saveAttributes([content2], ["aria-labelledby", "id", "role"]);
732
746
  content2.id ||= `accordion-content-${id}`;
733
747
  addTokenToAttribute(trigger2, "aria-controls", content2.id);
734
748
  trigger2.setAttribute(
@@ -878,7 +892,7 @@ function waitAnimationFinish(animation) {
878
892
  * Accordion
879
893
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
880
894
  *
881
- * @version 1.4.0
895
+ * @version 1.4.2
882
896
  * @author Yusuke Kamiyamane
883
897
  * @license MIT
884
898
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -890,7 +904,7 @@ function waitAnimationFinish(animation) {
890
904
  (**
891
905
  * Attributes Utils
892
906
  *
893
- * @version 1.0.5
907
+ * @version 1.1.0
894
908
  * @author Yusuke Kamiyamane
895
909
  * @license MIT
896
910
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -914,7 +928,7 @@ function waitAnimationFinish(animation) {
914
928
  * Lightweight roving tabindex utility with fully focus management.
915
929
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
916
930
  *
917
- * @version 1.3.1
931
+ * @version 2.0.4
918
932
  * @author Yusuke Kamiyamane
919
933
  * @license MIT
920
934
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -926,7 +940,7 @@ function waitAnimationFinish(animation) {
926
940
  (**
927
941
  * Attributes Utils
928
942
  *
929
- * @version 1.0.5
943
+ * @version 1.1.0
930
944
  * @author Yusuke Kamiyamane
931
945
  * @license MIT
932
946
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -939,7 +953,7 @@ function waitAnimationFinish(animation) {
939
953
  * High-precision focus management utility with full composed tree support.
940
954
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
941
955
  *
942
- * @version 4.2.0
956
+ * @version 4.3.1
943
957
  * @author Yusuke Kamiyamane
944
958
  * @license MIT
945
959
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/accordion",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "WAI-ARIA compliant accordion pattern implementation in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "homepage": "https://github.com/y14e/accordion#readme",
45
45
  "devDependencies": {
46
- "@y14e/attributes-utils": "^1.0.5",
46
+ "@y14e/attributes-utils": "^1.1.0",
47
47
  "@y14e/button": "^1.0.0",
48
- "@y14e/roving-tabindex": "^1.3.1",
48
+ "@y14e/roving-tabindex": "^2.0.4",
49
49
  "bun-types": "latest",
50
50
  "tsup": "^8.0.0",
51
51
  "typescript": "^5.6.0",