@graupl/core 1.0.0-beta.19 → 1.0.0-beta.21

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.
Files changed (89) hide show
  1. package/dist/css/component/badge.css +2 -0
  2. package/dist/css/component/badge.css.map +1 -0
  3. package/dist/css/component/disclosure.css +2 -0
  4. package/dist/css/component/disclosure.css.map +1 -0
  5. package/dist/css/component.css +1 -1
  6. package/dist/css/component.css.map +1 -1
  7. package/dist/css/graupl.css +1 -1
  8. package/dist/css/graupl.css.map +1 -1
  9. package/dist/css/layout.css +1 -1
  10. package/dist/css/layout.css.map +1 -1
  11. package/dist/js/accordion.js +2 -2
  12. package/dist/js/accordion.js.map +1 -1
  13. package/dist/js/alert.js.map +1 -1
  14. package/dist/js/carousel.js +2 -2
  15. package/dist/js/carousel.js.map +1 -1
  16. package/dist/js/component/accordion.cjs.js +2 -2
  17. package/dist/js/component/accordion.cjs.js.map +1 -1
  18. package/dist/js/component/accordion.es.js +2 -2
  19. package/dist/js/component/accordion.es.js.map +1 -1
  20. package/dist/js/component/accordion.iife.js +2 -2
  21. package/dist/js/component/accordion.iife.js.map +1 -1
  22. package/dist/js/component/alert.cjs.js.map +1 -1
  23. package/dist/js/component/alert.es.js.map +1 -1
  24. package/dist/js/component/alert.iife.js +2 -2
  25. package/dist/js/component/alert.iife.js.map +1 -1
  26. package/dist/js/component/carousel.cjs.js +2 -2
  27. package/dist/js/component/carousel.cjs.js.map +1 -1
  28. package/dist/js/component/carousel.es.js +2 -2
  29. package/dist/js/component/carousel.es.js.map +1 -1
  30. package/dist/js/component/carousel.iife.js +2 -2
  31. package/dist/js/component/carousel.iife.js.map +1 -1
  32. package/dist/js/component/disclosure.cjs.js +5 -0
  33. package/dist/js/component/disclosure.cjs.js.map +1 -0
  34. package/dist/js/component/disclosure.es.js +5 -0
  35. package/dist/js/component/disclosure.es.js.map +1 -0
  36. package/dist/js/component/disclosure.iife.js +5 -0
  37. package/dist/js/component/disclosure.iife.js.map +1 -0
  38. package/dist/js/generator/accordion.cjs.js +2 -2
  39. package/dist/js/generator/accordion.cjs.js.map +1 -1
  40. package/dist/js/generator/accordion.es.js +2 -2
  41. package/dist/js/generator/accordion.es.js.map +1 -1
  42. package/dist/js/generator/accordion.iife.js +2 -2
  43. package/dist/js/generator/accordion.iife.js.map +1 -1
  44. package/dist/js/generator/alert.cjs.js.map +1 -1
  45. package/dist/js/generator/alert.es.js.map +1 -1
  46. package/dist/js/generator/alert.iife.js +2 -2
  47. package/dist/js/generator/alert.iife.js.map +1 -1
  48. package/dist/js/generator/carousel.cjs.js +2 -2
  49. package/dist/js/generator/carousel.cjs.js.map +1 -1
  50. package/dist/js/generator/carousel.es.js +2 -2
  51. package/dist/js/generator/carousel.es.js.map +1 -1
  52. package/dist/js/generator/carousel.iife.js +2 -2
  53. package/dist/js/generator/carousel.iife.js.map +1 -1
  54. package/dist/js/generator/disclosure.cjs.js +5 -0
  55. package/dist/js/generator/disclosure.cjs.js.map +1 -0
  56. package/dist/js/generator/disclosure.es.js +5 -0
  57. package/dist/js/generator/disclosure.es.js.map +1 -0
  58. package/dist/js/generator/disclosure.iife.js +5 -0
  59. package/dist/js/generator/disclosure.iife.js.map +1 -0
  60. package/dist/js/generator/navigation.cjs.js.map +1 -1
  61. package/dist/js/generator/navigation.es.js.map +1 -1
  62. package/dist/js/generator/navigation.iife.js +1 -1
  63. package/dist/js/generator/navigation.iife.js.map +1 -1
  64. package/dist/js/graupl.js +6 -4
  65. package/dist/js/graupl.js.map +1 -1
  66. package/dist/js/navigation.js.map +1 -1
  67. package/package.json +1 -1
  68. package/scss/component/badge.scss +3 -0
  69. package/scss/component/disclosure.scss +3 -0
  70. package/src/js/TransactionalValue.js +140 -0
  71. package/src/js/accordion/Accordion.js +4 -4
  72. package/src/js/carousel/Carousel.js +1 -1
  73. package/src/js/disclosure/Disclosure.js +1378 -0
  74. package/src/js/disclosure/generator.js +47 -0
  75. package/src/js/disclosure/index.js +5 -0
  76. package/src/js/main.js +2 -0
  77. package/src/js/validate.js +7 -7
  78. package/src/scss/_index.scss +8 -8
  79. package/src/scss/base/_index.scss +4 -4
  80. package/src/scss/component/_index.scss +10 -8
  81. package/src/scss/component/badge/_defaults.scss +47 -0
  82. package/src/scss/component/badge/_index.scss +201 -0
  83. package/src/scss/component/badge/_variables.scss +112 -0
  84. package/src/scss/component/disclosure/_defaults.scss +45 -0
  85. package/src/scss/component/disclosure/_index.scss +214 -0
  86. package/src/scss/component/disclosure/_variables.scss +205 -0
  87. package/src/scss/layout/_index.scss +3 -3
  88. package/src/scss/theme/_index.scss +2 -2
  89. package/src/scss/utilities/_index.scss +21 -21
@@ -0,0 +1,1378 @@
1
+ /**
2
+ * @file
3
+ * The Disclosure class.
4
+ */
5
+
6
+ import {
7
+ isValidInstance,
8
+ isValidClassList,
9
+ isValidType,
10
+ isValidState,
11
+ isValidEvent,
12
+ isTag,
13
+ isQuerySelector,
14
+ } from "../validate.js";
15
+ import { addClass, removeClass } from "../domHelpers.js";
16
+ import { keyPress, preventEvent } from "../eventHandlers.js";
17
+ import storage from "../storage.js";
18
+ import { TransactionalValue } from "../TransactionalValue.js";
19
+
20
+ class Disclosure {
21
+ /**
22
+ * The DOM elements within the disclosure.
23
+ *
24
+ * @protected
25
+ *
26
+ * @type {Object<HTMLElement>}
27
+ *
28
+ * @property {HTMLElement} controller - The disclosure toggle element.
29
+ * @property {HTMLElement} disclosure - The disclosure element.
30
+ * @property {HTMLElement} content - The disclosure content element.
31
+ */
32
+ _dom = {
33
+ controller: null,
34
+ disclosure: null,
35
+ content: null,
36
+ };
37
+
38
+ /**
39
+ * The DOM elements within the disclosure that cannot be reset or generated by the disclosure itself.
40
+ *
41
+ * @protected
42
+ *
43
+ * @type {string[]}
44
+ */
45
+ _domLock = ["controller", "disclosure"];
46
+
47
+ /**
48
+ * The query selectors used by the disclosure.
49
+ *
50
+ * @protected
51
+ *
52
+ * @type {Object<string>}
53
+ *
54
+ * @property {string} content - The query selector for the disclosure content.
55
+ */
56
+ _selectors = {
57
+ content: "",
58
+ };
59
+
60
+ /**
61
+ * The classes to apply when the disclosure is in various states.
62
+ *
63
+ * @protected
64
+ *
65
+ * @type {Object<string, string[]>}
66
+ *
67
+ * @property {string|string[]} open - The class(es) to apply when the disclosure is open.
68
+ * @property {string|string[]} close - The class(es) to apply when the disclousre is closed.
69
+ * @property {string|string[]} tranistion - The class(es) to apply when the disclosure is transitioning between states.
70
+ * @property {string|string[]} initialize - The class(es) to apply when the disclosure is initializing.
71
+ */
72
+ _classes = {
73
+ open: "show",
74
+ close: "hide",
75
+ transition: "transitioning",
76
+ initialize: "initializing",
77
+ };
78
+
79
+ /**
80
+ * The duration times (in milliseconds) for various things throughout the disclosure.
81
+ *
82
+ * @protected
83
+ *
84
+ * @type {Object<number>}
85
+ *
86
+ * @property {number} transition - The duration time (in milliseconds) for the transition between open and closed states.
87
+ * @property {number} open - The duration time (in milliseconds) for the transition from closed to open states.
88
+ * @property {number} close - The duration time (in milliseconds) for the transition from open to closed states.
89
+ */
90
+ _durations = {
91
+ transition: 5000,
92
+ open: -1,
93
+ close: -1,
94
+ };
95
+
96
+ /**
97
+ * The current state of the disclosure's focus.
98
+ *
99
+ * @protected
100
+ *
101
+ * @type {string}
102
+ */
103
+ _focusState = "none";
104
+
105
+ /**
106
+ * This last event triggered on the disclosure.
107
+ *
108
+ * @protected
109
+ *
110
+ * @type {string}
111
+ */
112
+ _currentEvent = "none";
113
+
114
+ /**
115
+ * The open state of the disclosure.
116
+ *
117
+ * @protected
118
+ *
119
+ * @type {TransactionalValue<boolean>}
120
+ */
121
+ _open = new TransactionalValue(false);
122
+
123
+ /**
124
+ * A value to force the disclosure open when the breakpoint width is passed.
125
+ *
126
+ * @protected
127
+ *
128
+ * @type {boolean}
129
+ */
130
+ _shouldOpen = false;
131
+
132
+ /**
133
+ * Whether or not to close the disclosure when it loses focus in the DOM.
134
+ *
135
+ * @protected
136
+ *
137
+ * @type {boolean}
138
+ */
139
+ _closeOnBlur = false;
140
+
141
+ /**
142
+ * The width of the screen (in pixels) that the disclosure will automatically open/close itself.
143
+ *
144
+ * @protected
145
+ *
146
+ * @type {number}
147
+ */
148
+ _breakpointWidth = -1;
149
+
150
+ /**
151
+ * This ResizeObserver for the disclosure.
152
+ *
153
+ * @protected
154
+ *
155
+ * @type {ResizeObserver|null}
156
+ */
157
+ _observer = null;
158
+
159
+ /**
160
+ * The event that is triggered when the disclosure expands.
161
+ *
162
+ * @protected
163
+ *
164
+ * @event grauplDisclosureExpand
165
+ *
166
+ * @type {CustomEvent}
167
+ *
168
+ * @property {boolean} bubbles - A flag to bubble the event.
169
+ * @property {Object<Disclosure>} details - The details object containing the Disclosure itself.
170
+ */
171
+ _expandEvent = new CustomEvent("grauplDisclosureExpand", {
172
+ bubbles: true,
173
+ detail: { disclosure: this },
174
+ });
175
+
176
+ /**
177
+ * The event that is triggered when the disclosure collapses.
178
+ *
179
+ * @protected
180
+ *
181
+ * @event grauplDisclosureCollapse
182
+ *
183
+ * @type {CustomEvent}
184
+ *
185
+ * @property {boolean} bubbles - A flag to bubble the event.
186
+ * @property {Object<Disclosure>} details - The details object containing the Disclosure itself.
187
+ */
188
+ _collapseEvent = new CustomEvent("grauplDisclosureCollapse", {
189
+ bubbles: true,
190
+ detail: { disclosure: this },
191
+ });
192
+
193
+ /**
194
+ * The prefix to use for CSS custom properties.
195
+ *
196
+ * @protected
197
+ *
198
+ * @type {string}
199
+ */
200
+ _prefix = "graupl-";
201
+
202
+ /**
203
+ * An array of error messages generated by the disclosure.
204
+ *
205
+ * @protected
206
+ *
207
+ * @type {string[]}
208
+ */
209
+ _errors = [];
210
+
211
+ /**
212
+ * Constructs a new `Disclosure`.
213
+ *
214
+ * @param {object} options - The options for generating the disclosure.
215
+ * @param {HTMLElement} options.disclosureElement - The disclosure element in the DOM.
216
+ * @param {HTMLElement} options.controllerElement - The disclosure toggle element in the DOM.
217
+ * @param {string} [options.disclosureContentSelector = .disclosure-content] - The query selector string for the disclosure content.
218
+ * @param {?(string|string[])} [options.openClass = show] - The class to apply when a disclosure is "open".
219
+ * @param {?(string|string[])} [options.closeClass = hide] - The class to apply when a disclosure is "closed".
220
+ * @param {?(string|string[])} [options.transitionClass = transitioning] - The class to apply when a disclosure is transitioning between "open" and "closed" states.
221
+ * @param {number} [options.transitionDuration = 250] - The duration of the transition between "open" and "closed" states (in milliseconds).
222
+ * @param {boolean} [options.openDuration = -1] - The duration of the transition from "closed" to "open" states (in milliseconds).
223
+ * @param {boolean} [options.closeDuration = -1] - The duration of the transition from "open" to "closed" states (in milliseconds).
224
+ * @param {boolean} [options.closeOnBlur = false] - Whether to close the disclosure when it loses focus in the dom.
225
+ * @param {boolean} [options.minWidth = -1] - The width of the screen (in pixels) that the disclosure will automatically open/close itself.
226
+ * @param {boolean} [options.autoOpen = false] - Whether to automatically open when above the minWidth.
227
+ * @param {?string} [options.prefix = graupl-] - The prefix to use for CSS custom properties.
228
+ * @param {?(string|string[])} [options.initializeClass = initializing] - The class to apply when a disclosure is initialzing.
229
+ * @param {boolean} [options.initialize = false] - Whether to initialize the disclosure upon construction.
230
+ */
231
+ constructor({
232
+ disclosureElement,
233
+ controllerElement,
234
+ disclosureContentSelector = ".disclosure-content",
235
+ openClass = "show",
236
+ closeClass = "hide",
237
+ transitionClass = "transitioning",
238
+ transitionDuration = 250,
239
+ openDuration = -1,
240
+ closeDuration = -1,
241
+ closeOnBlur = false,
242
+ minWidth = -1,
243
+ autoOpen = false,
244
+ prefix = "graupl-",
245
+ initializeClass = "initializing",
246
+ initialize = false,
247
+ }) {
248
+ // Set the DOM elements.
249
+ this._dom.disclosure = disclosureElement;
250
+ this._dom.controller = controllerElement;
251
+
252
+ // Set the DOM selectors.
253
+ this._selectors.content = disclosureContentSelector;
254
+
255
+ // Set the classes.
256
+ this._classes.open = openClass || "";
257
+ this._classes.close = closeClass || "";
258
+ this._classes.transition = transitionClass || "";
259
+ this._classes.initialize = initializeClass || "";
260
+
261
+ // Set the transition durations.
262
+ this._durations.transition = transitionDuration;
263
+ this._durations.open = openDuration;
264
+ this._durations.close = closeDuration;
265
+
266
+ // Set close on blur.
267
+ this._closeOnBlur = closeOnBlur;
268
+
269
+ // Set collapse width and auto open functionality.
270
+ this._breakpointWidth = minWidth;
271
+ this._shouldOpen = autoOpen;
272
+
273
+ // Set the prefix.
274
+ this._prefix = prefix;
275
+
276
+ if (initialize) {
277
+ this.initialize();
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Initializes the disclosure.
283
+ */
284
+ initialize() {
285
+ try {
286
+ if (!this._validate()) {
287
+ throw new Error(
288
+ `Graupl Disclosure: cannot initialize disclosure. The following errors have been found:\n - ${this.errors.join(
289
+ "\n - "
290
+ )}`
291
+ );
292
+ }
293
+
294
+ addClass(this._classes.initialize, this.dom.disclosure);
295
+
296
+ // Set up the DOM.
297
+ this._generateKey();
298
+ this._setDOMElements();
299
+ this._setIds();
300
+ this._setAriaAttributes();
301
+
302
+ // Handle events.
303
+ this._handleFocus();
304
+ this._handleClick();
305
+ this._handleKeydown();
306
+ this._handleKeyup();
307
+ this._handleResize();
308
+
309
+ // Set the custom props.
310
+ this._setTransitionDurations();
311
+
312
+ // Set up the storage.
313
+ storage.initializeStorage("disclosures");
314
+ storage.initializeStorage("disclosures");
315
+ storage.pushToStorage("disclosures", this.dom.disclosure.id, this);
316
+
317
+ if (
318
+ this.dom.controller.getAttribute("aria-expanded") === "true" ||
319
+ (this.shouldOpen && window.matchMedia(this.openQuery).matches)
320
+ ) {
321
+ this._expand(false, false);
322
+ } else {
323
+ this._collapse(false, false);
324
+ }
325
+
326
+ requestAnimationFrame(() => {
327
+ removeClass(this._classes.initialize, this.dom.disclosure);
328
+ });
329
+ } catch (error) {
330
+ console.error(error);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * The DOM elements within the disclosure.
336
+ *
337
+ * @readonly
338
+ *
339
+ * @type {Object<HTMLElement>}
340
+ *
341
+ * @see _dom
342
+ */
343
+ get dom() {
344
+ return this._dom;
345
+ }
346
+
347
+ /**
348
+ * The query selectors used by the disclosure.
349
+ *
350
+ * @readonly
351
+ *
352
+ * @type {Object<string>}
353
+ *
354
+ * @see _selectors
355
+ */
356
+ get selectors() {
357
+ return this._selectors;
358
+ }
359
+
360
+ /**
361
+ * The class(es) to apply when the disclosure is open.
362
+ *
363
+ * @type {string|string[]}
364
+ *
365
+ * @see _classes.open
366
+ */
367
+ get openClass() {
368
+ return this._classes.open;
369
+ }
370
+
371
+ set openClass(value) {
372
+ isValidClassList({ openClass: value });
373
+
374
+ if (this._classes.open !== value) {
375
+ this._classes.open = value;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * The class(es) to apply when the disclosure is closed.
381
+ *
382
+ * @type {string|string[]}
383
+ *
384
+ * @see _classes.close
385
+ */
386
+ get closeClass() {
387
+ return this._classes.close;
388
+ }
389
+
390
+ set closeClass(value) {
391
+ isValidClassList({ closeClass: value });
392
+
393
+ if (this._classes.close !== value) {
394
+ this._classes.close = value;
395
+ }
396
+ }
397
+
398
+ /**
399
+ * The class(es) to apply when the disclosure is transitioning between open and closed.
400
+ *
401
+ * @type {string|string[]}
402
+ *
403
+ * @see _classes.transition
404
+ */
405
+ get transitionClass() {
406
+ return this._classes.transition;
407
+ }
408
+
409
+ set transitionClass(value) {
410
+ isValidClassList({ transitionClass: value });
411
+
412
+ if (this._classes.transition !== value) {
413
+ this._classes.transition = value;
414
+ }
415
+ }
416
+
417
+ /**
418
+ * The class(es) to apply to the shelf when the disclosure is initializing.
419
+ *
420
+ * @type {string|string[]}
421
+ *
422
+ * @see _classes
423
+ */
424
+ get initializeClass() {
425
+ return this._classes.initialize;
426
+ }
427
+
428
+ set initializeClass(value) {
429
+ isValidClassList({ initializeClass: value });
430
+
431
+ if (this._classes.initialize !== value) {
432
+ this._classes.initialize = value;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * The duration time (in milliseconds) for the transition between open and closed states.
438
+ *
439
+ * Setting this value will also set the --graupl-transition-duration CSS custom property on the disclosure.
440
+ *
441
+ * @type {number}
442
+ *
443
+ * @see _durations.transition
444
+ */
445
+ get transitionDuration() {
446
+ return this._durations.transition;
447
+ }
448
+
449
+ set transitionDuration(value) {
450
+ isValidType("number", { value });
451
+
452
+ if (this._durations.transition !== value) {
453
+ this._durations.transition = value;
454
+ this._setTransitionDurations();
455
+ }
456
+ }
457
+
458
+ /**
459
+ * The duration time (in milliseconds) for the transition from closed to open states.
460
+ *
461
+ * If openDuration is set to -1, the transitionDuration value will be used instead.
462
+ *
463
+ * Setting this value will also set the --graupl-open-transition-duration CSS custom property on the disclosure.
464
+ *
465
+ * @type {number}
466
+ *
467
+ * @see _durations.open
468
+ */
469
+ get openDuration() {
470
+ if (this._durations.open === -1) return this.transitionDuration;
471
+
472
+ return this._durations.open;
473
+ }
474
+
475
+ set openDuration(value) {
476
+ isValidType("number", { value });
477
+
478
+ if (this._durations.open !== value) {
479
+ this._durations.open = value;
480
+ this._setTransitionDurations();
481
+ }
482
+ }
483
+
484
+ /**
485
+ * The duration time (in milliseconds) for the transition from open to closed states.
486
+ *
487
+ * If closeDuration is set to -1, the transitionDuration value will be used instead.
488
+ *
489
+ * Setting this value will also set the --graupl-close-transition-duration CSS custom property on the disclosure.
490
+ *
491
+ * @type {number}
492
+ *
493
+ * @see _durations.close
494
+ */
495
+ get closeDuration() {
496
+ if (this._durations.close === -1) return this.transitionDuration;
497
+
498
+ return this._durations.close;
499
+ }
500
+
501
+ set closeDuration(value) {
502
+ isValidType("number", { value });
503
+
504
+ if (this._durations.close !== value) {
505
+ this._durations.close = value;
506
+ this._setTransitionDurations();
507
+ }
508
+ }
509
+
510
+ /**
511
+ * The width of the screen (in pixels) that the disclosure will automatically open/close itself.
512
+ *
513
+ * @type {number}
514
+ *
515
+ * @see _breakpointWidth
516
+ */
517
+ get minWidth() {
518
+ return this._breakpointWidth;
519
+ }
520
+
521
+ set minWidth(value) {
522
+ isValidType("number", { value });
523
+
524
+ if (this._breakpointWidth !== value) {
525
+ this._breakpointWidth = value;
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Whether to close the disclosure when it loses focus in the DOM.
531
+ *
532
+ * @type {boolean}
533
+ *
534
+ * @see _closeOnBlur
535
+ */
536
+ get closeOnBlur() {
537
+ return this._closeOnBlur;
538
+ }
539
+
540
+ set closeOnBlur(value) {
541
+ isValidType("boolean", { value });
542
+
543
+ if (this._closeOnBlur !== value) {
544
+ this._closeOnBlur = value;
545
+ }
546
+ }
547
+
548
+ /**
549
+ * The current state of the disclosure's focus.
550
+ *
551
+ * @type {string}
552
+ *
553
+ * @see _focusState
554
+ */
555
+ get focusState() {
556
+ return this._focusState;
557
+ }
558
+
559
+ set focusState(value) {
560
+ isValidState({ value });
561
+
562
+ if (this._focusState !== value) {
563
+ this._focusState = value;
564
+ }
565
+ }
566
+
567
+ /**
568
+ * The last event triggered on the disclosure.
569
+ *
570
+ * @type {string}
571
+ *
572
+ * @see _currentEvent
573
+ */
574
+ get currentEvent() {
575
+ return this._currentEvent;
576
+ }
577
+
578
+ set currentEvent(value) {
579
+ isValidEvent({ value });
580
+
581
+ if (this._currentEvent !== value) {
582
+ this._currentEvent = value;
583
+ }
584
+ }
585
+
586
+ /**
587
+ * The prefix to use for CSS custom properties.
588
+ *
589
+ * @type {string}
590
+ *
591
+ * @see _prefix
592
+ */
593
+ get prefix() {
594
+ return this._prefix;
595
+ }
596
+
597
+ set prefix(value) {
598
+ isValidType("string", { value });
599
+
600
+ if (this._prefix !== value) {
601
+ this._prefix = value;
602
+ this._setTransitionDurations();
603
+ }
604
+ }
605
+
606
+ /**
607
+ * The open state of the disclosure.
608
+ *
609
+ * @readonly
610
+ *
611
+ * @type {boolean}
612
+ *
613
+ * @see _open
614
+ */
615
+ get isOpen() {
616
+ return this._open.value;
617
+ }
618
+
619
+ /**
620
+ * The open state of the disclosure that the user specifically triggered.
621
+ *
622
+ * @readonly
623
+ *
624
+ * @type {boolean}
625
+ *
626
+ * @see _open
627
+ */
628
+ get hasOpened() {
629
+ return this._open.committed;
630
+ }
631
+
632
+ /**
633
+ * A value to force opening reguardless of user interaction.
634
+ *
635
+ * @type {boolean}
636
+ *
637
+ * @see _shouldOpen
638
+ */
639
+ get shouldOpen() {
640
+ return this._shouldOpen;
641
+ }
642
+
643
+ set shouldOpen(value) {
644
+ isValidType("boolean", { value });
645
+
646
+ if (this._shouldOpen !== value) {
647
+ this._shouldOpen = value;
648
+ }
649
+ }
650
+
651
+ /**
652
+ * A media query for when the disclosure should open.
653
+ *
654
+ * Will return an empty string if no min width is set.
655
+ *
656
+ * @readonly
657
+ *
658
+ * @type {string}
659
+ */
660
+ get openQuery() {
661
+ if (this.minWidth === -1) {
662
+ return "";
663
+ }
664
+
665
+ return `(width > ${this.minWidth}px)`;
666
+ }
667
+
668
+ /**
669
+ * A media query for when the disclosure should close.
670
+ *
671
+ * Will return an empty string if no min width is set.
672
+ *
673
+ * @readonly
674
+ *
675
+ * @type {string}
676
+ */
677
+ get closeQuery() {
678
+ if (this.minWidth === -1) {
679
+ return "";
680
+ }
681
+
682
+ return `(width <= ${this.minWidth}px)`;
683
+ }
684
+
685
+ /**
686
+ * A flag to check if the disclosure's focus methods should _actually_ move the focus in the DOM.
687
+ *
688
+ * This will be `false` unless any of the following criteria are met:
689
+ * - The disclosure's current event is "keyboard".
690
+ *
691
+ * @readonly
692
+ *
693
+ * @type {boolean}
694
+ */
695
+ get shouldFocus() {
696
+ let check = false;
697
+
698
+ if (this.currentEvent === "keyboard") {
699
+ check = true;
700
+ }
701
+
702
+ return check;
703
+ }
704
+
705
+ /**
706
+ * An array of error messages generated by the disclosure.
707
+ *
708
+ * @readonly
709
+ *
710
+ * @type {string[]}
711
+ *
712
+ * @see _errors
713
+ */
714
+ get errors() {
715
+ return this._errors;
716
+ }
717
+
718
+ /**
719
+ * Validates all aspects on the disclosure to ensure proper functionality.
720
+ *
721
+ * @protected
722
+ *
723
+ * @return {boolean} - The result of the validation.
724
+ */
725
+ _validate() {
726
+ let check = true;
727
+
728
+ // HTML element checks.
729
+ const htmlElementChecks = isValidInstance(HTMLElement, {
730
+ disclosureElement: this._dom.disclosure,
731
+ controllerElement: this._dom.controller,
732
+ });
733
+
734
+ if (!htmlElementChecks.status) {
735
+ this._errors.push(htmlElementChecks.error.message);
736
+ check = false;
737
+ }
738
+
739
+ // Query selector checks.
740
+ const querySelectorChecks = isQuerySelector({
741
+ disclosureContentSelector: this._selectors.content,
742
+ });
743
+
744
+ if (!querySelectorChecks.status) {
745
+ this._errors.push(querySelectorChecks.error.message);
746
+ check = false;
747
+ }
748
+
749
+ // Class list checks.
750
+ const classes = {};
751
+
752
+ for (const className of Object.keys(this._classes)) {
753
+ if (this._classes[className] === "") {
754
+ continue;
755
+ }
756
+
757
+ classes[`${className}Class`] = this._classes[className];
758
+ }
759
+
760
+ const classListChecks = isValidClassList(classes);
761
+
762
+ if (!classListChecks.status) {
763
+ this._errors.push(classListChecks.error.message);
764
+ check = false;
765
+ }
766
+
767
+ // Duration checks.
768
+ const durations = {};
769
+
770
+ for (const durationName of Object.keys(this._durations)) {
771
+ durations[`${durationName}Duration`] = this._durations[durationName];
772
+ }
773
+
774
+ const durationChecks = isValidType("number", durations);
775
+
776
+ if (!durationChecks.status) {
777
+ this._errors.push(durationChecks.error.message);
778
+ check = false;
779
+ }
780
+
781
+ // Prefix check.
782
+ const prefixCheck = isValidType("string", { prefix: this._prefix });
783
+
784
+ if (!prefixCheck.status) {
785
+ this._errors.push(prefixCheck.error.message);
786
+ check = false;
787
+ }
788
+
789
+ return check;
790
+ }
791
+
792
+ /**
793
+ * Generates a key for the disclosure.
794
+ *
795
+ * @param {boolean} [regenerate = false] - A flag to determine if the key should be regenerated.
796
+ */
797
+ _generateKey(regenerate = false) {
798
+ if (this.key === "" || regenerate) {
799
+ this.key = Math.random()
800
+ .toString(36)
801
+ .replace(/[^a-z]+/g, "")
802
+ .substring(0, 10);
803
+ }
804
+ }
805
+
806
+ /**
807
+ * Sets the IDs of the disclosure and it's controller if they do not already exist.
808
+ *
809
+ * The generated IDs use the key and follow the format:
810
+ * - disclosure: `disclosure-${key}`
811
+ * - controller: `disclosure-controller-${key}`
812
+ *
813
+ * @protected
814
+ */
815
+ _setIds() {
816
+ this.dom.disclosure.id = this.dom.disclosure.id || `disclosure-${this.key}`;
817
+ this.dom.controller.id =
818
+ this.dom.controller.id || `disclosure-controller-${this.key}`;
819
+ }
820
+
821
+ /**
822
+ * Sets the ARIA attributes on the disclosure and its controller.
823
+ *
824
+ * The first steps are to ensure that the controlled has `aria-expanded` set to "false"
825
+ * if it's not already explicity set to "true".
826
+ *
827
+ * Then, set the `aria-controls` attribute on the controller to the disclosure's ID.
828
+ *
829
+ * Finally, ensure the controller element has a role of "button" if it is not a native button element.
830
+ *
831
+ * @protected
832
+ */
833
+ _setAriaAttributes() {
834
+ // If the controller element doesn't have aria-expanded set to true, set it to false.
835
+ if (this.dom.controller.getAttribute("aria-expanded") !== "true") {
836
+ this.dom.controller.setAttribute("aria-expanded", "false");
837
+ }
838
+
839
+ // Set the aria-controls attribute on the controller to the disclosure's ID.
840
+ this.dom.controller.setAttribute("aria-controls", this.dom.disclosure.id);
841
+
842
+ // If the controller element is not a button, set its role to button.
843
+ if (!isTag("button", { controller: this.dom.controller })) {
844
+ this.dom.controller.setAttribute("role", "button");
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Sets the transition durations of the disclosure as a CSS custom properties.
850
+ *
851
+ * The custom properties are:
852
+ * - `--graupl-disclosure-transition-duration`,
853
+ * - `--graupl-disclosure-open-transition-duration`, and
854
+ * - `--graupl-disclosure-close-transition-duration`.
855
+ *
856
+ * The prefix of `graupl-` can be changed by setting the disclosure's prefix value.
857
+ *
858
+ * @protected
859
+ */
860
+ _setTransitionDurations() {
861
+ this.dom.disclosure.style.setProperty(
862
+ `--${this.prefix}disclosure-transition-duration`,
863
+ `${this.transitionDuration}ms`
864
+ );
865
+
866
+ this.dom.disclosure.style.setProperty(
867
+ `--${this.prefix}disclosure-open-transition-duration`,
868
+ `${this.openDuration}ms`
869
+ );
870
+
871
+ this.dom.disclosure.style.setProperty(
872
+ `--${this.prefix}disclosure-close-transition-duration`,
873
+ `${this.closeDuration}ms`
874
+ );
875
+ }
876
+
877
+ /**
878
+ * Sets DOM elements.
879
+ *
880
+ * Elements listed in _domLock cannot be set using this method.
881
+ *
882
+ * @protected
883
+ *
884
+ * @param {string} elementType - The type of element to populate.
885
+ * @param {Object<HTMLElement,boolean>} [options = {}] - The options for setting the DOM element type.
886
+ * @param {HTMLElement} [options.base = this.dom.disclosure] - The element used as the base for the querySelector.
887
+ * @param {boolean} [options.overwrite = true] - A flag to set if the existing elements will be overwritten.
888
+ * @param {boolean} [options.strict = true] - A flag to set if the elements must be direct children of the base.
889
+ */
890
+ _setDOMElementType(
891
+ elementType,
892
+ { base = this.dom.disclosure, overwrite = true, strict = true } = {}
893
+ ) {
894
+ if (typeof this.selectors[elementType] === "string") {
895
+ if (this._domLock.includes(elementType)) {
896
+ throw new Error(
897
+ `Graupl ${this.constructor.name}: "${elementType}" element cannot be set through _setDOMElementType.`
898
+ );
899
+ }
900
+
901
+ if (base !== this.dom.disclosure) isValidInstance(HTMLElement, { base });
902
+
903
+ // Get the all elements matching the selector in the base.
904
+ const domElements = Array.from(
905
+ base.querySelectorAll(this.selectors[elementType])
906
+ );
907
+
908
+ // Filter the elements so only direct children of the base are kept.
909
+ const filteredElements = domElements.filter((item) =>
910
+ strict ? item.parentElement === base : true
911
+ );
912
+
913
+ if (overwrite) {
914
+ if (Array.isArray(this._dom[elementType])) {
915
+ this._dom[elementType] = filteredElements;
916
+ } else {
917
+ this._dom[elementType] = filteredElements[0] || null;
918
+ }
919
+ } else {
920
+ if (Array.isArray(this._dom[elementType])) {
921
+ this._dom[elementType] = [
922
+ ...this._dom[elementType],
923
+ ...filteredElements,
924
+ ];
925
+ } else {
926
+ this._dom[elementType] = filteredElements[0] || null;
927
+ }
928
+ }
929
+ } else {
930
+ throw new Error(
931
+ `Graupl ${this.constructor.name}: "${elementType}" is not a valid element type.`
932
+ );
933
+ }
934
+ }
935
+
936
+ /**
937
+ * Resets DOM elements.
938
+ *
939
+ * Elements listed in _domLock cannot be reset using this method.
940
+ *
941
+ * @protected
942
+ *
943
+ * @param {string} elementType - The type of element to clear.
944
+ */
945
+ _resetDOMElementType(elementType) {
946
+ if (typeof this.selectors[elementType] === "string") {
947
+ if (this._domLock.includes(elementType)) {
948
+ throw new Error(
949
+ `Graupl ${this.constructor.name}: "${elementType}" element cannot be reset through _resetDOMElementType.`
950
+ );
951
+ }
952
+
953
+ if (Array.isArray(this._dom[elementType])) {
954
+ this._dom[elementType] = [];
955
+ } else {
956
+ this._dom[elementType] = null;
957
+ }
958
+ } else {
959
+ throw new Error(
960
+ `Graupl ${this.constructor.name}: "${elementType}" is not a valid element type.`
961
+ );
962
+ }
963
+ }
964
+
965
+ /**
966
+ * Sets all DOM elements within the disclosure.
967
+ *
968
+ * Utilizes _setDOMElementType and
969
+ * _resetDOMElementType.
970
+ *
971
+ * @protected
972
+ */
973
+ _setDOMElements() {
974
+ this._resetDOMElementType("content");
975
+ this._setDOMElementType("content");
976
+ }
977
+
978
+ /**
979
+ * Expands the disclosure.
980
+ *
981
+ * Sets the controller's `aria-expanded` to "true", adds the
982
+ * open class to the disclosure, and removes the closed class from the disclosure.
983
+ *
984
+ * @protected
985
+ *
986
+ * @fires grauplDisclosureExpand
987
+ *
988
+ * @param {boolean} [emit = true] - Emit the expand event once expanded.
989
+ * @param {boolean} [transition = true] - Respect the transition class.
990
+ */
991
+ _expand(emit = true, transition = true) {
992
+ this.dom.controller.setAttribute("aria-expanded", "true");
993
+
994
+ // If we're dealing with transition classes, then we need to utilize
995
+ // requestAnimationFrame to add the transition class, remove the close class,
996
+ // add the open class, and finally remove the transition class.
997
+ if (transition && this.transitionlass !== "") {
998
+ // this.dom.disclosure.style.height = `${this.dom.disclosure.getBoundingClientRect().height}px`;
999
+ // console.log(this.dom.disclosure.style.height);
1000
+ addClass(this.transitionClass, this.dom.disclosure);
1001
+
1002
+ requestAnimationFrame(() => {
1003
+ removeClass(this.closeClass, this.dom.disclosure);
1004
+
1005
+ // this.dom.disclosure.style.height = `${this.dom.disclosure.getBoundingClientRect().height}px`;
1006
+ // console.log(this.dom.disclosure.style.height);
1007
+
1008
+ requestAnimationFrame(() => {
1009
+ addClass(this.openClass, this.dom.disclosure);
1010
+
1011
+ // this.dom.disclosure.style.height = `${this.dom.content.getBoundingClientRect().height}px`;
1012
+ // console.log(this.dom.disclosure.style.height);
1013
+
1014
+ requestAnimationFrame(() => {
1015
+ setTimeout(() => {
1016
+ removeClass(this.transitionClass, this.dom.disclosure);
1017
+
1018
+ // this.dom.disclosure.style.height = "";
1019
+ // console.log(this.dom.disclosure.style.height);
1020
+ }, this.openDuration);
1021
+ });
1022
+ });
1023
+ });
1024
+ } else {
1025
+ // Add the open class
1026
+ addClass(this.openClass, this.dom.disclosure);
1027
+
1028
+ // Remove the close class.
1029
+ removeClass(this.closeClass, this.dom.disclosure);
1030
+ }
1031
+
1032
+ this.dom.content.removeAttribute("inert");
1033
+
1034
+ if (emit) {
1035
+ this.dom.controller.dispatchEvent(this._expandEvent);
1036
+ }
1037
+ }
1038
+
1039
+ /**
1040
+ * Collapses the disclosure.
1041
+ *
1042
+ * Sets the controller's `aria-expanded` to "false", adds the
1043
+ * close class to the disclosure, and removes the open class from the disclosure.
1044
+ *
1045
+ * @protected
1046
+ *
1047
+ * @fires grauplDisclosureCollapse
1048
+ *
1049
+ * @param {boolean} [emit = true] - Emit the collapse event once collapsed.
1050
+ * @param {boolean} [transition = true] - Respect the transition class.
1051
+ */
1052
+ _collapse(emit = true, transition = true) {
1053
+ this.dom.controller.setAttribute("aria-expanded", "false");
1054
+
1055
+ // If we're dealing with transition classes, then we need to utilize
1056
+ // requestAnimationFrame to add the transition class, remove the open class,
1057
+ // add the close class, and finally remove the transition class.
1058
+ if (transition && this.transitionClass !== "") {
1059
+ // this.dom.disclosure.style.height = `${this.dom.content.getBoundingClientRect().height}px`;
1060
+ // console.log(this.dom.disclosure.style.height);
1061
+
1062
+ addClass(this.transitionClass, this.dom.disclosure);
1063
+
1064
+ requestAnimationFrame(() => {
1065
+ removeClass(this.openClass, this.dom.disclosure);
1066
+
1067
+ // this.dom.disclosure.style.height = `${this.dom.content.getBoundingClientRect().height}px`;
1068
+ // console.log(this.dom.disclosure.style.height);
1069
+
1070
+ requestAnimationFrame(() => {
1071
+ addClass(this.closeClass, this.dom.disclosure);
1072
+
1073
+ // this.dom.disclosure.style.height = `${this.dom.disclosure.getBoundingClientRect().height}px`;
1074
+ // console.log(this.dom.disclosure.style.height);
1075
+
1076
+ requestAnimationFrame(() => {
1077
+ setTimeout(() => {
1078
+ removeClass(this.transitionClass, this.dom.disclosure);
1079
+
1080
+ this.dom.content.innert = true;
1081
+
1082
+ // this.dom.disclosure.style.height = "";
1083
+ // console.log(this.dom.disclosure.style.height);
1084
+ }, this.closeDuration);
1085
+ });
1086
+ });
1087
+ });
1088
+ } else {
1089
+ // Add the close class
1090
+ addClass(this.closeClass, this.dom.disclosure);
1091
+
1092
+ // Remove the open class.
1093
+ removeClass(this.openClass, this.dom.disclosure);
1094
+ }
1095
+
1096
+ this.dom.content.setAttribute("inert", "true");
1097
+
1098
+ if (emit) {
1099
+ this.dom.controller.dispatchEvent(this._collapseEvent);
1100
+ }
1101
+ }
1102
+
1103
+ _handleResize() {
1104
+ if (this._breakpointWidth <= 0) {
1105
+ return;
1106
+ }
1107
+
1108
+ let width = 0;
1109
+
1110
+ this._observer = new ResizeObserver((entries) => {
1111
+ requestAnimationFrame(() => {
1112
+ for (const entry of entries) {
1113
+ const boxSize = Array.isArray(entry.contentBoxSize)
1114
+ ? entry.contentBoxSize[0]
1115
+ : entry.contentBoxSize;
1116
+ const inlineSize =
1117
+ boxSize && typeof boxSize.inlineSize === "number"
1118
+ ? boxSize.inlineSize
1119
+ : entry.contentRect.width;
1120
+
1121
+ if (typeof inlineSize !== "number") continue;
1122
+
1123
+ if (width === inlineSize) continue;
1124
+
1125
+ const belowBreakpoint = inlineSize <= this.minWidth;
1126
+ const aboveBreakpoint = inlineSize > this.minWidth;
1127
+
1128
+ if (belowBreakpoint && this.isOpen) {
1129
+ this.close({ preserveState: true });
1130
+ } else if (
1131
+ aboveBreakpoint &&
1132
+ !this.isOpen &&
1133
+ (this.hasOpened || this.shouldOpen)
1134
+ ) {
1135
+ this.open();
1136
+ }
1137
+
1138
+ width = inlineSize;
1139
+ }
1140
+ });
1141
+ });
1142
+ this._observer.observe(document.body);
1143
+ }
1144
+
1145
+ /**
1146
+ * Handles focus events throughout the disclosure.
1147
+ *
1148
+ * - Adds a `focusout` listener to the disclosure so when the disclosure loses focus it will close.
1149
+ */
1150
+ _handleFocus() {
1151
+ this.dom.disclosure.addEventListener("focusout", (event) => {
1152
+ if (
1153
+ !this.closeOnBlur ||
1154
+ this.currentEvent !== "keyboard" ||
1155
+ event.relatedTarget === null ||
1156
+ this.dom.disclosure.contains(event.relatedTarget) ||
1157
+ this.dom.controller === event.relatedTarget
1158
+ ) {
1159
+ return;
1160
+ }
1161
+
1162
+ this.close();
1163
+ });
1164
+ }
1165
+
1166
+ /**
1167
+ * Handles click events throughout the disclosure.
1168
+ *
1169
+ * - Adds a `pointerup` listener to the controller that toggles the disclosure.
1170
+ * - Adds a `pointerup` listener to the `document` so if the user clicks outside the disclosure it will close.
1171
+ */
1172
+ _handleClick() {
1173
+ this.dom.controller.addEventListener("pointerup", (event) => {
1174
+ this.currentEvent = "mouse";
1175
+
1176
+ if (event.button !== 0) return;
1177
+
1178
+ preventEvent(event);
1179
+ this.toggle();
1180
+ });
1181
+
1182
+ document.addEventListener("pointerup", (event) => {
1183
+ if (this.focusState !== "self" || !this.closeOnBlur) return;
1184
+
1185
+ this.currentEvent = "mouse";
1186
+
1187
+ if (
1188
+ !this.dom.disclosure.contains(event.target) &&
1189
+ this.dom.controller !== event.target
1190
+ ) {
1191
+ this.close();
1192
+ }
1193
+ });
1194
+ }
1195
+
1196
+ /**
1197
+ * Handles keydown events throughout the disclosure.
1198
+ *
1199
+ * This method exists to assist the _handleKeyup method.
1200
+ *
1201
+ * - Adds a `keydown` listener to the controller.
1202
+ * - Blocks propagation on "Space" and "Enter" keys.
1203
+ * - Adds a `keydown` listener to the disclosure.
1204
+ * - Blocks propagation on "Escape" keys.
1205
+ */
1206
+ _handleKeydown() {
1207
+ this.dom.controller.addEventListener("keydown", (event) => {
1208
+ this.currentEvent = "keyboard";
1209
+
1210
+ const key = keyPress(event);
1211
+
1212
+ switch (key) {
1213
+ case "Space":
1214
+ case "Enter":
1215
+ preventEvent(event);
1216
+
1217
+ break;
1218
+ }
1219
+ });
1220
+
1221
+ this.dom.disclosure.addEventListener("keydown", (event) => {
1222
+ this.currentEvent = "keyboard";
1223
+
1224
+ const key = keyPress(event);
1225
+
1226
+ switch (key) {
1227
+ case "Escape":
1228
+ preventEvent(event);
1229
+
1230
+ break;
1231
+ }
1232
+ });
1233
+ }
1234
+
1235
+ /**
1236
+ * Handles keyup events throughout the disclosure.
1237
+ *
1238
+ * - Adds a `keyup` listener to the controller.
1239
+ * - Toggles the disclosure on "Space" and "Enter" keys.
1240
+ * - Adds a `keyup` listener to the disclosure.
1241
+ * - Closes the disclosure on "Escape" keys.
1242
+ */
1243
+ _handleKeyup() {
1244
+ this.dom.controller.addEventListener("keyup", (event) => {
1245
+ this.currentEvent = "keyboard";
1246
+
1247
+ const key = keyPress(event);
1248
+
1249
+ switch (key) {
1250
+ case "Space":
1251
+ case "Enter":
1252
+ this.toggle();
1253
+
1254
+ preventEvent(event);
1255
+
1256
+ break;
1257
+ }
1258
+ });
1259
+
1260
+ this.dom.disclosure.addEventListener("keyup", (event) => {
1261
+ this.currentEvent = "keyboard";
1262
+
1263
+ const key = keyPress(event);
1264
+
1265
+ switch (key) {
1266
+ case "Escape":
1267
+ this.close();
1268
+
1269
+ preventEvent(event);
1270
+
1271
+ break;
1272
+ }
1273
+ });
1274
+ }
1275
+
1276
+ /**
1277
+ * Opens the disclosure.
1278
+ *
1279
+ * Sets the disclosure's focus state to "self", calls expand, and sets isOpen to `true`.
1280
+ *
1281
+ * @public
1282
+ *
1283
+ * @param {Object<boolean>} [options = {}] - Options for opening the disclosure.
1284
+ * @param {boolean} [options.force = false] - Whether to force the open action.
1285
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the open state.
1286
+ */
1287
+ open({ force = false, preserveState = false } = {}) {
1288
+ if (this.isOpen && !force) return;
1289
+
1290
+ // Set the focus state.
1291
+ this.focusState = "self";
1292
+
1293
+ // Expand the disclosure.
1294
+ this._expand();
1295
+
1296
+ // Set the open state.
1297
+ this._open.value = true;
1298
+
1299
+ if (!preserveState) {
1300
+ this._open.commit();
1301
+ }
1302
+ }
1303
+
1304
+ /**
1305
+ * Opens the disclosure without entering it.
1306
+ *
1307
+ * Sets the disclosure's focus state to "none", calls expand, and sets isOpen to `true`.
1308
+ *
1309
+ * @public
1310
+ *
1311
+ * @param {Object<boolean>} [options = {}] - Options for previewing the disclosure.
1312
+ * @param {boolean} [options.force = false] - Whether to force the preview action.
1313
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the open state.
1314
+ */
1315
+ preview({ force = false, preserveState = false } = {}) {
1316
+ if (this.isOpen && !force) return;
1317
+
1318
+ // Set the focus state.
1319
+ this.focusState = "none";
1320
+
1321
+ // Expand the disclosure.
1322
+ this._expand();
1323
+
1324
+ // Set the open state.
1325
+ this._open.value = true;
1326
+
1327
+ if (!preserveState) {
1328
+ this._open.commit();
1329
+ }
1330
+ }
1331
+
1332
+ /**
1333
+ * Closes the disclosure.
1334
+ *
1335
+ * Sets the disclosure's focus state to "none", calls collapse, and sets isOpen to `false`.
1336
+ *
1337
+ * @public
1338
+ *
1339
+ * @param {Object<boolean>} [options = {}] - Options for closing the disclosure.
1340
+ * @param {boolean} [options.force = false] - Whether to force the close action.
1341
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the open state.
1342
+ */
1343
+ close({ force = false, preserveState = false } = {}) {
1344
+ if (!this.isOpen && !force) return;
1345
+
1346
+ // Set the focus state.
1347
+ this.focusState = "none";
1348
+
1349
+ // Collapse the disclosure.
1350
+ this._collapse();
1351
+
1352
+ // Set the open state.
1353
+ this._open.value = false;
1354
+
1355
+ if (!preserveState) {
1356
+ this._open.commit();
1357
+ }
1358
+ }
1359
+
1360
+ /**
1361
+ * Toggles the open state of the disclosure.
1362
+ *
1363
+ * @public
1364
+ *
1365
+ * @param {Object<boolean>} [options = {}] - Options for toggling the disclosure.
1366
+ * @param {boolean} [options.force = false] - Whether to force the open or close action.
1367
+ * @param {boolean} [options.preserveState = false] - Whether to preserve the open state.
1368
+ */
1369
+ toggle({ force = false, preserveState = false } = {}) {
1370
+ if (this.isOpen) {
1371
+ this.close({ force, preserveState });
1372
+ } else {
1373
+ this.open({ force, preserveState });
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ export default Disclosure;