vanilla-jui 1.5.7 → 1.5.9
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 +39 -23
- 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/menu.ts +78 -25
- package/src/css/content.css +2 -1
- package/src/css/themes/default/toast.css +1 -8
- package/src/css/toast.css +10 -1
- package/src/locales/index.ts +1 -0
- package/src/primitives/theme.ts +1 -2
- package/src/primitives/toast.ts +173 -68
- package/src/utilities/browser.ts +4 -4
package/src/primitives/toast.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type MaybeAccessor,
|
|
3
|
+
access,
|
|
4
|
+
createEffect,
|
|
5
|
+
createRoot,
|
|
6
|
+
insert,
|
|
7
|
+
jsx,
|
|
8
|
+
} from 'vanilla-signal';
|
|
2
9
|
import { t } from 'vanilla-signal-i18n';
|
|
3
10
|
|
|
4
11
|
import locales from '../locales/index.ts';
|
|
5
12
|
import { icon } from '../primitives/icons.ts';
|
|
13
|
+
import { createLoading } from '../primitives/loading.ts';
|
|
6
14
|
import { joinClasses } from '../utilities/class-name.ts';
|
|
7
15
|
import { q } from '../utilities/dom.ts';
|
|
8
16
|
import { listen } from '../utilities/events.ts';
|
|
@@ -10,7 +18,9 @@ import { randomId } from '../utilities/id.ts';
|
|
|
10
18
|
import { timer } from '../utilities/timer.ts';
|
|
11
19
|
import { validateParam } from '../utilities/types.ts';
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
const s = (k: string) => t(k, locales);
|
|
22
|
+
|
|
23
|
+
export type ToastTheme = 'info' | 'success' | 'warning' | 'error' | 'primary';
|
|
14
24
|
|
|
15
25
|
export interface ToastClassNames {
|
|
16
26
|
container: string;
|
|
@@ -18,11 +28,11 @@ export interface ToastClassNames {
|
|
|
18
28
|
icon: string;
|
|
19
29
|
message: string;
|
|
20
30
|
lite: string;
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
confirm: string;
|
|
32
|
+
buttons: string;
|
|
23
33
|
button: string;
|
|
24
34
|
closeBtn: string;
|
|
25
|
-
|
|
35
|
+
confirmBtn: string;
|
|
26
36
|
info: string;
|
|
27
37
|
success: string;
|
|
28
38
|
warning: string;
|
|
@@ -32,17 +42,30 @@ export interface ToastClassNames {
|
|
|
32
42
|
|
|
33
43
|
export type ToastClassNameConfig = Partial<ToastClassNames>;
|
|
34
44
|
|
|
35
|
-
export interface
|
|
45
|
+
export interface ToastClassNameOptions {
|
|
36
46
|
className?: ToastClassNameConfig;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
export interface
|
|
40
|
-
|
|
49
|
+
export interface ToastThemeOptions extends ToastClassNameOptions {
|
|
50
|
+
theme?: ToastTheme;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ToastOptions extends ToastThemeOptions {
|
|
54
|
+
duration?: number;
|
|
55
|
+
loading?: MaybeAccessor<boolean>;
|
|
56
|
+
text?: {
|
|
57
|
+
loading?: string;
|
|
58
|
+
};
|
|
59
|
+
onClose?: () => void | Promise<void>;
|
|
60
|
+
onCancel?: () => void | Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ToastConfirmProps extends ToastThemeOptions {
|
|
41
64
|
text?: {
|
|
42
65
|
close?: string;
|
|
43
|
-
|
|
66
|
+
confirm?: string;
|
|
44
67
|
};
|
|
45
|
-
|
|
68
|
+
onConfirm?: () => void | Promise<void>;
|
|
46
69
|
onClose?: () => void | Promise<void>;
|
|
47
70
|
}
|
|
48
71
|
|
|
@@ -52,11 +75,11 @@ const DEFAULT_CLASS_NAMES: ToastClassNames = {
|
|
|
52
75
|
icon: 'el-icon',
|
|
53
76
|
message: 'el-text',
|
|
54
77
|
lite: 'j-toast-lite',
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
confirm: 'j-toast is-confirm',
|
|
79
|
+
buttons: 'toast-buttons',
|
|
57
80
|
button: 'j-button is-sm',
|
|
58
81
|
closeBtn: 'is-ghost',
|
|
59
|
-
|
|
82
|
+
confirmBtn: 'is-outline',
|
|
60
83
|
info: 'is-info',
|
|
61
84
|
primary: 'is-primary',
|
|
62
85
|
success: 'is-success',
|
|
@@ -64,7 +87,7 @@ const DEFAULT_CLASS_NAMES: ToastClassNames = {
|
|
|
64
87
|
error: 'is-error',
|
|
65
88
|
};
|
|
66
89
|
|
|
67
|
-
const
|
|
90
|
+
const TOAST_THEME_RULE = {
|
|
68
91
|
type: 'string',
|
|
69
92
|
enum: ['info', 'success', 'warning', 'error', 'primary'],
|
|
70
93
|
};
|
|
@@ -81,10 +104,87 @@ function mergeClassNames(value?: ToastClassNameConfig): ToastClassNames {
|
|
|
81
104
|
return { ...DEFAULT_CLASS_NAMES, ...value };
|
|
82
105
|
}
|
|
83
106
|
|
|
84
|
-
function resolveClassNames(options?:
|
|
107
|
+
function resolveClassNames(options?: ToastClassNameOptions): ToastClassNames {
|
|
85
108
|
return mergeClassNames({ ...classNames, ...options?.className });
|
|
86
109
|
}
|
|
87
110
|
|
|
111
|
+
function addToastDisposer(element: HTMLElement, dispose: () => void): void {
|
|
112
|
+
const current = disposers.get(element);
|
|
113
|
+
disposers.set(element, () => {
|
|
114
|
+
current?.();
|
|
115
|
+
dispose();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isToastLoading(options: ToastOptions): boolean {
|
|
120
|
+
return !!access(options.loading ?? false);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function toastIconName(theme: ToastTheme): ToastTheme {
|
|
124
|
+
return theme === 'primary' ? 'info' : theme;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function toastIconStyle(options: ToastOptions): Record<string, string> {
|
|
128
|
+
return isToastLoading(options)
|
|
129
|
+
? {
|
|
130
|
+
position: 'relative',
|
|
131
|
+
display: 'inline-flex',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
width: 'var(--toast-icon-size, 1em)',
|
|
135
|
+
height: 'var(--toast-icon-size, 1em)',
|
|
136
|
+
flex: '0 0 auto',
|
|
137
|
+
}
|
|
138
|
+
: {};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toastIconContent(
|
|
142
|
+
theme: ToastTheme,
|
|
143
|
+
options: ToastOptions
|
|
144
|
+
): HTMLElement | SVGElement {
|
|
145
|
+
if (isToastLoading(options)) return createLoading();
|
|
146
|
+
return icon(toastIconName(theme));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toastMessage(message: string, options: ToastOptions): string {
|
|
150
|
+
return isToastLoading(options)
|
|
151
|
+
? options.text?.loading || s('Loading...')
|
|
152
|
+
: message;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function closeByUser(
|
|
156
|
+
toast: HTMLElement,
|
|
157
|
+
options: ToastOptions
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
if (isToastLoading(options)) await options.onCancel?.();
|
|
160
|
+
await options.onClose?.();
|
|
161
|
+
hide(toast);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function bindToastStatus(
|
|
165
|
+
element: HTMLElement,
|
|
166
|
+
iconElement: HTMLElement,
|
|
167
|
+
messageElement: HTMLElement,
|
|
168
|
+
message: string,
|
|
169
|
+
theme: ToastTheme,
|
|
170
|
+
options: ToastOptions,
|
|
171
|
+
duration = 0
|
|
172
|
+
): void {
|
|
173
|
+
const id = element.dataset.toast || randomId();
|
|
174
|
+
const dispose = createRoot((dispose) => {
|
|
175
|
+
insert(iconElement, () => toastIconContent(theme, options));
|
|
176
|
+
insert(messageElement, () => toastMessage(message, options));
|
|
177
|
+
createEffect(() => {
|
|
178
|
+
cancelToastTimers(id);
|
|
179
|
+
if (duration > 0 && !isToastLoading(options)) {
|
|
180
|
+
setToastTimer(id, 'hide', () => hide(element), duration);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return dispose;
|
|
184
|
+
});
|
|
185
|
+
addToastDisposer(element, dispose);
|
|
186
|
+
}
|
|
187
|
+
|
|
88
188
|
function getOrCreateContainer(names: ToastClassNames): HTMLElement {
|
|
89
189
|
let container = q<HTMLElement>('[data-toast-container]');
|
|
90
190
|
if (!container) {
|
|
@@ -241,45 +341,48 @@ function hide(toast: HTMLElement | null | undefined): void {
|
|
|
241
341
|
});
|
|
242
342
|
}
|
|
243
343
|
|
|
244
|
-
function show(
|
|
245
|
-
message = '',
|
|
246
|
-
duration = 3000,
|
|
247
|
-
type: ToastType = 'info',
|
|
248
|
-
options: ToastOptions = {}
|
|
249
|
-
): HTMLElement {
|
|
344
|
+
function show(message = '', options: ToastOptions = {}): HTMLElement {
|
|
250
345
|
validateParam('message', message, 'string', 'Toast.show');
|
|
346
|
+
const duration = options.duration ?? 3000;
|
|
347
|
+
const theme = options.theme || 'info';
|
|
251
348
|
validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.show');
|
|
252
|
-
validateParam('
|
|
349
|
+
validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.show');
|
|
253
350
|
|
|
254
351
|
const names = resolveClassNames(options);
|
|
255
352
|
const id = randomId();
|
|
353
|
+
const iconElement = jsx('span', {
|
|
354
|
+
className: names.icon,
|
|
355
|
+
'data-toast-icon': id,
|
|
356
|
+
style: () => toastIconStyle(options),
|
|
357
|
+
}) as HTMLElement;
|
|
358
|
+
const messageElement = jsx('span', {
|
|
359
|
+
className: names.message,
|
|
360
|
+
'data-toast-message': id,
|
|
361
|
+
}) as HTMLElement;
|
|
256
362
|
const toast = jsx('div', {
|
|
257
|
-
className: joinClasses(names.toast, names[
|
|
363
|
+
className: joinClasses(names.toast, names[theme]),
|
|
258
364
|
'data-toast': id,
|
|
259
365
|
role: 'alert',
|
|
260
366
|
'aria-atomic': 'true',
|
|
261
|
-
children: [
|
|
262
|
-
jsx('span', {
|
|
263
|
-
className: names.icon,
|
|
264
|
-
'data-toast-icon': id,
|
|
265
|
-
children: icon(type === 'primary' ? 'info' : type),
|
|
266
|
-
}),
|
|
267
|
-
jsx('span', {
|
|
268
|
-
className: names.message,
|
|
269
|
-
'data-toast-message': id,
|
|
270
|
-
children: message,
|
|
271
|
-
}),
|
|
272
|
-
],
|
|
367
|
+
children: [iconElement, messageElement],
|
|
273
368
|
}) as HTMLElement;
|
|
369
|
+
bindToastStatus(
|
|
370
|
+
toast,
|
|
371
|
+
iconElement,
|
|
372
|
+
messageElement,
|
|
373
|
+
message,
|
|
374
|
+
theme,
|
|
375
|
+
options,
|
|
376
|
+
duration
|
|
377
|
+
);
|
|
274
378
|
mountToast(
|
|
275
379
|
toast,
|
|
276
380
|
() => insert(getOrCreateContainer(names), toast),
|
|
277
|
-
|
|
381
|
+
theme === 'error' ? 'assertive' : 'polite'
|
|
278
382
|
);
|
|
279
|
-
|
|
280
|
-
disposers.set(
|
|
383
|
+
addToastDisposer(
|
|
281
384
|
toast,
|
|
282
|
-
listen(toast, 'click', () =>
|
|
385
|
+
listen(toast, 'click', () => void closeByUser(toast, options))
|
|
283
386
|
);
|
|
284
387
|
return toast;
|
|
285
388
|
}
|
|
@@ -287,12 +390,12 @@ function show(
|
|
|
287
390
|
function lite(
|
|
288
391
|
message = '',
|
|
289
392
|
duration = 2000,
|
|
290
|
-
|
|
393
|
+
className?: ToastClassNameConfig
|
|
291
394
|
): HTMLElement {
|
|
292
395
|
validateParam('message', message, 'string', 'Toast.lite');
|
|
293
396
|
validateParam('duration', duration, LITE_DURATION_RULE, 'Toast.lite');
|
|
294
397
|
|
|
295
|
-
const names =
|
|
398
|
+
const names = mergeClassNames({ ...classNames, ...className });
|
|
296
399
|
const previous = q<HTMLElement>('[data-toast-lite]');
|
|
297
400
|
if (previous) {
|
|
298
401
|
cancelToastTimers(previous.dataset.toast || '');
|
|
@@ -311,16 +414,18 @@ function lite(
|
|
|
311
414
|
return element;
|
|
312
415
|
}
|
|
313
416
|
|
|
314
|
-
function
|
|
315
|
-
validateParam('message', message, 'string', 'Toast.
|
|
316
|
-
const closeText = props.text?.close ||
|
|
317
|
-
const
|
|
417
|
+
function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
|
|
418
|
+
validateParam('message', message, 'string', 'Toast.confirm');
|
|
419
|
+
const closeText = props.text?.close || s('Close');
|
|
420
|
+
const confirmText = props.text?.confirm || s('Confirm');
|
|
421
|
+
const theme = props.theme || 'info';
|
|
422
|
+
validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.confirm');
|
|
318
423
|
const names = resolveClassNames(props);
|
|
319
424
|
const id = randomId();
|
|
320
425
|
const element = jsx('div', {
|
|
321
|
-
className: joinClasses(names.
|
|
426
|
+
className: joinClasses(names.confirm, names[theme]),
|
|
322
427
|
'data-toast': id,
|
|
323
|
-
'data-toast-
|
|
428
|
+
'data-toast-confirm': '',
|
|
324
429
|
children: [
|
|
325
430
|
jsx('div', {
|
|
326
431
|
className: names.message,
|
|
@@ -328,13 +433,13 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
328
433
|
children: message,
|
|
329
434
|
}),
|
|
330
435
|
jsx('div', {
|
|
331
|
-
className: names.
|
|
332
|
-
'data-toast-
|
|
436
|
+
className: names.buttons,
|
|
437
|
+
'data-toast-buttons': id,
|
|
333
438
|
children: [
|
|
334
439
|
jsx('button', {
|
|
335
440
|
className: joinClasses(names.button, names.closeBtn),
|
|
336
441
|
children: closeText,
|
|
337
|
-
'data-
|
|
442
|
+
'data-toast-button': 'close',
|
|
338
443
|
'aria-label': closeText,
|
|
339
444
|
onClick: async () => {
|
|
340
445
|
await props.onClose?.();
|
|
@@ -342,12 +447,12 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
342
447
|
},
|
|
343
448
|
}),
|
|
344
449
|
jsx('button', {
|
|
345
|
-
className: joinClasses(names.button, names.
|
|
346
|
-
children:
|
|
347
|
-
'data-
|
|
348
|
-
'aria-label':
|
|
450
|
+
className: joinClasses(names.button, names.confirmBtn),
|
|
451
|
+
children: confirmText,
|
|
452
|
+
'data-toast-button': 'confirm',
|
|
453
|
+
'aria-label': confirmText,
|
|
349
454
|
onClick: async () => {
|
|
350
|
-
await props.
|
|
455
|
+
await props.onConfirm?.();
|
|
351
456
|
hide(element);
|
|
352
457
|
},
|
|
353
458
|
}),
|
|
@@ -360,7 +465,7 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
360
465
|
() => insert(getOrCreateContainer(names), element),
|
|
361
466
|
'polite',
|
|
362
467
|
() => {
|
|
363
|
-
q<HTMLButtonElement>('[data-
|
|
468
|
+
q<HTMLButtonElement>('[data-toast-button="confirm"]', element)?.focus();
|
|
364
469
|
}
|
|
365
470
|
);
|
|
366
471
|
return element;
|
|
@@ -381,24 +486,24 @@ function clearAll(): void {
|
|
|
381
486
|
export const Toast = {
|
|
382
487
|
timers,
|
|
383
488
|
disposers,
|
|
384
|
-
configure(options:
|
|
489
|
+
configure(options: ToastClassNameOptions = {}): ToastClassNameOptions {
|
|
385
490
|
classNames = mergeClassNames(options.className);
|
|
386
491
|
return { className: classNames };
|
|
387
492
|
},
|
|
388
493
|
show,
|
|
389
|
-
success: (message = '',
|
|
390
|
-
show(message,
|
|
391
|
-
info: (message = '',
|
|
392
|
-
show(message,
|
|
393
|
-
primary: (message = '',
|
|
394
|
-
show(message,
|
|
395
|
-
warning: (message = '',
|
|
396
|
-
show(message,
|
|
397
|
-
error: (message = '',
|
|
398
|
-
show(message,
|
|
494
|
+
success: (message = '', options: ToastOptions = {}) =>
|
|
495
|
+
show(message, { ...options, theme: 'success' }),
|
|
496
|
+
info: (message = '', options: ToastOptions = {}) =>
|
|
497
|
+
show(message, { ...options, theme: 'info' }),
|
|
498
|
+
primary: (message = '', options: ToastOptions = {}) =>
|
|
499
|
+
show(message, { ...options, theme: 'primary' }),
|
|
500
|
+
warning: (message = '', options: ToastOptions = {}) =>
|
|
501
|
+
show(message, { ...options, theme: 'warning' }),
|
|
502
|
+
error: (message = '', options: ToastOptions = {}) =>
|
|
503
|
+
show(message, { ...options, theme: 'error' }),
|
|
399
504
|
hide,
|
|
400
505
|
lite,
|
|
401
|
-
|
|
506
|
+
confirm,
|
|
402
507
|
clearAll,
|
|
403
508
|
destroyAll: clearAll,
|
|
404
509
|
};
|
package/src/utilities/browser.ts
CHANGED
|
@@ -128,13 +128,13 @@ export function isModernBrowser(): SupportES2022 {
|
|
|
128
128
|
}
|
|
129
129
|
export function checkModernBrowser(): SupportES2022 {
|
|
130
130
|
if (!isModernBrowser()) {
|
|
131
|
-
Toast.
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
Toast.confirm(t('wn', locales), {
|
|
132
|
+
theme: 'error',
|
|
133
|
+
onConfirm: () => {
|
|
134
134
|
window.open('https://www.google.cn/chrome/fallback/', '_blank');
|
|
135
135
|
},
|
|
136
136
|
className: {
|
|
137
|
-
|
|
137
|
+
confirmBtn: 'j-button is-sm is-danger',
|
|
138
138
|
},
|
|
139
139
|
});
|
|
140
140
|
return false;
|