vanilla-jui 1.4.5 → 1.4.6

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 (103) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +1273 -1896
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +21 -2
  7. package/src/components/accordion.ts +297 -216
  8. package/src/components/flow.ts +222 -258
  9. package/src/components/form.ts +44 -102
  10. package/src/components/menu.ts +268 -356
  11. package/src/components/modal.ts +181 -641
  12. package/src/components/offcanvas.ts +225 -106
  13. package/src/components/pagination.ts +296 -326
  14. package/src/components/sticky.ts +55 -54
  15. package/src/components/swiper.ts +237 -247
  16. package/src/components/tabs.ts +278 -271
  17. package/src/components/toc.ts +42 -46
  18. package/src/core/Component.ts +69 -47
  19. package/src/css/accordion.css +2 -2
  20. package/src/css/animation.css +1 -5
  21. package/src/css/avatar.css +1 -1
  22. package/src/css/badge.css +1 -1
  23. package/src/css/breadcrumb.css +9 -10
  24. package/src/css/button.css +1 -1
  25. package/src/css/card.css +2 -2
  26. package/src/css/color.css +974 -1002
  27. package/src/css/color_p3.css +974 -1004
  28. package/src/css/components.css +1 -1
  29. package/src/css/content.css +72 -63
  30. package/src/css/drop.css +1 -1
  31. package/src/css/editor.css +14 -14
  32. package/src/css/flow.css +4 -14
  33. package/src/css/font.css +5 -4
  34. package/src/css/form.css +76 -52
  35. package/src/css/gradient.css +299 -222
  36. package/src/css/icon.css +57 -57
  37. package/src/css/index.css +1 -1
  38. package/src/css/layout.css +13 -10
  39. package/src/css/loading.css +60 -12
  40. package/src/css/menu.css +262 -188
  41. package/src/css/modal.css +1 -1
  42. package/src/css/offcanvas.css +9 -13
  43. package/src/css/pagination.css +5 -1
  44. package/src/css/popup.css +1 -1
  45. package/src/css/skeleton.css +3 -2
  46. package/src/css/status.css +2 -3
  47. package/src/css/swiper.css +1 -1
  48. package/src/css/table.css +1 -1
  49. package/src/css/tabs.css +12 -11
  50. package/src/css/tag.css +1 -1
  51. package/src/css/theme-palette.css +19 -5
  52. package/src/css/themes/default/accordion.css +61 -61
  53. package/src/css/themes/default/avatar.css +54 -54
  54. package/src/css/themes/default/badge.css +62 -62
  55. package/src/css/themes/default/breadcrumb.css +23 -23
  56. package/src/css/themes/default/button.css +203 -203
  57. package/src/css/themes/default/content.css +53 -53
  58. package/src/css/themes/default/drop.css +10 -10
  59. package/src/css/themes/default/editor.css +9 -9
  60. package/src/css/themes/default/index.css +1 -1
  61. package/src/css/themes/default/loading.css +26 -20
  62. package/src/css/themes/default/menu.css +75 -11
  63. package/src/css/themes/default/modal.css +32 -36
  64. package/src/css/themes/default/pagination.css +7 -7
  65. package/src/css/themes/default/popup.css +6 -6
  66. package/src/css/themes/default/swiper.css +32 -32
  67. package/src/css/themes/default/table.css +16 -16
  68. package/src/css/themes/default/tabs.css +30 -30
  69. package/src/css/themes/default/tag.css +71 -71
  70. package/src/css/themes/default/theme-palette.css +11 -11
  71. package/src/css/themes/default/tip.css +42 -42
  72. package/src/css/themes/default/toast.css +60 -60
  73. package/src/css/themes/default/toolbar.css +23 -29
  74. package/src/css/themes/default/tooltip.css +43 -13
  75. package/src/css/themes/index.css +1 -1
  76. package/src/css/tip.css +2 -2
  77. package/src/css/toast.css +3 -3
  78. package/src/css/tokens.css +12 -17
  79. package/src/css/toolbar.css +9 -10
  80. package/src/css/tooltip.css +3 -2
  81. package/src/index.ts +22 -19
  82. package/src/{components → primitives}/drop.ts +58 -49
  83. package/src/{components → primitives}/icons.ts +1 -1
  84. package/src/primitives/loading.ts +23 -0
  85. package/src/primitives/parabola.ts +388 -0
  86. package/src/primitives/popup.ts +61 -0
  87. package/src/{components → primitives}/theme.ts +88 -57
  88. package/src/{components → primitives}/toast.ts +18 -25
  89. package/src/{components → primitives}/tooltip.ts +100 -25
  90. package/src/utilities/browser.ts +23 -49
  91. package/src/utilities/class-name.ts +5 -0
  92. package/src/utilities/dom.ts +14 -32
  93. package/src/utilities/function.ts +4 -0
  94. package/src/utilities/http.ts +7 -7
  95. package/src/utilities/id.ts +57 -0
  96. package/src/utilities/object.ts +5 -0
  97. package/src/utilities/timer.ts +38 -0
  98. package/src/utilities/types.ts +420 -0
  99. package/src/{components → validation}/validator.ts +64 -53
  100. package/src/components/parabola.ts +0 -335
  101. package/src/utilities/cache.ts +0 -57
  102. package/src/utilities/core.ts +0 -614
  103. package/src/utilities/storage.ts +0 -46
@@ -1,36 +1,36 @@
1
- import { createDeepStore, flushSync, jsx } from 'vanilla-signal';
1
+ import {
2
+ createDeepStore,
3
+ createEffect,
4
+ createRoot,
5
+ flushSync,
6
+ jsx,
7
+ untrack,
8
+ } from 'vanilla-signal';
2
9
 
3
10
  import Component, {
4
11
  type ComponentDOM,
5
12
  type ComponentRuntime,
6
13
  } from '../core/Component.ts';
7
- import {
8
- type ResolveSchema,
9
- randomId,
10
- resolveProps,
11
- timer,
12
- } from '../utilities/core.ts';
14
+ import { createLoading } from '../primitives/loading.ts';
13
15
  import {
14
16
  type RenderableContent,
15
- createLoading,
16
- isRenderableContent,
17
17
  normalizeContentNodes,
18
18
  } from '../utilities/dom.ts';
19
+ import { randomId } from '../utilities/id.ts';
20
+ import { timer } from '../utilities/timer.ts';
21
+ import {
22
+ type ResolveSchema,
23
+ resolveProps,
24
+ validateParam,
25
+ } from '../utilities/types.ts';
19
26
 
20
27
  export type OffcanvasDirection = 'top' | 'right' | 'bottom' | 'left';
21
- export type OffcanvasAnimation = 'slide' | 'none';
28
+ export type OffcanvasAnimate = string;
22
29
 
23
30
  export interface OffcanvasClassNames {
24
31
  root: string;
25
32
  overlay: string;
26
33
  content: string;
27
- active: string;
28
- top: string;
29
- right: string;
30
- bottom: string;
31
- left: string;
32
- slide: string;
33
- none: string;
34
34
  }
35
35
 
36
36
  export type OffcanvasClassNameConfig = Partial<OffcanvasClassNames>;
@@ -43,10 +43,11 @@ export interface OffcanvasProps extends Record<string, unknown> {
43
43
  content?: OffcanvasContent;
44
44
  overlay?: boolean;
45
45
  filter?: boolean;
46
+ bodyOverflow?: boolean;
46
47
  cache?: boolean;
47
48
  ttl?: number;
48
49
  direction?: OffcanvasDirection;
49
- animation?: OffcanvasAnimation;
50
+ animate?: OffcanvasAnimate;
50
51
  bgClose?: boolean;
51
52
  escClose?: boolean;
52
53
  id?: string | null;
@@ -61,10 +62,11 @@ interface ResolvedOffcanvasProps extends Record<string, unknown> {
61
62
  content: OffcanvasContent;
62
63
  overlay: boolean;
63
64
  filter: boolean;
65
+ bodyOverflow: boolean;
64
66
  cache: boolean;
65
67
  ttl: number;
66
68
  direction: OffcanvasDirection;
67
- animation: OffcanvasAnimation;
69
+ animate: OffcanvasAnimate;
68
70
  bgClose: boolean;
69
71
  escClose: boolean;
70
72
  id: string;
@@ -76,12 +78,14 @@ interface ResolvedOffcanvasProps extends Record<string, unknown> {
76
78
  }
77
79
 
78
80
  interface OffcanvasState extends Record<string, unknown> {
81
+ content: OffcanvasContent;
79
82
  visible: boolean;
80
83
  loading: boolean;
81
84
  }
82
85
 
83
86
  interface OffcanvasDOM extends ComponentDOM {
84
87
  root: HTMLElement | null;
88
+ overlay: HTMLElement | null;
85
89
  content: HTMLElement | null;
86
90
  }
87
91
 
@@ -92,31 +96,29 @@ interface OffcanvasCache {
92
96
  }
93
97
 
94
98
  interface OffcanvasRuntime extends ComponentRuntime {
99
+ built: boolean;
95
100
  cache: OffcanvasCache;
96
101
  contentLoadId: number;
97
102
  }
98
103
 
104
+ interface OffcanvasCleanupExtras {
105
+ state?: (() => void) | null;
106
+ }
107
+
99
108
  const DEFAULT_CLASS_NAMES: OffcanvasClassNames = {
100
109
  root: 'j-offcanvas',
101
110
  overlay: 'j-offcanvas-overlay',
102
111
  content: 'offcanvas-content',
103
- active: 'is-active',
104
- top: 'is-top',
105
- right: 'is-right',
106
- bottom: 'is-bottom',
107
- left: 'is-left',
108
- slide: 'is-slide',
109
- none: 'is-none',
110
112
  };
111
113
 
112
114
  const OFFCANVAS_PROPS_SCHEMA = {
113
115
  content: {
114
116
  default: '',
115
- validate: isRenderableContent,
116
- message: 'expects string, Node, array, function or null.',
117
+ type: 'renderable',
117
118
  },
118
119
  overlay: { default: true, type: 'boolean' },
119
120
  filter: { default: true, type: 'boolean' },
121
+ bodyOverflow: { default: true, type: 'boolean' },
120
122
  cache: { default: false, type: 'boolean' },
121
123
  ttl: { default: 0, type: 'number' },
122
124
  direction: {
@@ -124,10 +126,9 @@ const OFFCANVAS_PROPS_SCHEMA = {
124
126
  type: 'string',
125
127
  enum: ['top', 'right', 'bottom', 'left'],
126
128
  },
127
- animation: {
129
+ animate: {
128
130
  default: 'slide',
129
131
  type: 'string',
130
- enum: ['slide', 'none'],
131
132
  },
132
133
  bgClose: { default: true, type: 'boolean' },
133
134
  escClose: { default: true, type: 'boolean' },
@@ -157,6 +158,10 @@ const OFFCANVAS_PROPS_SCHEMA = {
157
158
  onHidden: { default: null, types: ['function', 'null'] },
158
159
  } satisfies ResolveSchema<OffcanvasProps>;
159
160
 
161
+ const OFFCANVAS_STATE_SCHEMA = {
162
+ content: OFFCANVAS_PROPS_SCHEMA.content,
163
+ };
164
+
160
165
  function normalizeTtl(ttl: number): number {
161
166
  return ttl > 0 ? ttl : 0;
162
167
  }
@@ -167,10 +172,11 @@ function normalizeProps(input: OffcanvasProps): ResolvedOffcanvasProps {
167
172
  content: props.content as OffcanvasContent,
168
173
  overlay: props.overlay as boolean,
169
174
  filter: props.filter as boolean,
175
+ bodyOverflow: props.bodyOverflow as boolean,
170
176
  cache: props.cache as boolean,
171
177
  ttl: props.ttl as number,
172
178
  direction: props.direction as OffcanvasDirection,
173
- animation: props.animation as OffcanvasAnimation,
179
+ animate: props.animate as OffcanvasAnimate,
174
180
  bgClose: props.bgClose as boolean,
175
181
  escClose: props.escClose as boolean,
176
182
  id: props.id as string,
@@ -182,32 +188,25 @@ function normalizeProps(input: OffcanvasProps): ResolvedOffcanvasProps {
182
188
  };
183
189
  }
184
190
 
185
- function joinClasses(
186
- ...classes: Array<string | null | undefined | false>
187
- ): string {
188
- return classes.filter(Boolean).join(' ');
189
- }
190
-
191
- /**
192
- * 侧滑面板组件,继承 Component。
193
- *
194
- * 适用于侧边菜单、筛选面板、移动端抽屉等场景。
195
- */
196
- export class Offcanvas extends Component<
191
+ class OffcanvasComponent extends Component<
197
192
  ResolvedOffcanvasProps,
198
193
  OffcanvasState,
199
194
  OffcanvasDOM
200
195
  > {
201
196
  declare runtime: OffcanvasRuntime;
202
197
  declare state: OffcanvasState;
203
- _overlay: HTMLElement | null;
198
+ declare cleanup: Component['cleanup'] & OffcanvasCleanupExtras;
204
199
 
205
200
  constructor(input: OffcanvasProps = {}) {
206
201
  const props = normalizeProps(input);
207
202
  super(props);
208
203
 
204
+ this.dom.overlay = null;
209
205
  this.dom.content = null;
210
- this._overlay = null;
206
+
207
+ this.cleanup.state = null;
208
+
209
+ this.runtime.built = false;
211
210
  this.runtime.cache = {
212
211
  content: null,
213
212
  hasContent: false,
@@ -216,51 +215,128 @@ export class Offcanvas extends Component<
216
215
  this.runtime.contentLoadId = 0;
217
216
 
218
217
  this.state = createDeepStore({
218
+ content: props.content,
219
219
  visible: false,
220
220
  loading: false,
221
221
  }) as OffcanvasState;
222
+ }
223
+
224
+ build(): this {
225
+ if (this.runtime.destroyed) {
226
+ throw new Error('Offcanvas.build: instance has been destroyed.');
227
+ }
228
+ if (this.runtime.built) return this;
229
+
230
+ try {
231
+ this.dom.root = this.buildRoot();
232
+ this.runtime.built = true;
233
+ this.bindState();
234
+ } catch (error) {
235
+ this.destroy();
236
+ throw error;
237
+ }
222
238
 
223
- this.onInit(props);
239
+ this.emit('init', this.props);
240
+ return this;
224
241
  }
225
242
 
226
- protected onInit(props: ResolvedOffcanvasProps): void {
227
- if (props.overlay) this._overlay = this.buildOverlay(props);
228
- this.root = this.buildRoot(props);
243
+ private assertBuilt(method: string): void {
244
+ if (this.runtime.destroyed) {
245
+ throw new Error(`Offcanvas.${method}: instance has been destroyed.`);
246
+ }
247
+ if (!this.runtime.built) {
248
+ throw new Error(`Offcanvas.${method}: call build() first.`);
249
+ }
229
250
  }
230
251
 
231
- private buildOverlay(props: ResolvedOffcanvasProps): HTMLElement {
252
+ private buildRoot(): HTMLElement {
253
+ if (this.props.overlay) {
254
+ this.dom.overlay = this.buildOverlay();
255
+ }
256
+
257
+ return this.buildPanel();
258
+ }
259
+
260
+ private buildOverlay(): HTMLElement {
261
+ const filterStyle = this.props.filter
262
+ ? { backdropFilter: 'blur(2px)' }
263
+ : {};
232
264
  return jsx('div', {
233
- className: props.className.overlay,
234
- 'data-offcanvas-overlay': props.id,
235
- style: props.filter ? { backdropFilter: 'blur(2px)' } : {},
265
+ className: this.props.className.overlay,
266
+ hidden: true,
267
+ 'data-offcanvas-overlay': this.props.id,
268
+ style: {
269
+ ...filterStyle,
270
+ },
236
271
  }) as HTMLElement;
237
272
  }
238
273
 
239
- private buildRoot(props: ResolvedOffcanvasProps): HTMLElement {
274
+ private buildPanel(): HTMLElement {
240
275
  const content = jsx('div', {
241
- className: props.className.content,
242
- 'data-offcanvas-content': props.id,
276
+ className: this.props.className.content,
277
+ 'data-offcanvas-content': this.props.id,
243
278
  }) as HTMLElement;
244
279
  this.dom.content = content;
245
280
 
246
- if (typeof props.content !== 'function') {
247
- content.append(...normalizeContentNodes(props.content, this));
281
+ if (typeof this.state.content !== 'function') {
282
+ this.renderContent(this.state.content);
248
283
  }
249
284
 
250
285
  return jsx('div', {
251
- className: joinClasses(
252
- props.className.root,
253
- props.className[props.direction],
254
- props.className[props.animation]
255
- ),
256
- id: props.id,
286
+ className: this.props.className.root,
287
+ id: this.props.id,
288
+ role: 'dialog',
289
+ 'aria-modal': this.props.overlay ? 'true' : 'false',
290
+ 'aria-expanded': 'false',
257
291
  'data-offcanvas': 'root',
258
- 'data-offcanvas-direction': props.direction,
259
- 'data-offcanvas-animation': props.animation,
292
+ 'data-direction': this.props.direction,
293
+ 'data-animate': this.props.animate,
260
294
  children: content,
261
295
  }) as HTMLElement;
262
296
  }
263
297
 
298
+ private bindState(): void {
299
+ if (this.cleanup.state) return;
300
+
301
+ this.cleanup.state = createRoot((dispose) => {
302
+ let initialized = false;
303
+ createEffect(() => {
304
+ const content = this.state.content;
305
+ if (!initialized) {
306
+ initialized = true;
307
+ return;
308
+ }
309
+ void untrack(() => this.syncContent(content));
310
+ });
311
+ return dispose;
312
+ });
313
+ }
314
+
315
+ private async syncContent(content: OffcanvasContent): Promise<void> {
316
+ if (!this.runtime.built) return;
317
+ validateParam(
318
+ 'content',
319
+ content,
320
+ OFFCANVAS_STATE_SCHEMA.content,
321
+ 'Offcanvas.state'
322
+ );
323
+ this.runtime.cache.hasContent = false;
324
+ this.runtime.cache.content = null;
325
+ this.runtime.cache.updatedAt = 0;
326
+
327
+ if (typeof content === 'function') {
328
+ if (this.state.visible) await this.loadContent();
329
+ else this.clearContent();
330
+ return;
331
+ }
332
+
333
+ this.runtime.contentLoadId += 1;
334
+ flushSync(() => {
335
+ this.state.loading = false;
336
+ });
337
+ this.renderContent(content);
338
+ }
339
+
264
340
  private isCacheValid(): boolean {
265
341
  if (!this.props.cache || !this.runtime.cache.hasContent) return false;
266
342
 
@@ -268,15 +344,20 @@ export class Offcanvas extends Component<
268
344
  return !ttl || Date.now() - this.runtime.cache.updatedAt <= ttl;
269
345
  }
270
346
 
347
+ private clearContent(): void {
348
+ if (this.dom.content) this.dom.content.textContent = '';
349
+ }
350
+
271
351
  private renderContent(content: RenderableContent<Offcanvas>): void {
272
352
  if (!this.dom.content) return;
273
353
 
274
- this.dom.content.textContent = '';
354
+ this.clearContent();
275
355
  this.dom.content.append(...normalizeContentNodes(content, this));
276
356
  }
277
357
 
278
358
  private async loadContent(): Promise<void> {
279
- const { content, cache } = this.props;
359
+ const { cache } = this.props;
360
+ const content = this.state.content;
280
361
  if (typeof content !== 'function') {
281
362
  this.runtime.contentLoadId += 1;
282
363
  flushSync(() => {
@@ -300,7 +381,7 @@ export class Offcanvas extends Component<
300
381
  });
301
382
 
302
383
  if (this.dom.content) {
303
- this.dom.content.textContent = '';
384
+ this.clearContent();
304
385
  this.dom.content.appendChild(createLoading());
305
386
  }
306
387
 
@@ -330,8 +411,8 @@ export class Offcanvas extends Component<
330
411
  this.unbindEvents();
331
412
  const { bgClose, escClose } = this.props;
332
413
 
333
- if (this._overlay && bgClose) {
334
- this.cleanup.events.on('overlay', this._overlay, 'click', () => {
414
+ if (this.dom.overlay && bgClose) {
415
+ this.cleanup.events.on('overlay', this.dom.overlay, 'click', () => {
335
416
  void this.hide();
336
417
  });
337
418
  }
@@ -356,47 +437,54 @@ export class Offcanvas extends Component<
356
437
  }
357
438
  }
358
439
 
359
- unbindEvents(): void {
360
- this.cleanup?.events.clear();
440
+ private unbindEvents(): void {
441
+ this.cleanup.events.clear();
361
442
  }
362
443
 
363
- private renderPanel(): void {
364
- const { overlay, id, className } = this.props;
365
- const body = document.body;
366
-
367
- if (overlay && this._overlay) body.appendChild(this._overlay);
368
- if (this.dom.root) body.appendChild(this.dom.root);
444
+ private showPanel(): void {
445
+ const { id } = this.props;
446
+ timer.cancel(`oc-hide-${id}`);
369
447
 
370
- body.style.overflow = 'hidden';
448
+ if (this.dom.overlay && !this.dom.overlay.isConnected) {
449
+ this.dom.overlay.hidden = false;
450
+ document.body.appendChild(this.dom.overlay);
451
+ }
452
+ if (this.dom.root && !this.dom.root.isConnected) {
453
+ document.body.appendChild(this.dom.root);
454
+ }
455
+ if (this.props.bodyOverflow) {
456
+ document.body.style.overflow = 'hidden';
457
+ }
371
458
 
372
459
  timer.start(`oc-show-${id}`, 10, () => {
373
- if (!this.dom.root) return;
374
- this._overlay?.classList.add(className.active);
375
- this.dom.root.classList.add(className.active);
460
+ this.dom.root?.setAttribute('aria-expanded', 'true');
376
461
  });
377
462
  }
378
463
 
379
- private removePanel(): void {
380
- const { id, className } = this.props;
381
- const body = document.body;
464
+ private hidePanel(): void {
465
+ const { id } = this.props;
466
+ timer.cancel(`oc-show-${id}`);
467
+ timer.cancel(`oc-shown-${id}`);
382
468
 
383
- this._overlay?.classList.remove(className.active);
384
- this.dom.root?.classList.remove(className.active);
469
+ this.dom.root?.setAttribute('aria-expanded', 'false');
385
470
 
386
- timer.start(`oc-remove-${id}`, 100, () => {
387
- this._overlay?.remove();
471
+ timer.start(`oc-hide-${id}`, 100, () => {
472
+ this.dom.overlay?.remove();
388
473
  this.dom.root?.remove();
389
- body.style.overflow = '';
474
+ if (this.props.bodyOverflow) {
475
+ document.body.style.overflow = '';
476
+ }
390
477
  });
391
478
  }
392
479
 
393
480
  async show(): Promise<void> {
394
- if (this.runtime.destroyed || this.state.visible) return;
481
+ this.assertBuilt('show');
482
+ if (this.state.visible) return;
395
483
 
396
484
  const { onShow, onShown, id } = this.props;
397
485
  if (onShow) await Promise.resolve(onShow(this));
398
486
 
399
- this.renderPanel();
487
+ this.showPanel();
400
488
  this.bindEvents();
401
489
 
402
490
  flushSync(() => {
@@ -412,7 +500,8 @@ export class Offcanvas extends Component<
412
500
  }
413
501
 
414
502
  async hide(): Promise<void> {
415
- if (this.runtime.destroyed || !this.state.visible) return;
503
+ this.assertBuilt('hide');
504
+ if (!this.state.visible) return;
416
505
 
417
506
  const { onHide, onHidden } = this.props;
418
507
  if (onHide) await Promise.resolve(onHide(this));
@@ -423,7 +512,7 @@ export class Offcanvas extends Component<
423
512
  });
424
513
 
425
514
  this.unbindEvents();
426
- this.removePanel();
515
+ this.hidePanel();
427
516
 
428
517
  flushSync(() => {
429
518
  this.state.visible = false;
@@ -432,26 +521,56 @@ export class Offcanvas extends Component<
432
521
  void onHidden?.(this);
433
522
  }
434
523
 
435
- onDestroy(): void {
436
- const { id, className } = this.props;
524
+ protected onDestroy(): void {
525
+ const { id } = this.props;
437
526
 
438
527
  timer.cancel(`oc-show-${id}`);
439
- timer.cancel(`oc-remove-${id}`);
528
+ timer.cancel(`oc-hide-${id}`);
440
529
  timer.cancel(`oc-shown-${id}`);
441
530
 
442
- if (this.state.visible) this.unbindEvents();
531
+ this.unbindEvents();
532
+ this.cleanup.state?.();
533
+ this.cleanup.state = null;
443
534
 
444
- this.dom.root?.classList.remove(className.active);
535
+ this.dom.root?.setAttribute('aria-expanded', 'false');
536
+ this.dom.overlay?.remove();
445
537
  this.dom.root?.remove();
446
- this._overlay?.remove();
538
+ if (this.props.bodyOverflow) {
539
+ document.body.style.overflow = '';
540
+ }
447
541
 
448
- const body = document.body;
449
- body.style.overflow = '';
542
+ this.runtime.built = false;
543
+ }
450
544
 
451
- this._overlay = null;
545
+ protected override validateStatePatch(patch: Partial<OffcanvasState>): void {
546
+ validateParam(
547
+ 'state',
548
+ patch,
549
+ {
550
+ type: 'plainObject',
551
+ },
552
+ 'Offcanvas.setState'
553
+ );
554
+
555
+ for (const key of Object.keys(patch)) {
556
+ if (!Object.hasOwn(OFFCANVAS_STATE_SCHEMA, key)) {
557
+ throw new Error(
558
+ `Offcanvas.setState: "${key}" is not a supported state key.`
559
+ );
560
+ }
561
+ const stateKey = key as keyof typeof OFFCANVAS_STATE_SCHEMA;
562
+ validateParam(
563
+ key,
564
+ patch[key as keyof OffcanvasState],
565
+ OFFCANVAS_STATE_SCHEMA[stateKey],
566
+ 'Offcanvas.setState'
567
+ );
568
+ }
452
569
  }
453
570
  }
454
571
 
455
- export function createOffcanvas(options: OffcanvasProps = {}): Offcanvas {
456
- return new Offcanvas(options);
572
+ export type Offcanvas = OffcanvasComponent;
573
+
574
+ export function createOffcanvas(input: OffcanvasProps = {}): Offcanvas {
575
+ return new OffcanvasComponent(input);
457
576
  }