vanilla-jui 1.5.20 → 1.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.5.20",
3
+ "version": "1.5.21",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -51,6 +51,7 @@ export interface ToastThemeOptions extends ToastClassNameOptions {
51
51
 
52
52
  export interface ToastOptions extends ToastThemeOptions {
53
53
  duration?: number;
54
+ once?: boolean;
54
55
  loading?: MaybeAccessor<boolean>;
55
56
  text?: {
56
57
  loading?: string;
@@ -60,6 +61,7 @@ export interface ToastOptions extends ToastThemeOptions {
60
61
  }
61
62
 
62
63
  export interface ToastConfirmProps extends ToastThemeOptions {
64
+ once?: boolean;
63
65
  text?: {
64
66
  close?: string;
65
67
  confirm?: string;
@@ -97,6 +99,7 @@ const timers = new Set<string>();
97
99
  const disposers = new Map<HTMLElement, () => void>();
98
100
  const animations = new Map<HTMLElement, Animation>();
99
101
  const operations = new Map<HTMLElement, number>();
102
+ const singletonToasts = new Map<'show' | 'confirm', HTMLElement>();
100
103
  let classNames = DEFAULT_CLASS_NAMES;
101
104
 
102
105
  function mergeClassNames(value?: ToastClassNameConfig): ToastClassNames {
@@ -115,6 +118,30 @@ function addToastDisposer(element: HTMLElement, dispose: () => void): void {
115
118
  });
116
119
  }
117
120
 
121
+ function getSingletonToast(kind: 'show' | 'confirm'): HTMLElement | null {
122
+ const toast = singletonToasts.get(kind);
123
+ if (!toast) return null;
124
+ if (!toast.isConnected || toast.getAttribute('data-mount') === 'false') {
125
+ singletonToasts.delete(kind);
126
+ return null;
127
+ }
128
+ return toast;
129
+ }
130
+
131
+ function setSingletonToast(
132
+ kind: 'show' | 'confirm',
133
+ toast: HTMLElement,
134
+ once: boolean
135
+ ): void {
136
+ if (once) singletonToasts.set(kind, toast);
137
+ }
138
+
139
+ function clearSingletonToast(toast: HTMLElement): void {
140
+ for (const [kind, current] of singletonToasts) {
141
+ if (current === toast) singletonToasts.delete(kind);
142
+ }
143
+ }
144
+
118
145
  function isToastLoading(options: ToastOptions): boolean {
119
146
  return !!access(options.loading ?? false);
120
147
  }
@@ -229,6 +256,7 @@ function removeToastElement(element: HTMLElement): void {
229
256
  element.remove();
230
257
  disposers.get(element)?.();
231
258
  disposers.delete(element);
259
+ clearSingletonToast(element);
232
260
  const container = q<HTMLElement>('[data-toast-container]');
233
261
  if (container && container.children.length === 0) container.remove();
234
262
  }
@@ -342,9 +370,15 @@ function hide(toast: HTMLElement | null | undefined): void {
342
370
  }
343
371
 
344
372
  function show(message = '', options: ToastOptions = {}): HTMLElement {
373
+ const once = options.once ?? false;
374
+ if (once) {
375
+ const toast = getSingletonToast('show');
376
+ if (toast) return toast;
377
+ }
345
378
  validateParam('message', message, 'string', 'Toast.show');
346
379
  const duration = options.duration ?? 3000;
347
380
  const theme = options.theme || 'info';
381
+ validateParam('once', once, 'boolean', 'Toast.show');
348
382
  validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.show');
349
383
  validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.show');
350
384
 
@@ -380,6 +414,7 @@ function show(message = '', options: ToastOptions = {}): HTMLElement {
380
414
  () => insert(getOrCreateContainer(names), toast),
381
415
  theme === 'error' ? 'assertive' : 'polite'
382
416
  );
417
+ setSingletonToast('show', toast, once);
383
418
  addToastDisposer(
384
419
  toast,
385
420
  listen(toast, 'click', () => void closeByUser(toast, options))
@@ -415,10 +450,16 @@ function lite(
415
450
  }
416
451
 
417
452
  function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
453
+ const once = props.once ?? true;
454
+ if (once) {
455
+ const toast = getSingletonToast('confirm');
456
+ if (toast) return toast;
457
+ }
418
458
  validateParam('message', message, 'string', 'Toast.confirm');
419
459
  const closeText = props.text?.close || s('Close');
420
460
  const confirmText = props.text?.confirm || s('Confirm');
421
461
  const theme = props.theme || 'info';
462
+ validateParam('once', once, 'boolean', 'Toast.confirm');
422
463
  validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.confirm');
423
464
  const names = resolveClassNames(props);
424
465
  const id = randomId();
@@ -468,6 +509,7 @@ function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
468
509
  q<HTMLButtonElement>('[data-toast-button="confirm"]', element)?.focus();
469
510
  }
470
511
  );
512
+ setSingletonToast('confirm', element, once);
471
513
  return element;
472
514
  }
473
515
 
@@ -479,6 +521,7 @@ function clearAll(): void {
479
521
  for (const animation of animations.values()) animation.cancel();
480
522
  animations.clear();
481
523
  operations.clear();
524
+ singletonToasts.clear();
482
525
  q<HTMLElement>('[data-toast-container]')?.remove();
483
526
  q<HTMLElement>('[data-toast-lite]')?.remove();
484
527
  }