vanilla-jui 1.4.8 → 1.5.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.
Files changed (58) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +402 -977
  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 +3 -3
  7. package/src/components/accordion.ts +467 -349
  8. package/src/components/flow.ts +779 -1133
  9. package/src/components/form.ts +666 -509
  10. package/src/components/menu.ts +199 -326
  11. package/src/components/modal.ts +510 -747
  12. package/src/components/offcanvas.ts +281 -361
  13. package/src/components/pagination.ts +207 -374
  14. package/src/components/swiper.ts +688 -1168
  15. package/src/components/tabs.ts +468 -788
  16. package/src/components/toc.ts +191 -244
  17. package/src/core/component.ts +332 -0
  18. package/src/core/motion.ts +298 -0
  19. package/src/core/presence.ts +233 -0
  20. package/src/core/scheduler.ts +91 -0
  21. package/src/core/view.ts +31 -0
  22. package/src/css/accordion.css +94 -3
  23. package/src/css/color.css +4 -5
  24. package/src/css/color_p3.css +6 -5
  25. package/src/css/content.css +18 -24
  26. package/src/css/drop.css +4 -1
  27. package/src/css/form.css +8 -0
  28. package/src/css/gradient.css +64 -131
  29. package/src/css/layout.css +9 -23
  30. package/src/css/menu.css +10 -5
  31. package/src/css/modal.css +2 -1
  32. package/src/css/offcanvas.css +1 -17
  33. package/src/css/swiper.css +2 -1
  34. package/src/css/tabs.css +8 -17
  35. package/src/css/themes/default/accordion.css +0 -48
  36. package/src/css/themes/default/swiper.css +1 -1
  37. package/src/css/themes/default/tabs.css +4 -6
  38. package/src/css/toast.css +1 -17
  39. package/src/css/tokens.css +18 -35
  40. package/src/css/tooltip.css +2 -1
  41. package/src/index.ts +8 -3
  42. package/src/locales/index.ts +3 -0
  43. package/src/primitives/drop.ts +253 -262
  44. package/src/primitives/loading.ts +22 -4
  45. package/src/primitives/parabola.ts +123 -164
  46. package/src/primitives/sticky.ts +297 -0
  47. package/src/primitives/theme.ts +164 -222
  48. package/src/primitives/toast.ts +309 -284
  49. package/src/primitives/tooltip.ts +65 -66
  50. package/src/utilities/browser.ts +38 -0
  51. package/src/utilities/dom.ts +0 -54
  52. package/src/utilities/refs.ts +61 -0
  53. package/src/utilities/state.ts +73 -0
  54. package/src/utilities/timer.ts +11 -31
  55. package/src/validation/validator.ts +289 -389
  56. package/src/components/sticky.ts +0 -297
  57. package/src/core/Component.ts +0 -377
  58. package/src/utilities/scheduler.ts +0 -156
@@ -1,17 +1,23 @@
1
- import { createDeepStore, flushSync, jsx } from 'vanilla-signal';
1
+ import {
2
+ createDeepStore,
3
+ createEffect,
4
+ flushSync,
5
+ insert,
6
+ jsx,
7
+ untrack,
8
+ } from 'vanilla-signal';
2
9
 
3
- import Component, {
4
- type ComponentDOM,
5
- type ComponentRuntime,
6
- } from '../core/Component.ts';
7
- import { createLoading } from '../primitives/loading.ts';
8
10
  import {
9
- type RenderableContent,
10
- normalizeContentNodes,
11
- } from '../utilities/dom.ts';
11
+ type FunctionalComponent,
12
+ defineComponent,
13
+ } from '../core/component.ts';
14
+ import { createLoading } from '../primitives/loading.ts';
15
+ import { type RenderableContent } from '../utilities/dom.ts';
12
16
  import { randomId } from '../utilities/id.ts';
13
- import { createStateSync } from '../utilities/scheduler.ts';
14
- import { timer } from '../utilities/timer.ts';
17
+ import { createEventManager } from '../utilities/events.ts';
18
+ import { createMotionGroup, createTransition } from '../core/motion.ts';
19
+ import { createPresence } from '../core/presence.ts';
20
+ import { createElementRef } from '../utilities/refs.ts';
15
21
  import {
16
22
  type ResolveSchema,
17
23
  resolveProps,
@@ -73,38 +79,35 @@ interface ResolvedOffcanvasProps extends Record<string, unknown> {
73
79
 
74
80
  interface OffcanvasState extends Record<string, unknown> {
75
81
  content: OffcanvasContent;
82
+ resolvedContent: RenderableContent<Offcanvas>;
76
83
  visible: boolean;
77
84
  loading: boolean;
78
85
  }
79
86
 
80
- interface OffcanvasDOM extends ComponentDOM {
81
- root: HTMLElement | null;
82
- overlay: HTMLElement | null;
83
- content: HTMLElement | null;
84
- }
85
-
86
87
  interface OffcanvasCache {
87
88
  content: RenderableContent<Offcanvas>;
88
89
  hasContent: boolean;
89
90
  updatedAt: number;
90
91
  }
91
92
 
92
- interface OffcanvasRuntime extends ComponentRuntime {
93
- built: boolean;
93
+ interface OffcanvasRuntimeExtras {
94
94
  cache: OffcanvasCache;
95
95
  contentLoadId: number;
96
96
  }
97
97
 
98
- interface OffcanvasCleanupExtras {
99
- state?: (() => void) | null;
100
- }
101
-
102
98
  const DEFAULT_CLASS_NAMES: OffcanvasClassNames = {
103
99
  root: 'j-offcanvas',
104
100
  overlay: 'j-offcanvas-overlay',
105
101
  content: 'offcanvas-content',
106
102
  };
107
103
 
104
+ const OFFCANVAS_HIDDEN_TRANSFORM: Record<OffcanvasDirection, string> = {
105
+ top: 'translateY(-100%)',
106
+ right: 'translateX(100%)',
107
+ bottom: 'translateY(100%)',
108
+ left: 'translateX(-100%)',
109
+ };
110
+
108
111
  const OFFCANVAS_PROPS_SCHEMA = {
109
112
  content: {
110
113
  default: '',
@@ -154,6 +157,9 @@ const OFFCANVAS_PROPS_SCHEMA = {
154
157
 
155
158
  const OFFCANVAS_STATE_SCHEMA = {
156
159
  content: OFFCANVAS_PROPS_SCHEMA.content,
160
+ resolvedContent: { type: 'renderable' },
161
+ visible: { type: 'boolean' },
162
+ loading: { type: 'boolean' },
157
163
  };
158
164
 
159
165
  function normalizeTtl(ttl: number): number {
@@ -182,381 +188,295 @@ function normalizeProps(input: OffcanvasProps): ResolvedOffcanvasProps {
182
188
  };
183
189
  }
184
190
 
185
- class OffcanvasComponent extends Component<
186
- ResolvedOffcanvasProps,
187
- OffcanvasState,
188
- OffcanvasDOM
189
- > {
190
- declare runtime: OffcanvasRuntime;
191
- declare state: OffcanvasState;
192
- declare cleanup: Component['cleanup'] & OffcanvasCleanupExtras;
193
-
194
- constructor(input: OffcanvasProps = {}) {
195
- const props = normalizeProps(input);
196
- super(props);
197
-
198
- this.dom.overlay = null;
199
- this.dom.content = null;
200
-
201
- this.cleanup.state = null;
202
-
203
- this.runtime.built = false;
204
- this.runtime.cache = {
205
- content: null,
206
- hasContent: false,
207
- updatedAt: 0,
208
- };
209
- this.runtime.contentLoadId = 0;
210
-
211
- this.state = createDeepStore({
212
- content: props.content,
213
- visible: false,
214
- loading: false,
215
- }) as OffcanvasState;
216
- }
217
-
218
- build(): this {
219
- if (this.runtime.destroyed) {
220
- throw new Error('Offcanvas.build: instance has been destroyed.');
221
- }
222
- if (this.runtime.built) return this;
223
-
224
- try {
225
- this.dom.root = this.buildRoot();
226
- this.runtime.built = true;
227
- this.bindState();
228
- } catch (error) {
229
- this.destroy();
230
- throw error;
231
- }
232
-
233
- this.emit('init', this.props);
234
- return this;
235
- }
236
-
237
- private assertBuilt(method: string): void {
238
- if (this.runtime.destroyed) {
239
- throw new Error(`Offcanvas.${method}: instance has been destroyed.`);
240
- }
241
- if (!this.runtime.built) {
242
- throw new Error(`Offcanvas.${method}: call build() first.`);
243
- }
244
- }
245
-
246
- private buildRoot(): HTMLElement {
247
- if (this.props.overlay) {
248
- this.dom.overlay = this.buildOverlay();
249
- }
250
-
251
- return this.buildPanel();
252
- }
253
-
254
- private buildOverlay(): HTMLElement {
255
- const filterStyle = this.props.filter
256
- ? { backdropFilter: 'blur(2px)' }
257
- : {};
258
- return jsx('div', {
259
- className: this.props.className.overlay,
260
- hidden: true,
261
- 'data-offcanvas-overlay': this.props.id,
262
- style: {
263
- ...filterStyle,
264
- },
265
- }) as HTMLElement;
266
- }
191
+ interface OffcanvasActions {
192
+ show(): Promise<void>;
193
+ hide(): Promise<void>;
194
+ }
267
195
 
268
- private buildPanel(): HTMLElement {
269
- const content = jsx('div', {
270
- className: this.props.className.content,
271
- 'data-offcanvas-content': this.props.id,
272
- }) as HTMLElement;
273
- this.dom.content = content;
196
+ let offcanvasScrollLockCount = 0;
197
+ let offcanvasBodyOverflow = '';
274
198
 
275
- if (typeof this.state.content !== 'function') {
276
- this.renderContent(this.state.content);
199
+ export type Offcanvas = FunctionalComponent<
200
+ ResolvedOffcanvasProps,
201
+ OffcanvasState,
202
+ HTMLElement,
203
+ OffcanvasActions
204
+ >;
205
+
206
+ function validateStatePatch(patch: Partial<OffcanvasState>): void {
207
+ validateParam('state', patch, { type: 'plainObject' }, 'Offcanvas.setState');
208
+ for (const key of Object.keys(patch)) {
209
+ if (!Object.hasOwn(OFFCANVAS_STATE_SCHEMA, key)) {
210
+ throw new Error(
211
+ `Offcanvas.setState: "${key}" is not a supported state key.`
212
+ );
277
213
  }
278
-
279
- return jsx('div', {
280
- className: this.props.className.root,
281
- id: this.props.id,
282
- role: 'dialog',
283
- 'aria-modal': this.props.overlay ? 'true' : 'false',
284
- 'aria-expanded': 'false',
285
- 'data-offcanvas': 'root',
286
- 'data-direction': this.props.direction,
287
- 'data-animate': this.props.animate,
288
- children: content,
289
- }) as HTMLElement;
290
- }
291
-
292
- private bindState(): void {
293
- if (this.cleanup.state) return;
294
-
295
- this.cleanup.state = createStateSync(
296
- () => this.state.content,
297
- (content) => this.syncContent(content)
298
- );
299
- }
300
-
301
- private async syncContent(content: OffcanvasContent): Promise<void> {
302
- if (!this.runtime.built) return;
214
+ const stateKey = key as keyof typeof OFFCANVAS_STATE_SCHEMA;
303
215
  validateParam(
304
- 'content',
305
- content,
306
- OFFCANVAS_STATE_SCHEMA.content,
307
- 'Offcanvas.state'
216
+ key,
217
+ patch[key as keyof OffcanvasState],
218
+ OFFCANVAS_STATE_SCHEMA[stateKey],
219
+ 'Offcanvas.setState'
308
220
  );
309
- this.runtime.cache.hasContent = false;
310
- this.runtime.cache.content = null;
311
- this.runtime.cache.updatedAt = 0;
312
-
313
- if (typeof content === 'function') {
314
- if (this.state.visible) await this.loadContent();
315
- else this.clearContent();
316
- return;
317
- }
318
-
319
- this.runtime.contentLoadId += 1;
320
- flushSync(() => {
321
- this.state.loading = false;
322
- });
323
- this.renderContent(content);
324
221
  }
222
+ }
325
223
 
326
- private isCacheValid(): boolean {
327
- if (!this.props.cache || !this.runtime.cache.hasContent) return false;
328
-
329
- const ttl = normalizeTtl(this.props.ttl);
330
- return !ttl || Date.now() - this.runtime.cache.updatedAt <= ttl;
331
- }
224
+ export function createOffcanvas(input: OffcanvasProps = {}): Offcanvas {
225
+ const props = normalizeProps(input);
226
+ const state = createDeepStore({
227
+ content: props.content,
228
+ resolvedContent: typeof props.content === 'function' ? null : props.content,
229
+ visible: false,
230
+ loading: false,
231
+ }) as OffcanvasState;
232
+ const runtime: OffcanvasRuntimeExtras = {
233
+ cache: { content: null, hasContent: false, updatedAt: 0 },
234
+ contentLoadId: 0,
235
+ };
236
+ const events = createEventManager();
237
+ const overlayRef = createElementRef<HTMLElement>();
238
+ let offcanvas!: Offcanvas;
239
+ let scrollLocked = false;
240
+
241
+ const lockScroll = (): void => {
242
+ if (!props.bodyOverflow || scrollLocked) return;
243
+ if (offcanvasScrollLockCount === 0) {
244
+ offcanvasBodyOverflow = document.body.style.overflow;
245
+ document.body.style.overflow = 'hidden';
246
+ }
247
+ offcanvasScrollLockCount += 1;
248
+ scrollLocked = true;
249
+ };
250
+ const unlockScroll = (): void => {
251
+ if (!scrollLocked) return;
252
+ offcanvasScrollLockCount = Math.max(0, offcanvasScrollLockCount - 1);
253
+ if (offcanvasScrollLockCount === 0) {
254
+ document.body.style.overflow = offcanvasBodyOverflow;
255
+ offcanvasBodyOverflow = '';
256
+ }
257
+ scrollLocked = false;
258
+ };
332
259
 
333
- private clearContent(): void {
334
- if (this.dom.content) this.dom.content.textContent = '';
335
- }
260
+ const panelMotion = createTransition(() => offcanvas.element, {
261
+ keyframes: [
262
+ { transform: OFFCANVAS_HIDDEN_TRANSFORM[props.direction] },
263
+ { transform: 'translate(0, 0)' },
264
+ ],
265
+ options: { duration: 300, easing: 'ease' },
266
+ });
267
+ const motion = createMotionGroup(
268
+ ...(props.animate === 'slide' ? [panelMotion] : []),
269
+ createTransition(() => overlayRef.current, {
270
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
271
+ options: { duration: 300, easing: 'ease' },
272
+ })
273
+ );
274
+
275
+ const presence = createPresence({
276
+ elements: () => [overlayRef.current, offcanvas.element],
277
+ mount: () => {
278
+ const overlay = overlayRef.current;
279
+ if (overlay && !overlay.isConnected) insert(document.body, overlay);
280
+ offcanvas.mount(document.body);
281
+ lockScroll();
282
+ },
283
+ activate: () => {
284
+ state.visible = true;
285
+ },
286
+ deactivate: () => {
287
+ state.visible = false;
288
+ },
289
+ motion,
290
+ unmount: () => {
291
+ overlayRef.current?.remove();
292
+ offcanvas.unmount();
293
+ unlockScroll();
294
+ },
295
+ });
336
296
 
337
- private renderContent(content: RenderableContent<Offcanvas>): void {
338
- if (!this.dom.content) return;
297
+ const clearCache = (): void => {
298
+ runtime.cache = { content: null, hasContent: false, updatedAt: 0 };
299
+ };
339
300
 
340
- this.clearContent();
341
- this.dom.content.append(...normalizeContentNodes(content, this));
342
- }
301
+ const isCacheValid = (): boolean => {
302
+ if (!props.cache || !runtime.cache.hasContent) return false;
303
+ const ttl = normalizeTtl(props.ttl);
304
+ return !ttl || Date.now() - runtime.cache.updatedAt <= ttl;
305
+ };
343
306
 
344
- private async loadContent(): Promise<void> {
345
- const { cache } = this.props;
346
- const content = this.state.content;
307
+ const loadContent = async (): Promise<void> => {
308
+ const content = state.content;
347
309
  if (typeof content !== 'function') {
348
- this.runtime.contentLoadId += 1;
310
+ runtime.contentLoadId += 1;
349
311
  flushSync(() => {
350
- this.state.loading = false;
312
+ state.resolvedContent = content;
313
+ state.loading = false;
351
314
  });
352
315
  return;
353
316
  }
354
-
355
- if (this.isCacheValid()) {
356
- this.runtime.contentLoadId += 1;
357
- this.renderContent(this.runtime.cache.content);
317
+ if (isCacheValid()) {
318
+ runtime.contentLoadId += 1;
358
319
  flushSync(() => {
359
- this.state.loading = false;
320
+ state.resolvedContent = runtime.cache.content;
321
+ state.loading = false;
360
322
  });
361
323
  return;
362
324
  }
363
325
 
364
- const loadId = ++this.runtime.contentLoadId;
326
+ const loadId = ++runtime.contentLoadId;
365
327
  flushSync(() => {
366
- this.state.loading = true;
328
+ state.loading = true;
329
+ state.resolvedContent = null;
367
330
  });
368
-
369
- if (this.dom.content) {
370
- this.clearContent();
371
- this.dom.content.appendChild(createLoading());
372
- }
373
-
374
331
  try {
375
- const result = await Promise.resolve(content(this));
376
-
377
- if (this.runtime.destroyed || loadId !== this.runtime.contentLoadId) {
332
+ const result = await Promise.resolve(content(offcanvas));
333
+ if (offcanvas.runtime.destroyed || loadId !== runtime.contentLoadId)
378
334
  return;
335
+ if (props.cache) {
336
+ runtime.cache = {
337
+ content: result,
338
+ hasContent: true,
339
+ updatedAt: Date.now(),
340
+ };
379
341
  }
380
-
381
- if (cache) {
382
- this.runtime.cache.content = result;
383
- this.runtime.cache.hasContent = true;
384
- this.runtime.cache.updatedAt = Date.now();
385
- }
386
- this.renderContent(result);
342
+ flushSync(() => {
343
+ state.resolvedContent = result;
344
+ });
387
345
  } finally {
388
- if (!this.runtime.destroyed && loadId === this.runtime.contentLoadId) {
346
+ if (!offcanvas.runtime.destroyed && loadId === runtime.contentLoadId) {
389
347
  flushSync(() => {
390
- this.state.loading = false;
348
+ state.loading = false;
391
349
  });
392
350
  }
393
351
  }
394
- }
395
-
396
- private bindEvents(): void {
397
- this.unbindEvents();
398
- const { bgClose, escClose } = this.props;
399
-
400
- if (this.dom.overlay && bgClose) {
401
- this.cleanup.events.on('overlay', this.dom.overlay, 'click', () => {
402
- void this.hide();
403
- });
404
- }
405
- if (escClose) {
406
- this.cleanup.events.on('esc', document, 'keydown', (event) => {
407
- if (
408
- event instanceof KeyboardEvent &&
409
- event.key === 'Escape' &&
410
- this.state.visible
411
- ) {
412
- void this.hide();
413
- }
414
- });
415
- }
416
- if (this.dom.root) {
417
- this.cleanup.events.on('close', this.dom.root, 'click', (event) => {
418
- if (!(event.target instanceof Element)) return;
419
- const action = event.target.closest<HTMLElement>('[data-action]');
420
- const name = action?.dataset.action;
421
- if (name === 'close' || name === 'cancel') void this.hide();
422
- });
423
- }
424
- }
425
-
426
- private unbindEvents(): void {
427
- this.cleanup.events.clear();
428
- }
429
-
430
- private showPanel(): void {
431
- const { id } = this.props;
432
- timer.cancel(`oc-hide-${id}`);
433
-
434
- if (this.dom.overlay && !this.dom.overlay.isConnected) {
435
- this.dom.overlay.hidden = false;
436
- document.body.appendChild(this.dom.overlay);
437
- }
438
- if (this.dom.root && !this.dom.root.isConnected) {
439
- document.body.appendChild(this.dom.root);
440
- }
441
- if (this.props.bodyOverflow) {
442
- document.body.style.overflow = 'hidden';
443
- }
444
-
445
- timer.start(`oc-show-${id}`, 10, () => {
446
- this.dom.root?.setAttribute('aria-expanded', 'true');
447
- });
448
- }
449
-
450
- private hidePanel(): void {
451
- const { id } = this.props;
452
- timer.cancel(`oc-show-${id}`);
453
- timer.cancel(`oc-shown-${id}`);
454
-
455
- this.dom.root?.setAttribute('aria-expanded', 'false');
352
+ };
456
353
 
457
- timer.start(`oc-hide-${id}`, 100, () => {
458
- this.dom.overlay?.remove();
459
- this.dom.root?.remove();
460
- if (this.props.bodyOverflow) {
461
- document.body.style.overflow = '';
354
+ const actions: OffcanvasActions = {
355
+ async show() {
356
+ if (!offcanvas.runtime.built) {
357
+ throw new Error('Offcanvas.show: call build() first.');
462
358
  }
463
- });
464
- }
465
-
466
- async show(): Promise<void> {
467
- this.assertBuilt('show');
468
- if (this.state.visible) return;
469
-
470
- const { onShow, onShown, id } = this.props;
471
- if (onShow) await Promise.resolve(onShow(this));
472
-
473
- this.showPanel();
474
- this.bindEvents();
475
-
476
- flushSync(() => {
477
- this.state.visible = true;
478
- });
479
-
480
- await this.loadContent();
481
-
482
- timer.start(`oc-shown-${id}`, 300, () => {
483
- if (this.runtime.destroyed) return;
484
- void onShown?.(this);
485
- });
486
- }
487
-
488
- async hide(): Promise<void> {
489
- this.assertBuilt('hide');
490
- if (!this.state.visible) return;
491
-
492
- const { onHide, onHidden } = this.props;
493
- if (onHide) await Promise.resolve(onHide(this));
494
-
495
- this.runtime.contentLoadId += 1;
496
- flushSync(() => {
497
- this.state.loading = false;
498
- });
499
-
500
- this.unbindEvents();
501
- this.hidePanel();
502
-
503
- flushSync(() => {
504
- this.state.visible = false;
505
- });
506
-
507
- void onHidden?.(this);
508
- }
509
-
510
- protected onDestroy(): void {
511
- const { id } = this.props;
512
-
513
- timer.cancel(`oc-show-${id}`);
514
- timer.cancel(`oc-hide-${id}`);
515
- timer.cancel(`oc-shown-${id}`);
359
+ if (state.visible) return;
360
+ await Promise.resolve(props.onShow?.(offcanvas));
516
361
 
517
- this.unbindEvents();
518
- this.cleanup.state?.();
519
- this.cleanup.state = null;
520
-
521
- this.dom.root?.setAttribute('aria-expanded', 'false');
522
- this.dom.overlay?.remove();
523
- this.dom.root?.remove();
524
- if (this.props.bodyOverflow) {
525
- document.body.style.overflow = '';
526
- }
527
-
528
- this.runtime.built = false;
529
- }
362
+ const entering = presence.enter();
363
+ const loading = loadContent();
364
+ if ((await entering) && !offcanvas.runtime.destroyed) {
365
+ void props.onShown?.(offcanvas);
366
+ }
367
+ await loading;
368
+ },
369
+ async hide() {
370
+ if (!offcanvas.runtime.built) {
371
+ throw new Error('Offcanvas.hide: call build() first.');
372
+ }
373
+ if (!state.visible) return;
374
+ const onHideResult = props.onHide?.(offcanvas);
375
+ if (onHideResult) await onHideResult;
530
376
 
531
- protected override validateStatePatch(patch: Partial<OffcanvasState>): void {
532
- validateParam(
533
- 'state',
534
- patch,
535
- {
536
- type: 'plainObject',
537
- },
538
- 'Offcanvas.setState'
539
- );
377
+ runtime.contentLoadId += 1;
378
+ flushSync(() => {
379
+ state.loading = false;
380
+ });
381
+ if (await presence.leave()) {
382
+ void props.onHidden?.(offcanvas);
383
+ }
384
+ },
385
+ };
540
386
 
541
- for (const key of Object.keys(patch)) {
542
- if (!Object.hasOwn(OFFCANVAS_STATE_SCHEMA, key)) {
543
- throw new Error(
544
- `Offcanvas.setState: "${key}" is not a supported state key.`
387
+ offcanvas = defineComponent({
388
+ name: 'Offcanvas',
389
+ props,
390
+ state,
391
+ actions,
392
+ validateStatePatch,
393
+ view(context) {
394
+ const overlay = props.overlay
395
+ ? (jsx('div', {
396
+ ref: overlayRef.set,
397
+ className: props.className.overlay,
398
+ 'data-offcanvas-overlay': props.id,
399
+ 'data-mount': () => (state.visible ? 'true' : 'false'),
400
+ style: props.filter ? { backdropFilter: 'blur(2px)' } : {},
401
+ onClick: () => {
402
+ if (props.bgClose) void offcanvas.hide();
403
+ },
404
+ }) as HTMLElement)
405
+ : null;
406
+ if (overlay) context.own(() => overlay.remove());
407
+
408
+ createEffect(() => {
409
+ const content = state.content;
410
+ validateParam(
411
+ 'content',
412
+ content,
413
+ OFFCANVAS_STATE_SCHEMA.content,
414
+ 'Offcanvas.state'
545
415
  );
546
- }
547
- const stateKey = key as keyof typeof OFFCANVAS_STATE_SCHEMA;
548
- validateParam(
549
- key,
550
- patch[key as keyof OffcanvasState],
551
- OFFCANVAS_STATE_SCHEMA[stateKey],
552
- 'Offcanvas.setState'
553
- );
554
- }
555
- }
556
- }
416
+ clearCache();
417
+ if (typeof content === 'function') {
418
+ state.resolvedContent = null;
419
+ if (untrack(() => state.visible)) void loadContent();
420
+ } else {
421
+ runtime.contentLoadId += 1;
422
+ state.resolvedContent = content;
423
+ state.loading = false;
424
+ }
425
+ });
557
426
 
558
- export type Offcanvas = OffcanvasComponent;
427
+ return jsx('div', {
428
+ className: props.className.root,
429
+ id: props.id,
430
+ role: 'dialog',
431
+ 'aria-modal': props.overlay ? 'true' : 'false',
432
+ 'aria-expanded': () => String(state.visible),
433
+ 'data-mount': () => (state.visible ? 'true' : 'false'),
434
+ 'data-offcanvas': 'root',
435
+ 'data-direction': props.direction,
436
+ 'data-animate': props.animate,
437
+ onClick: (event: Event) => {
438
+ if (!(event.target instanceof Element)) return;
439
+ const action = event.target.closest<HTMLElement>('[data-action]');
440
+ if (
441
+ action?.dataset.action === 'close' ||
442
+ action?.dataset.action === 'cancel'
443
+ ) {
444
+ void offcanvas.hide();
445
+ }
446
+ },
447
+ children: jsx('div', {
448
+ className: props.className.content,
449
+ 'data-offcanvas-content': props.id,
450
+ children: () =>
451
+ state.loading
452
+ ? createLoading()
453
+ : typeof state.resolvedContent === 'function'
454
+ ? state.resolvedContent(offcanvas)
455
+ : state.resolvedContent,
456
+ }),
457
+ }) as HTMLElement;
458
+ },
459
+ onBuild(context) {
460
+ if (props.escClose) {
461
+ events.on('esc', document, 'keydown', (event) => {
462
+ if (
463
+ event instanceof KeyboardEvent &&
464
+ event.key === 'Escape' &&
465
+ state.visible
466
+ ) {
467
+ void offcanvas.hide();
468
+ }
469
+ });
470
+ }
471
+ context.own(() => events.clear());
472
+ },
473
+ onDestroy() {
474
+ presence.cancel();
475
+ runtime.contentLoadId += 1;
476
+ overlayRef.current?.remove();
477
+ unlockScroll();
478
+ },
479
+ });
559
480
 
560
- export function createOffcanvas(input: OffcanvasProps = {}): Offcanvas {
561
- return new OffcanvasComponent(input);
481
+ return offcanvas;
562
482
  }