vanilla-jui 1.7.31 → 1.7.32
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/dist/index.d.ts +24 -4
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/accordion.ts +22 -8
- package/src/components/flow.ts +1 -1
- package/src/components/form.ts +1 -1
- package/src/components/modal.ts +1 -1
- package/src/components/offcanvas.ts +2 -2
- package/src/components/pagination.ts +5 -5
- package/src/components/tabs.ts +132 -16
- package/src/css/animation.css +7 -3
- package/src/css/card.css +10 -10
- package/src/css/font.css +3 -6
- package/src/css/icon.css +3 -20
- package/src/css/layout.css +11 -18
- package/src/css/status.css +39 -3
- package/src/css/themes/default/accordion.css +13 -45
- package/src/css/themes/default/avatar.css +2 -3
- package/src/css/themes/default/badge.css +14 -22
- package/src/css/themes/default/breadcrumb.css +11 -12
- package/src/css/themes/default/button.css +101 -159
- package/src/css/themes/default/editor.css +36 -36
- package/src/css/themes/default/flow.css +4 -4
- package/src/css/themes/default/form.css +110 -217
- package/src/css/themes/default/loading.css +48 -63
- package/src/css/themes/default/menu.css +17 -17
- package/src/css/themes/default/modal.css +9 -9
- package/src/css/themes/default/offcanvas.css +3 -3
- package/src/css/themes/default/swiper.css +18 -18
- package/src/css/themes/default/tabs.css +38 -24
- package/src/css/themes/default/theme-palette.css +22 -18
- package/src/css/themes/default/tip.css +24 -13
- package/src/css/themes/default/toast.css +65 -45
- package/src/css/themes/default/toolbar.css +2 -2
- package/src/css/themes/default/tooltip.css +2 -3
- package/src/css/tokens.css +89 -59
- package/src/css/tokens_/345/211/257/346/234/254.css +4 -4
- package/src/icons/index.ts +9 -7
- package/src/locales/index.ts +3 -1
- package/src/primitives/theme.ts +7 -10
- package/src/primitives/toast.ts +191 -107
- package/src/utilities/browser.ts +1 -1
package/src/primitives/toast.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
access,
|
|
4
4
|
createEffect,
|
|
5
5
|
createRoot,
|
|
6
|
+
createSignal,
|
|
6
7
|
insert,
|
|
7
8
|
jsx,
|
|
8
9
|
} from 'vanilla-signal';
|
|
@@ -23,6 +24,7 @@ export interface ToastClassNames {
|
|
|
23
24
|
toast: string;
|
|
24
25
|
icon: string;
|
|
25
26
|
message: string;
|
|
27
|
+
description: string;
|
|
26
28
|
lite: string;
|
|
27
29
|
confirm: string;
|
|
28
30
|
buttons: string;
|
|
@@ -46,35 +48,55 @@ export interface ToastThemeOptions extends ToastClassNameOptions {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface ToastOptions extends ToastThemeOptions {
|
|
51
|
+
description?: string;
|
|
49
52
|
duration?: number;
|
|
50
53
|
once?: boolean;
|
|
54
|
+
onClose?: () => void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LoadingToastOptions extends ToastOptions {
|
|
51
58
|
loading?: MaybeAccessor<boolean>;
|
|
52
59
|
text?: {
|
|
53
60
|
loading?: string;
|
|
54
61
|
};
|
|
55
|
-
onClose?: () => void | Promise<void>;
|
|
56
62
|
onCancel?: () => void | Promise<void>;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
export interface
|
|
60
|
-
once?: boolean;
|
|
65
|
+
export interface ConfirmToastOptions extends ToastOptions {
|
|
61
66
|
text?: {
|
|
62
67
|
close?: string;
|
|
63
68
|
confirm?: string;
|
|
64
69
|
};
|
|
65
70
|
onConfirm?: () => void | Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ToastConfirmProps = ConfirmToastOptions;
|
|
74
|
+
|
|
75
|
+
export interface PromiseToastOptions extends ToastClassNameOptions {
|
|
76
|
+
duration?: number;
|
|
77
|
+
text?: { loading?: string; success?: string; error?: string };
|
|
78
|
+
onCancel?: () => void | Promise<void>;
|
|
66
79
|
onClose?: () => void | Promise<void>;
|
|
80
|
+
onSuccess?: () => void | Promise<void>;
|
|
81
|
+
onError?: () => void | Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ToastStatus {
|
|
85
|
+
message: string;
|
|
86
|
+
theme: ToastTheme;
|
|
87
|
+
loading: boolean;
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
const DEFAULT_CLASS_NAMES: ToastClassNames = {
|
|
70
91
|
container: 'j-toast-container',
|
|
71
92
|
toast: 'j-toast',
|
|
72
93
|
icon: 'el-icon',
|
|
73
|
-
message: '
|
|
94
|
+
message: 'toast-title',
|
|
95
|
+
description: 'toast-description',
|
|
74
96
|
lite: 'j-toast-lite',
|
|
75
97
|
confirm: 'j-toast is-confirm',
|
|
76
|
-
buttons: 'toast-
|
|
77
|
-
button: 'j-
|
|
98
|
+
buttons: 'toast-btns',
|
|
99
|
+
button: 'j-btn is-sm',
|
|
78
100
|
closeBtn: 'is-ghost',
|
|
79
101
|
confirmBtn: 'is-reverse',
|
|
80
102
|
info: 'is-info',
|
|
@@ -94,7 +116,8 @@ const timers = new Set<string>();
|
|
|
94
116
|
const disposers = new Map<HTMLElement, () => void>();
|
|
95
117
|
const animations = new Map<HTMLElement, Animation>();
|
|
96
118
|
const operations = new Map<HTMLElement, number>();
|
|
97
|
-
|
|
119
|
+
type ToastKind = 'show' | 'confirm' | 'loading';
|
|
120
|
+
const singletonToasts = new Map<ToastKind, HTMLElement>();
|
|
98
121
|
let classNames = DEFAULT_CLASS_NAMES;
|
|
99
122
|
|
|
100
123
|
function addToastDisposer(element: HTMLElement, dispose: () => void): void {
|
|
@@ -105,7 +128,7 @@ function addToastDisposer(element: HTMLElement, dispose: () => void): void {
|
|
|
105
128
|
});
|
|
106
129
|
}
|
|
107
130
|
|
|
108
|
-
function getSingletonToast(kind:
|
|
131
|
+
function getSingletonToast(kind: ToastKind): HTMLElement | null {
|
|
109
132
|
const toast = singletonToasts.get(kind);
|
|
110
133
|
if (!toast) return null;
|
|
111
134
|
if (!toast.isConnected || toast.getAttribute('data-mount') === 'false') {
|
|
@@ -116,7 +139,7 @@ function getSingletonToast(kind: 'show' | 'confirm'): HTMLElement | null {
|
|
|
116
139
|
}
|
|
117
140
|
|
|
118
141
|
function setSingletonToast(
|
|
119
|
-
kind:
|
|
142
|
+
kind: ToastKind,
|
|
120
143
|
toast: HTMLElement,
|
|
121
144
|
once: boolean
|
|
122
145
|
): void {
|
|
@@ -129,77 +152,20 @@ function clearSingletonToast(toast: HTMLElement): void {
|
|
|
129
152
|
}
|
|
130
153
|
}
|
|
131
154
|
|
|
132
|
-
function
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function toastIconStyle(options: ToastOptions): Record<string, string> {
|
|
137
|
-
return isToastLoading(options)
|
|
155
|
+
function toastIconStyle(loading: boolean): Record<string, string> {
|
|
156
|
+
return loading
|
|
138
157
|
? {
|
|
139
158
|
position: 'relative',
|
|
140
159
|
display: 'inline-flex',
|
|
141
160
|
alignItems: 'center',
|
|
142
161
|
justifyContent: 'center',
|
|
143
|
-
width: '
|
|
144
|
-
height: '
|
|
162
|
+
width: '1.25rem',
|
|
163
|
+
height: '1.25rem',
|
|
145
164
|
flex: '0 0 auto',
|
|
146
165
|
}
|
|
147
166
|
: {};
|
|
148
167
|
}
|
|
149
168
|
|
|
150
|
-
function toastIconContent(
|
|
151
|
-
theme: ToastTheme,
|
|
152
|
-
options: ToastOptions
|
|
153
|
-
): HTMLElement | SVGElement | null {
|
|
154
|
-
if (isToastLoading(options)) return createLoading();
|
|
155
|
-
return theme ? icon(theme) : null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function toastMessage(message: string, options: ToastOptions): string {
|
|
159
|
-
return isToastLoading(options)
|
|
160
|
-
? options.text?.loading || translate('Loading...')
|
|
161
|
-
: message;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function closeByUser(
|
|
165
|
-
toast: HTMLElement,
|
|
166
|
-
options: ToastOptions
|
|
167
|
-
): Promise<void> {
|
|
168
|
-
const shouldCancel = isToastLoading(options);
|
|
169
|
-
hide(toast);
|
|
170
|
-
if (shouldCancel) await options.onCancel?.();
|
|
171
|
-
await options.onClose?.();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function bindToastStatus(
|
|
175
|
-
element: HTMLElement,
|
|
176
|
-
iconElement: HTMLElement,
|
|
177
|
-
messageElement: HTMLElement,
|
|
178
|
-
message: string,
|
|
179
|
-
theme: ToastTheme,
|
|
180
|
-
options: ToastOptions,
|
|
181
|
-
duration = 0
|
|
182
|
-
): void {
|
|
183
|
-
const id = element.dataset.toast || randomId();
|
|
184
|
-
const dispose = createRoot((dispose) => {
|
|
185
|
-
insert(
|
|
186
|
-
element,
|
|
187
|
-
() => (theme || isToastLoading(options) ? iconElement : null),
|
|
188
|
-
messageElement
|
|
189
|
-
);
|
|
190
|
-
insert(iconElement, () => toastIconContent(theme, options));
|
|
191
|
-
insert(messageElement, () => toastMessage(message, options));
|
|
192
|
-
createEffect(() => {
|
|
193
|
-
cancelToastTimers(id);
|
|
194
|
-
if (duration > 0 && !isToastLoading(options)) {
|
|
195
|
-
setToastTimer(id, 'hide', () => hide(element), duration);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
return dispose;
|
|
199
|
-
});
|
|
200
|
-
addToastDisposer(element, dispose);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
169
|
function getOrCreateContainer(names: ToastClassNames): HTMLElement {
|
|
204
170
|
let container = q<HTMLElement>('[data-toast-container]');
|
|
205
171
|
if (!container) {
|
|
@@ -357,10 +323,16 @@ function hide(toast: HTMLElement | null | undefined): void {
|
|
|
357
323
|
});
|
|
358
324
|
}
|
|
359
325
|
|
|
360
|
-
function
|
|
326
|
+
function createToast(
|
|
327
|
+
message: string,
|
|
328
|
+
options: ToastOptions,
|
|
329
|
+
status: () => ToastStatus,
|
|
330
|
+
kind: ToastKind = 'show',
|
|
331
|
+
onCancel?: () => void | Promise<void>
|
|
332
|
+
): HTMLElement {
|
|
361
333
|
const once = options.once ?? false;
|
|
362
334
|
if (once) {
|
|
363
|
-
const toast = getSingletonToast(
|
|
335
|
+
const toast = getSingletonToast(kind);
|
|
364
336
|
if (toast) return toast;
|
|
365
337
|
}
|
|
366
338
|
validateParam('message', message, 'string', 'Toast.show');
|
|
@@ -374,42 +346,140 @@ function show(message = '', options: ToastOptions = {}): HTMLElement {
|
|
|
374
346
|
classNames,
|
|
375
347
|
options.className ?? {}
|
|
376
348
|
) as ToastClassNames;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
349
|
+
return createRoot((dispose) => {
|
|
350
|
+
const id = randomId();
|
|
351
|
+
const iconElement = jsx('span', {
|
|
352
|
+
className: names.icon,
|
|
353
|
+
'data-toast-icon': id,
|
|
354
|
+
style: () => toastIconStyle(status().loading),
|
|
355
|
+
children: () =>
|
|
356
|
+
status().loading
|
|
357
|
+
? createLoading()
|
|
358
|
+
: status().theme
|
|
359
|
+
? icon(status().theme)
|
|
360
|
+
: null,
|
|
361
|
+
}) as HTMLElement;
|
|
362
|
+
const messageElement = jsx('span', {
|
|
363
|
+
className: names.message,
|
|
364
|
+
'data-toast-message': id,
|
|
365
|
+
children: () => status().message,
|
|
366
|
+
}) as HTMLElement;
|
|
367
|
+
const description = options.description ?? '';
|
|
368
|
+
const descriptionElement = description
|
|
369
|
+
? jsx('span', {
|
|
370
|
+
className: names.description,
|
|
371
|
+
'data-toast-description': id,
|
|
372
|
+
children: description,
|
|
373
|
+
})
|
|
374
|
+
: null;
|
|
375
|
+
const toast = jsx('div', {
|
|
376
|
+
className: () =>
|
|
377
|
+
joinClasses(
|
|
378
|
+
names.toast,
|
|
379
|
+
status().theme ? names[status().theme as Exclude<ToastTheme, ''>] : ''
|
|
380
|
+
),
|
|
381
|
+
'data-toast': id,
|
|
382
|
+
role: 'alert',
|
|
383
|
+
'aria-atomic': 'true',
|
|
384
|
+
'aria-live': () => (status().theme === 'error' ? 'assertive' : 'polite'),
|
|
385
|
+
children: [messageElement, descriptionElement],
|
|
386
|
+
}) as HTMLElement;
|
|
387
|
+
insert(
|
|
388
|
+
toast,
|
|
389
|
+
() => (status().theme || status().loading ? iconElement : null),
|
|
390
|
+
messageElement
|
|
391
|
+
);
|
|
392
|
+
createEffect(() => {
|
|
393
|
+
cancelToastTimers(id);
|
|
394
|
+
if (duration > 0 && !status().loading)
|
|
395
|
+
setToastTimer(id, 'hide', () => hide(toast), duration);
|
|
396
|
+
});
|
|
397
|
+
addToastDisposer(toast, dispose);
|
|
398
|
+
mountToast(
|
|
399
|
+
toast,
|
|
400
|
+
() => insert(getOrCreateContainer(names), toast),
|
|
401
|
+
status().theme === 'error' ? 'assertive' : 'polite'
|
|
402
|
+
);
|
|
403
|
+
setSingletonToast(kind, toast, once);
|
|
404
|
+
addToastDisposer(
|
|
405
|
+
toast,
|
|
406
|
+
listen(toast, 'click', () => {
|
|
407
|
+
const pending = status().loading;
|
|
408
|
+
hide(toast);
|
|
409
|
+
void (async () => {
|
|
410
|
+
if (pending) await onCancel?.();
|
|
411
|
+
await options.onClose?.();
|
|
412
|
+
})();
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
return toast;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function show(message = '', options: ToastOptions = {}): HTMLElement {
|
|
420
|
+
return createToast(message, options, () => ({
|
|
421
|
+
message,
|
|
422
|
+
theme: options.theme ?? '',
|
|
423
|
+
loading: false,
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const LOADING_MESSAGE = translate('Loading') + '...';
|
|
428
|
+
|
|
429
|
+
function loading(message = '', options: LoadingToastOptions = {}): HTMLElement {
|
|
430
|
+
return createToast(
|
|
398
431
|
message,
|
|
399
|
-
theme,
|
|
400
432
|
options,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
listen(toast, 'click', () => void closeByUser(toast, options))
|
|
433
|
+
() => {
|
|
434
|
+
const pending = !!access(options.loading ?? false);
|
|
435
|
+
return {
|
|
436
|
+
message: pending ? (options.text?.loading ?? LOADING_MESSAGE) : message,
|
|
437
|
+
theme: pending ? '' : 'success',
|
|
438
|
+
loading: pending,
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
'loading',
|
|
442
|
+
options.onCancel
|
|
412
443
|
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function promise<T>(
|
|
447
|
+
task: Promise<T>,
|
|
448
|
+
options: PromiseToastOptions = {}
|
|
449
|
+
): HTMLElement {
|
|
450
|
+
const [status, setStatus] = createSignal<ToastStatus>({
|
|
451
|
+
message: options.text?.loading ?? LOADING_MESSAGE,
|
|
452
|
+
theme: '',
|
|
453
|
+
loading: true,
|
|
454
|
+
});
|
|
455
|
+
const toast = createToast('', options, status, 'show', options.onCancel);
|
|
456
|
+
let disposed = false;
|
|
457
|
+
addToastDisposer(toast, () => {
|
|
458
|
+
disposed = true;
|
|
459
|
+
});
|
|
460
|
+
// Observe both outcomes even after dismissal so a late rejection is consumed.
|
|
461
|
+
void task
|
|
462
|
+
.then(
|
|
463
|
+
async () => {
|
|
464
|
+
if (disposed) return;
|
|
465
|
+
setStatus({
|
|
466
|
+
message: options.text?.success ?? translate('Success'),
|
|
467
|
+
theme: 'success',
|
|
468
|
+
loading: false,
|
|
469
|
+
});
|
|
470
|
+
await options.onSuccess?.();
|
|
471
|
+
},
|
|
472
|
+
async () => {
|
|
473
|
+
if (disposed) return;
|
|
474
|
+
setStatus({
|
|
475
|
+
message: options.text?.error ?? translate('Error'),
|
|
476
|
+
theme: 'error',
|
|
477
|
+
loading: false,
|
|
478
|
+
});
|
|
479
|
+
await options.onError?.();
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
.catch(console.error);
|
|
413
483
|
return toast;
|
|
414
484
|
}
|
|
415
485
|
|
|
@@ -443,7 +513,7 @@ function lite(
|
|
|
443
513
|
return element;
|
|
444
514
|
}
|
|
445
515
|
|
|
446
|
-
function confirm(message = '', props:
|
|
516
|
+
function confirm(message = '', props: ConfirmToastOptions = {}): HTMLElement {
|
|
447
517
|
const once = props.once ?? true;
|
|
448
518
|
if (once) {
|
|
449
519
|
const toast = getSingletonToast('confirm');
|
|
@@ -453,13 +523,18 @@ function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
|
|
|
453
523
|
const closeText = props.text?.close || translate('Close');
|
|
454
524
|
const confirmText = props.text?.confirm || translate('Confirm');
|
|
455
525
|
validateParam('once', once, 'boolean', 'Toast.confirm');
|
|
526
|
+
const duration = props.duration ?? 3000;
|
|
527
|
+
const theme = props.theme ?? '';
|
|
528
|
+
validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.confirm');
|
|
529
|
+
validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.confirm');
|
|
530
|
+
const description = props.description ?? '';
|
|
456
531
|
const names = mergeShallowConfig(
|
|
457
532
|
classNames,
|
|
458
533
|
props.className ?? {}
|
|
459
534
|
) as ToastClassNames;
|
|
460
535
|
const id = randomId();
|
|
461
536
|
const element = jsx('div', {
|
|
462
|
-
className: names.confirm,
|
|
537
|
+
className: joinClasses(names.confirm, theme ? names[theme] : ''),
|
|
463
538
|
'data-toast': id,
|
|
464
539
|
'data-toast-confirm': '',
|
|
465
540
|
children: [
|
|
@@ -468,6 +543,12 @@ function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
|
|
|
468
543
|
'data-toast-message': id,
|
|
469
544
|
children: message,
|
|
470
545
|
}),
|
|
546
|
+
description
|
|
547
|
+
? jsx('div', {
|
|
548
|
+
className: names.description,
|
|
549
|
+
children: description,
|
|
550
|
+
})
|
|
551
|
+
: null,
|
|
471
552
|
jsx('div', {
|
|
472
553
|
className: names.buttons,
|
|
473
554
|
'data-toast-buttons': id,
|
|
@@ -505,6 +586,7 @@ function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
|
|
|
505
586
|
}
|
|
506
587
|
);
|
|
507
588
|
setSingletonToast('confirm', element, once);
|
|
589
|
+
if (duration > 0) setToastTimer(id, 'hide', () => hide(element), duration);
|
|
508
590
|
return element;
|
|
509
591
|
}
|
|
510
592
|
|
|
@@ -532,6 +614,8 @@ export const Toast = {
|
|
|
532
614
|
return { className: classNames };
|
|
533
615
|
},
|
|
534
616
|
show,
|
|
617
|
+
loading,
|
|
618
|
+
promise,
|
|
535
619
|
info: (message = '', options: ToastOptions = {}) =>
|
|
536
620
|
show(message, { ...options, theme: 'info' }),
|
|
537
621
|
success: (message = '', options: ToastOptions = {}) =>
|
package/src/utilities/browser.ts
CHANGED
|
@@ -132,7 +132,7 @@ export function checkModernBrowser(): SupportES2022 {
|
|
|
132
132
|
window.open('https://www.google.cn/chrome/fallback/', '_blank');
|
|
133
133
|
},
|
|
134
134
|
className: {
|
|
135
|
-
confirmBtn: 'j-
|
|
135
|
+
confirmBtn: 'j-btn is-sm is-danger',
|
|
136
136
|
},
|
|
137
137
|
});
|
|
138
138
|
return false;
|