@y14e/disclosure-css 1.3.1 → 1.3.3

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/disclosure-css
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import Disclosure from '@y14e/disclosure-css';
13
+ import Disclosure from '@y14e/disclosure-css@1.3.3';
14
14
 
15
15
  // CDNs
16
- import Disclosure from 'https://esm.sh/@y14e/disclosure-css'
16
+ import Disclosure from 'https://esm.sh/@y14e/disclosure-css@1.3.3';
17
17
  // or
18
- import Disclosure from 'https://cdn.jsdelivr.net/npm/@y14e/disclosure-css/+esm';
18
+ import Disclosure from 'https://cdn.jsdelivr.net/npm/@y14e/disclosure-css@1.3.3/+esm';
19
19
  // or
20
- import Disclosure from 'https://unpkg.com/@y14e/disclosure-css/dist/index.js';
20
+ import Disclosure from 'https://esm.unpkg.com/@y14e/disclosure-css@1.3.3';
21
21
  ```
22
22
 
23
23
  ## Usage
package/dist/index.cjs CHANGED
@@ -82,7 +82,13 @@ function getFocusables(container = document.body, options = {}) {
82
82
  console.warn("Invalid container element. Fallback: <body> element.");
83
83
  container = document.body;
84
84
  }
85
- let { composed = false, filter, include } = options;
85
+ let {
86
+ composed = false,
87
+ filter,
88
+ include,
89
+ skipNegativeTabIndexCheck = false,
90
+ skipVisibilityCheck = false
91
+ } = options;
86
92
  if (typeof composed !== "boolean") {
87
93
  console.warn("Invalid composed option. Fallback: false.");
88
94
  composed = false;
@@ -99,11 +105,22 @@ function getFocusables(container = document.body, options = {}) {
99
105
  );
100
106
  include = void 0;
101
107
  }
108
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
109
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
110
+ skipNegativeTabIndexCheck = false;
111
+ }
112
+ if (typeof skipVisibilityCheck !== "boolean") {
113
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
114
+ skipVisibilityCheck = false;
115
+ }
102
116
  const elements = [];
103
117
  if (composed || include) {
104
118
  let traverse2 = function(node) {
105
119
  if (node instanceof Element) {
106
- if (isFocusable(node) || include?.(node)) {
120
+ if (isFocusable(node, {
121
+ skipNegativeTabIndexCheck,
122
+ skipVisibilityCheck
123
+ }) || include?.(node)) {
107
124
  elements[elements.length] = node;
108
125
  }
109
126
  }
@@ -124,7 +141,10 @@ function getFocusables(container = document.body, options = {}) {
124
141
  if (!(candidate instanceof Element)) {
125
142
  continue;
126
143
  }
127
- if (isFocusable(candidate)) {
144
+ if (isFocusable(candidate, {
145
+ skipNegativeTabIndexCheck,
146
+ skipVisibilityCheck
147
+ })) {
128
148
  elements[elements.length] = candidate;
129
149
  }
130
150
  }
@@ -132,24 +152,35 @@ function getFocusables(container = document.body, options = {}) {
132
152
  const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
133
153
  return filter ? unfiltered.filter(filter) : unfiltered;
134
154
  }
135
- function isFocusable(element) {
155
+ function isFocusable(element, options = {}) {
136
156
  if (!(element instanceof Element)) {
137
157
  console.warn("Invalid element");
138
158
  return false;
139
159
  }
160
+ let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
161
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
162
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
163
+ skipNegativeTabIndexCheck = false;
164
+ }
165
+ if (typeof skipVisibilityCheck !== "boolean") {
166
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
167
+ skipVisibilityCheck = false;
168
+ }
140
169
  if (element.hasAttribute("hidden") || isInert(element)) {
141
170
  return false;
142
171
  }
143
- if (getTabIndex(element) < 0) {
172
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
144
173
  return false;
145
174
  }
146
- if (!element.matches(FOCUSABLE_SELECTOR)) {
175
+ if (!element.matches(
176
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
177
+ )) {
147
178
  return false;
148
179
  }
149
180
  if (isDisabledDeep(element)) {
150
181
  return false;
151
182
  }
152
- if (!element.checkVisibility({
183
+ if (!skipVisibilityCheck && !element.checkVisibility({
153
184
  contentVisibilityAuto: true,
154
185
  opacityProperty: true,
155
186
  visibilityProperty: true
@@ -304,7 +335,7 @@ function createRovingTabIndex(container, options = {}) {
304
335
  console.warn("Invalid navigationOnly option. Fallback: false.");
305
336
  navigationOnly = false;
306
337
  }
307
- if (typeof selector !== "undefined" && typeof selector !== "string") {
338
+ if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
308
339
  console.warn(
309
340
  "Invalid selector. Fallback: all focusable elements (undefined)."
310
341
  );
@@ -318,13 +349,14 @@ function createRovingTabIndex(container, options = {}) {
318
349
  console.warn("Invalid wrap option. Fallback: false.");
319
350
  wrap = false;
320
351
  }
321
- const roving = new RovingTabIndex(container, {
322
- direction,
323
- navigationOnly,
324
- selector,
325
- typeahead,
326
- wrap
327
- });
352
+ const settings = { navigationOnly, typeahead, wrap };
353
+ if (direction) {
354
+ Object.assign(settings, { direction });
355
+ }
356
+ if (selector) {
357
+ Object.assign(settings, { selector });
358
+ }
359
+ const roving = new RovingTabIndex(container, settings);
328
360
  return () => roving.destroy();
329
361
  }
330
362
  var RovingTabIndex = class {
@@ -366,8 +398,8 @@ var RovingTabIndex = class {
366
398
  if (!event.composedPath().includes(this.#container)) {
367
399
  return;
368
400
  }
369
- const { key, altKey, ctrlKey, metaKey } = event;
370
- if (altKey || ctrlKey || metaKey) {
401
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
402
+ if (altKey || ctrlKey || metaKey || shiftKey) {
371
403
  return;
372
404
  }
373
405
  const { direction, typeahead, wrap } = this.#options;
@@ -430,13 +462,7 @@ var RovingTabIndex = class {
430
462
  focusElement(focusable);
431
463
  };
432
464
  #update(active) {
433
- const current = /* @__PURE__ */ new Set([
434
- ...this.#getFocusables(),
435
- ...getFocusables(this.#container, {
436
- composed: true,
437
- filter: this.#selectorFilter
438
- })
439
- ]);
465
+ const current = new Set(this.#getFocusables());
440
466
  for (const focusable of this.#focusables) {
441
467
  if (current.has(focusable)) {
442
468
  continue;
@@ -500,7 +526,7 @@ var RovingTabIndex = class {
500
526
  return getFocusables(this.#container, {
501
527
  composed: true,
502
528
  filter: this.#selectorFilter,
503
- include: (element) => this.#focusables.has(element)
529
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly
504
530
  });
505
531
  }
506
532
  };
@@ -608,12 +634,16 @@ var Disclosure = class {
608
634
  this.#rootElement.removeAttribute("data-disclosure-initialized");
609
635
  }
610
636
  #initialize() {
637
+ saveAttributes(this.#summaryElements, [
638
+ "aria-disabled",
639
+ "style",
640
+ "tabindex"
641
+ ]);
611
642
  this.#summaryElements.forEach((summary) => {
612
643
  if (!summary) {
613
644
  return;
614
645
  }
615
646
  if (!isFocusable2(summary)) {
616
- saveAttributes([summary], ["aria-disabled", "style", "tabindex"]);
617
647
  summary.setAttribute("aria-disabled", "true");
618
648
  summary.setAttribute("tabindex", "-1");
619
649
  summary.style.setProperty("pointer-events", "none");
@@ -644,7 +674,7 @@ function isFocusable2(element) {
644
674
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
645
675
  * Using the <details> and <summary> element.
646
676
  *
647
- * @version 1.3.1
677
+ * @version 1.3.3
648
678
  * @author Yusuke Kamiyamane
649
679
  * @license MIT
650
680
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -656,7 +686,7 @@ function isFocusable2(element) {
656
686
  (**
657
687
  * Attributes Utils
658
688
  *
659
- * @version 1.0.5
689
+ * @version 1.1.0
660
690
  * @author Yusuke Kamiyamane
661
691
  * @license MIT
662
692
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -669,7 +699,7 @@ function isFocusable2(element) {
669
699
  * Lightweight roving tabindex utility with fully focus management.
670
700
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
671
701
  *
672
- * @version 1.3.0
702
+ * @version 2.0.4
673
703
  * @author Yusuke Kamiyamane
674
704
  * @license MIT
675
705
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -681,7 +711,7 @@ function isFocusable2(element) {
681
711
  (**
682
712
  * Attributes Utils
683
713
  *
684
- * @version 1.0.5
714
+ * @version 1.1.0
685
715
  * @author Yusuke Kamiyamane
686
716
  * @license MIT
687
717
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -694,7 +724,7 @@ function isFocusable2(element) {
694
724
  * High-precision focus management utility with full composed tree support.
695
725
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
696
726
  *
697
- * @version 4.1.8
727
+ * @version 4.3.1
698
728
  * @author Yusuke Kamiyamane
699
729
  * @license MIT
700
730
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
4
4
  * Using the <details> and <summary> element.
5
5
  *
6
- * @version 1.3.1
6
+ * @version 1.3.3
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
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
4
4
  * Using the <details> and <summary> element.
5
5
  *
6
- * @version 1.3.1
6
+ * @version 1.3.3
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -80,7 +80,13 @@ function getFocusables(container = document.body, options = {}) {
80
80
  console.warn("Invalid container element. Fallback: <body> element.");
81
81
  container = document.body;
82
82
  }
83
- let { composed = false, filter, include } = options;
83
+ let {
84
+ composed = false,
85
+ filter,
86
+ include,
87
+ skipNegativeTabIndexCheck = false,
88
+ skipVisibilityCheck = false
89
+ } = options;
84
90
  if (typeof composed !== "boolean") {
85
91
  console.warn("Invalid composed option. Fallback: false.");
86
92
  composed = false;
@@ -97,11 +103,22 @@ function getFocusables(container = document.body, options = {}) {
97
103
  );
98
104
  include = void 0;
99
105
  }
106
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
107
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
108
+ skipNegativeTabIndexCheck = false;
109
+ }
110
+ if (typeof skipVisibilityCheck !== "boolean") {
111
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
112
+ skipVisibilityCheck = false;
113
+ }
100
114
  const elements = [];
101
115
  if (composed || include) {
102
116
  let traverse2 = function(node) {
103
117
  if (node instanceof Element) {
104
- if (isFocusable(node) || include?.(node)) {
118
+ if (isFocusable(node, {
119
+ skipNegativeTabIndexCheck,
120
+ skipVisibilityCheck
121
+ }) || include?.(node)) {
105
122
  elements[elements.length] = node;
106
123
  }
107
124
  }
@@ -122,7 +139,10 @@ function getFocusables(container = document.body, options = {}) {
122
139
  if (!(candidate instanceof Element)) {
123
140
  continue;
124
141
  }
125
- if (isFocusable(candidate)) {
142
+ if (isFocusable(candidate, {
143
+ skipNegativeTabIndexCheck,
144
+ skipVisibilityCheck
145
+ })) {
126
146
  elements[elements.length] = candidate;
127
147
  }
128
148
  }
@@ -130,24 +150,35 @@ function getFocusables(container = document.body, options = {}) {
130
150
  const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
131
151
  return filter ? unfiltered.filter(filter) : unfiltered;
132
152
  }
133
- function isFocusable(element) {
153
+ function isFocusable(element, options = {}) {
134
154
  if (!(element instanceof Element)) {
135
155
  console.warn("Invalid element");
136
156
  return false;
137
157
  }
158
+ let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
159
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
160
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
161
+ skipNegativeTabIndexCheck = false;
162
+ }
163
+ if (typeof skipVisibilityCheck !== "boolean") {
164
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
165
+ skipVisibilityCheck = false;
166
+ }
138
167
  if (element.hasAttribute("hidden") || isInert(element)) {
139
168
  return false;
140
169
  }
141
- if (getTabIndex(element) < 0) {
170
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
142
171
  return false;
143
172
  }
144
- if (!element.matches(FOCUSABLE_SELECTOR)) {
173
+ if (!element.matches(
174
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
175
+ )) {
145
176
  return false;
146
177
  }
147
178
  if (isDisabledDeep(element)) {
148
179
  return false;
149
180
  }
150
- if (!element.checkVisibility({
181
+ if (!skipVisibilityCheck && !element.checkVisibility({
151
182
  contentVisibilityAuto: true,
152
183
  opacityProperty: true,
153
184
  visibilityProperty: true
@@ -302,7 +333,7 @@ function createRovingTabIndex(container, options = {}) {
302
333
  console.warn("Invalid navigationOnly option. Fallback: false.");
303
334
  navigationOnly = false;
304
335
  }
305
- if (typeof selector !== "undefined" && typeof selector !== "string") {
336
+ if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
306
337
  console.warn(
307
338
  "Invalid selector. Fallback: all focusable elements (undefined)."
308
339
  );
@@ -316,13 +347,14 @@ function createRovingTabIndex(container, options = {}) {
316
347
  console.warn("Invalid wrap option. Fallback: false.");
317
348
  wrap = false;
318
349
  }
319
- const roving = new RovingTabIndex(container, {
320
- direction,
321
- navigationOnly,
322
- selector,
323
- typeahead,
324
- wrap
325
- });
350
+ const settings = { navigationOnly, typeahead, wrap };
351
+ if (direction) {
352
+ Object.assign(settings, { direction });
353
+ }
354
+ if (selector) {
355
+ Object.assign(settings, { selector });
356
+ }
357
+ const roving = new RovingTabIndex(container, settings);
326
358
  return () => roving.destroy();
327
359
  }
328
360
  var RovingTabIndex = class {
@@ -364,8 +396,8 @@ var RovingTabIndex = class {
364
396
  if (!event.composedPath().includes(this.#container)) {
365
397
  return;
366
398
  }
367
- const { key, altKey, ctrlKey, metaKey } = event;
368
- if (altKey || ctrlKey || metaKey) {
399
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
400
+ if (altKey || ctrlKey || metaKey || shiftKey) {
369
401
  return;
370
402
  }
371
403
  const { direction, typeahead, wrap } = this.#options;
@@ -428,13 +460,7 @@ var RovingTabIndex = class {
428
460
  focusElement(focusable);
429
461
  };
430
462
  #update(active) {
431
- const current = /* @__PURE__ */ new Set([
432
- ...this.#getFocusables(),
433
- ...getFocusables(this.#container, {
434
- composed: true,
435
- filter: this.#selectorFilter
436
- })
437
- ]);
463
+ const current = new Set(this.#getFocusables());
438
464
  for (const focusable of this.#focusables) {
439
465
  if (current.has(focusable)) {
440
466
  continue;
@@ -498,7 +524,7 @@ var RovingTabIndex = class {
498
524
  return getFocusables(this.#container, {
499
525
  composed: true,
500
526
  filter: this.#selectorFilter,
501
- include: (element) => this.#focusables.has(element)
527
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly
502
528
  });
503
529
  }
504
530
  };
@@ -606,12 +632,16 @@ var Disclosure = class {
606
632
  this.#rootElement.removeAttribute("data-disclosure-initialized");
607
633
  }
608
634
  #initialize() {
635
+ saveAttributes(this.#summaryElements, [
636
+ "aria-disabled",
637
+ "style",
638
+ "tabindex"
639
+ ]);
609
640
  this.#summaryElements.forEach((summary) => {
610
641
  if (!summary) {
611
642
  return;
612
643
  }
613
644
  if (!isFocusable2(summary)) {
614
- saveAttributes([summary], ["aria-disabled", "style", "tabindex"]);
615
645
  summary.setAttribute("aria-disabled", "true");
616
646
  summary.setAttribute("tabindex", "-1");
617
647
  summary.style.setProperty("pointer-events", "none");
@@ -642,7 +672,7 @@ function isFocusable2(element) {
642
672
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
643
673
  * Using the <details> and <summary> element.
644
674
  *
645
- * @version 1.3.1
675
+ * @version 1.3.3
646
676
  * @author Yusuke Kamiyamane
647
677
  * @license MIT
648
678
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -654,7 +684,7 @@ function isFocusable2(element) {
654
684
  (**
655
685
  * Attributes Utils
656
686
  *
657
- * @version 1.0.5
687
+ * @version 1.1.0
658
688
  * @author Yusuke Kamiyamane
659
689
  * @license MIT
660
690
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -667,7 +697,7 @@ function isFocusable2(element) {
667
697
  * Lightweight roving tabindex utility with fully focus management.
668
698
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
669
699
  *
670
- * @version 1.3.0
700
+ * @version 2.0.4
671
701
  * @author Yusuke Kamiyamane
672
702
  * @license MIT
673
703
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -679,7 +709,7 @@ function isFocusable2(element) {
679
709
  (**
680
710
  * Attributes Utils
681
711
  *
682
- * @version 1.0.5
712
+ * @version 1.1.0
683
713
  * @author Yusuke Kamiyamane
684
714
  * @license MIT
685
715
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -692,7 +722,7 @@ function isFocusable2(element) {
692
722
  * High-precision focus management utility with full composed tree support.
693
723
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
694
724
  *
695
- * @version 4.1.8
725
+ * @version 4.3.1
696
726
  * @author Yusuke Kamiyamane
697
727
  * @license MIT
698
728
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/disclosure-css",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "WAI-ARIA compliant disclosure pattern implementation in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "homepage": "https://github.com/y14e/disclosure-css#readme",
45
45
  "devDependencies": {
46
- "@y14e/attributes-utils": "^1.0.5",
47
- "@y14e/roving-tabindex": "^1.3.0",
46
+ "@y14e/attributes-utils": "^1.1.0",
47
+ "@y14e/roving-tabindex": "^2.0.4",
48
48
  "bun-types": "latest",
49
49
  "tsup": "^8.0.0",
50
50
  "typescript": "^5.6.0"