vanilla-jui 1.5.8 → 1.5.10
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 +1 -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 +174 -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,88 @@ 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
|
+
const shouldCancel = isToastLoading(options);
|
|
160
|
+
hide(toast);
|
|
161
|
+
if (shouldCancel) await options.onCancel?.();
|
|
162
|
+
await options.onClose?.();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function bindToastStatus(
|
|
166
|
+
element: HTMLElement,
|
|
167
|
+
iconElement: HTMLElement,
|
|
168
|
+
messageElement: HTMLElement,
|
|
169
|
+
message: string,
|
|
170
|
+
theme: ToastTheme,
|
|
171
|
+
options: ToastOptions,
|
|
172
|
+
duration = 0
|
|
173
|
+
): void {
|
|
174
|
+
const id = element.dataset.toast || randomId();
|
|
175
|
+
const dispose = createRoot((dispose) => {
|
|
176
|
+
insert(iconElement, () => toastIconContent(theme, options));
|
|
177
|
+
insert(messageElement, () => toastMessage(message, options));
|
|
178
|
+
createEffect(() => {
|
|
179
|
+
cancelToastTimers(id);
|
|
180
|
+
if (duration > 0 && !isToastLoading(options)) {
|
|
181
|
+
setToastTimer(id, 'hide', () => hide(element), duration);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return dispose;
|
|
185
|
+
});
|
|
186
|
+
addToastDisposer(element, dispose);
|
|
187
|
+
}
|
|
188
|
+
|
|
88
189
|
function getOrCreateContainer(names: ToastClassNames): HTMLElement {
|
|
89
190
|
let container = q<HTMLElement>('[data-toast-container]');
|
|
90
191
|
if (!container) {
|
|
@@ -241,45 +342,48 @@ function hide(toast: HTMLElement | null | undefined): void {
|
|
|
241
342
|
});
|
|
242
343
|
}
|
|
243
344
|
|
|
244
|
-
function show(
|
|
245
|
-
message = '',
|
|
246
|
-
duration = 3000,
|
|
247
|
-
type: ToastType = 'info',
|
|
248
|
-
options: ToastOptions = {}
|
|
249
|
-
): HTMLElement {
|
|
345
|
+
function show(message = '', options: ToastOptions = {}): HTMLElement {
|
|
250
346
|
validateParam('message', message, 'string', 'Toast.show');
|
|
347
|
+
const duration = options.duration ?? 3000;
|
|
348
|
+
const theme = options.theme || 'info';
|
|
251
349
|
validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.show');
|
|
252
|
-
validateParam('
|
|
350
|
+
validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.show');
|
|
253
351
|
|
|
254
352
|
const names = resolveClassNames(options);
|
|
255
353
|
const id = randomId();
|
|
354
|
+
const iconElement = jsx('span', {
|
|
355
|
+
className: names.icon,
|
|
356
|
+
'data-toast-icon': id,
|
|
357
|
+
style: () => toastIconStyle(options),
|
|
358
|
+
}) as HTMLElement;
|
|
359
|
+
const messageElement = jsx('span', {
|
|
360
|
+
className: names.message,
|
|
361
|
+
'data-toast-message': id,
|
|
362
|
+
}) as HTMLElement;
|
|
256
363
|
const toast = jsx('div', {
|
|
257
|
-
className: joinClasses(names.toast, names[
|
|
364
|
+
className: joinClasses(names.toast, names[theme]),
|
|
258
365
|
'data-toast': id,
|
|
259
366
|
role: 'alert',
|
|
260
367
|
'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
|
-
],
|
|
368
|
+
children: [iconElement, messageElement],
|
|
273
369
|
}) as HTMLElement;
|
|
370
|
+
bindToastStatus(
|
|
371
|
+
toast,
|
|
372
|
+
iconElement,
|
|
373
|
+
messageElement,
|
|
374
|
+
message,
|
|
375
|
+
theme,
|
|
376
|
+
options,
|
|
377
|
+
duration
|
|
378
|
+
);
|
|
274
379
|
mountToast(
|
|
275
380
|
toast,
|
|
276
381
|
() => insert(getOrCreateContainer(names), toast),
|
|
277
|
-
|
|
382
|
+
theme === 'error' ? 'assertive' : 'polite'
|
|
278
383
|
);
|
|
279
|
-
|
|
280
|
-
disposers.set(
|
|
384
|
+
addToastDisposer(
|
|
281
385
|
toast,
|
|
282
|
-
listen(toast, 'click', () =>
|
|
386
|
+
listen(toast, 'click', () => void closeByUser(toast, options))
|
|
283
387
|
);
|
|
284
388
|
return toast;
|
|
285
389
|
}
|
|
@@ -287,12 +391,12 @@ function show(
|
|
|
287
391
|
function lite(
|
|
288
392
|
message = '',
|
|
289
393
|
duration = 2000,
|
|
290
|
-
|
|
394
|
+
className?: ToastClassNameConfig
|
|
291
395
|
): HTMLElement {
|
|
292
396
|
validateParam('message', message, 'string', 'Toast.lite');
|
|
293
397
|
validateParam('duration', duration, LITE_DURATION_RULE, 'Toast.lite');
|
|
294
398
|
|
|
295
|
-
const names =
|
|
399
|
+
const names = mergeClassNames({ ...classNames, ...className });
|
|
296
400
|
const previous = q<HTMLElement>('[data-toast-lite]');
|
|
297
401
|
if (previous) {
|
|
298
402
|
cancelToastTimers(previous.dataset.toast || '');
|
|
@@ -311,16 +415,18 @@ function lite(
|
|
|
311
415
|
return element;
|
|
312
416
|
}
|
|
313
417
|
|
|
314
|
-
function
|
|
315
|
-
validateParam('message', message, 'string', 'Toast.
|
|
316
|
-
const closeText = props.text?.close ||
|
|
317
|
-
const
|
|
418
|
+
function confirm(message = '', props: ToastConfirmProps = {}): HTMLElement {
|
|
419
|
+
validateParam('message', message, 'string', 'Toast.confirm');
|
|
420
|
+
const closeText = props.text?.close || s('Close');
|
|
421
|
+
const confirmText = props.text?.confirm || s('Confirm');
|
|
422
|
+
const theme = props.theme || 'info';
|
|
423
|
+
validateParam('theme', theme, TOAST_THEME_RULE, 'Toast.confirm');
|
|
318
424
|
const names = resolveClassNames(props);
|
|
319
425
|
const id = randomId();
|
|
320
426
|
const element = jsx('div', {
|
|
321
|
-
className: joinClasses(names.
|
|
427
|
+
className: joinClasses(names.confirm, names[theme]),
|
|
322
428
|
'data-toast': id,
|
|
323
|
-
'data-toast-
|
|
429
|
+
'data-toast-confirm': '',
|
|
324
430
|
children: [
|
|
325
431
|
jsx('div', {
|
|
326
432
|
className: names.message,
|
|
@@ -328,13 +434,13 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
328
434
|
children: message,
|
|
329
435
|
}),
|
|
330
436
|
jsx('div', {
|
|
331
|
-
className: names.
|
|
332
|
-
'data-toast-
|
|
437
|
+
className: names.buttons,
|
|
438
|
+
'data-toast-buttons': id,
|
|
333
439
|
children: [
|
|
334
440
|
jsx('button', {
|
|
335
441
|
className: joinClasses(names.button, names.closeBtn),
|
|
336
442
|
children: closeText,
|
|
337
|
-
'data-
|
|
443
|
+
'data-toast-button': 'close',
|
|
338
444
|
'aria-label': closeText,
|
|
339
445
|
onClick: async () => {
|
|
340
446
|
await props.onClose?.();
|
|
@@ -342,12 +448,12 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
342
448
|
},
|
|
343
449
|
}),
|
|
344
450
|
jsx('button', {
|
|
345
|
-
className: joinClasses(names.button, names.
|
|
346
|
-
children:
|
|
347
|
-
'data-
|
|
348
|
-
'aria-label':
|
|
451
|
+
className: joinClasses(names.button, names.confirmBtn),
|
|
452
|
+
children: confirmText,
|
|
453
|
+
'data-toast-button': 'confirm',
|
|
454
|
+
'aria-label': confirmText,
|
|
349
455
|
onClick: async () => {
|
|
350
|
-
await props.
|
|
456
|
+
await props.onConfirm?.();
|
|
351
457
|
hide(element);
|
|
352
458
|
},
|
|
353
459
|
}),
|
|
@@ -360,7 +466,7 @@ function action(message = '', props: ToastActionProps = {}): HTMLElement {
|
|
|
360
466
|
() => insert(getOrCreateContainer(names), element),
|
|
361
467
|
'polite',
|
|
362
468
|
() => {
|
|
363
|
-
q<HTMLButtonElement>('[data-
|
|
469
|
+
q<HTMLButtonElement>('[data-toast-button="confirm"]', element)?.focus();
|
|
364
470
|
}
|
|
365
471
|
);
|
|
366
472
|
return element;
|
|
@@ -381,24 +487,24 @@ function clearAll(): void {
|
|
|
381
487
|
export const Toast = {
|
|
382
488
|
timers,
|
|
383
489
|
disposers,
|
|
384
|
-
configure(options:
|
|
490
|
+
configure(options: ToastClassNameOptions = {}): ToastClassNameOptions {
|
|
385
491
|
classNames = mergeClassNames(options.className);
|
|
386
492
|
return { className: classNames };
|
|
387
493
|
},
|
|
388
494
|
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,
|
|
495
|
+
success: (message = '', options: ToastOptions = {}) =>
|
|
496
|
+
show(message, { ...options, theme: 'success' }),
|
|
497
|
+
info: (message = '', options: ToastOptions = {}) =>
|
|
498
|
+
show(message, { ...options, theme: 'info' }),
|
|
499
|
+
primary: (message = '', options: ToastOptions = {}) =>
|
|
500
|
+
show(message, { ...options, theme: 'primary' }),
|
|
501
|
+
warning: (message = '', options: ToastOptions = {}) =>
|
|
502
|
+
show(message, { ...options, theme: 'warning' }),
|
|
503
|
+
error: (message = '', options: ToastOptions = {}) =>
|
|
504
|
+
show(message, { ...options, theme: 'error' }),
|
|
399
505
|
hide,
|
|
400
506
|
lite,
|
|
401
|
-
|
|
507
|
+
confirm,
|
|
402
508
|
clearAll,
|
|
403
509
|
destroyAll: clearAll,
|
|
404
510
|
};
|
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;
|