vanilla-jui 1.3.11 → 1.4.0

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 (71) hide show
  1. package/dist/index.d.ts +2927 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.umd.js +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +4 -3
  6. package/src/components/accordion.ts +522 -0
  7. package/src/components/drop.ts +497 -0
  8. package/src/components/flow.ts +1579 -0
  9. package/src/components/form.ts +953 -0
  10. package/src/components/{icons.js → icons.ts} +24 -11
  11. package/src/components/menu.ts +615 -0
  12. package/src/components/{modal.js → modal.ts} +560 -237
  13. package/src/components/offcanvas.ts +496 -0
  14. package/src/components/{pagination.js → pagination.ts} +251 -76
  15. package/src/components/parabola.ts +335 -0
  16. package/src/components/{sticky.js → sticky.ts} +123 -98
  17. package/src/components/{swiper.js → swiper.ts} +510 -232
  18. package/src/components/tabs.ts +984 -0
  19. package/src/components/theme.ts +437 -0
  20. package/src/components/toast.ts +371 -0
  21. package/src/components/toc.ts +385 -0
  22. package/src/components/tooltip.ts +179 -0
  23. package/src/components/validator.ts +596 -0
  24. package/src/core/{Component.js → Component.ts} +134 -27
  25. package/src/css/animation.css +31 -1
  26. package/src/css/card.css +1 -1
  27. package/src/css/color.css +863 -1013
  28. package/src/css/color_p3.css +822 -975
  29. package/src/css/components.css +1 -0
  30. package/src/css/form.css +1 -1
  31. package/src/css/icon.css +17 -1
  32. package/src/css/index.css +2 -2
  33. package/src/css/menu.css +1 -1
  34. package/src/css/tabs.css +1 -0
  35. package/src/css/tokens.css +1 -1
  36. package/src/index.ts +37 -0
  37. package/src/locales/{index.js → index.ts} +1 -2
  38. package/src/types.d.ts +1 -0
  39. package/src/utilities/browser.ts +131 -0
  40. package/src/utilities/cache.ts +57 -0
  41. package/src/utilities/core.ts +614 -0
  42. package/src/utilities/dom.ts +468 -0
  43. package/src/utilities/events.ts +106 -0
  44. package/src/utilities/http.ts +35 -0
  45. package/src/utilities/storage.ts +46 -0
  46. package/src/components/accordion.js +0 -373
  47. package/src/components/drop.js +0 -437
  48. package/src/components/flow.js +0 -1133
  49. package/src/components/form.js +0 -585
  50. package/src/components/menu.js +0 -422
  51. package/src/components/offcanvas.js +0 -332
  52. package/src/components/parabola.js +0 -295
  53. package/src/components/tabs.js +0 -746
  54. package/src/components/theme.js +0 -353
  55. package/src/components/toast.js +0 -329
  56. package/src/components/toc.js +0 -226
  57. package/src/components/tooltip.js +0 -86
  58. package/src/components/validator.js +0 -586
  59. package/src/index.js +0 -45
  60. package/src/locales/index.min.js +0 -1
  61. package/src/utilities/browser.js +0 -111
  62. package/src/utilities/cache.js +0 -159
  63. package/src/utilities/core.js +0 -502
  64. package/src/utilities/debug.js +0 -103
  65. package/src/utilities/dom.js +0 -313
  66. package/src/utilities/events.js +0 -69
  67. package/src/utilities/http.js +0 -30
  68. package/src/utilities/legacy-dom.js +0 -179
  69. package/src/utilities/storage.js +0 -46
  70. /package/src/{components/components.js → css/themes/index.css} +0 -0
  71. /package/src/icons/{index.js → index.ts} +0 -0
@@ -0,0 +1,371 @@
1
+ import { jsx } from 'vanilla-signal';
2
+
3
+ import { randomId, timer, validateParam } from '../utilities/core.ts';
4
+ import { q } from '../utilities/dom.ts';
5
+ import { listen } from '../utilities/events.ts';
6
+ import { icon } from './icons.ts';
7
+
8
+ export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'primary';
9
+
10
+ export interface ToastClassNames {
11
+ container: string;
12
+ toast: string;
13
+ icon: string;
14
+ message: string;
15
+ shown: string;
16
+ hidden: string;
17
+ lite: string;
18
+ action: string;
19
+ actions: string;
20
+ button: string;
21
+ cancelButton: string;
22
+ actionButton: string;
23
+ info: string;
24
+ success: string;
25
+ warning: string;
26
+ error: string;
27
+ primary: string;
28
+ }
29
+
30
+ export type ToastClassNameConfig = Partial<ToastClassNames>;
31
+
32
+ export interface ToastOptions {
33
+ className?: ToastClassNameConfig;
34
+ }
35
+
36
+ export interface ToastActionProps extends ToastOptions {
37
+ text?: {
38
+ cancel?: string;
39
+ action?: string;
40
+ };
41
+ onAction?: () => void | Promise<void>;
42
+ }
43
+
44
+ interface ResolvedToastOptions {
45
+ className: ToastClassNames;
46
+ }
47
+
48
+ const DEFAULT_CLASS_NAMES: ToastClassNames = {
49
+ container: 'j-toast-container',
50
+ toast: 'j-toast',
51
+ icon: 'toast-icon',
52
+ message: 'toast-message',
53
+ shown: 'is-shown',
54
+ hidden: 'is-hidden',
55
+ lite: 'j-toast-lite',
56
+ action: 'is-action',
57
+ actions: 'toast-actions',
58
+ button: 'j-button is-sm',
59
+ cancelButton: 'is-ghost',
60
+ actionButton: 'is-outline',
61
+ info: 'is-info',
62
+ success: 'is-success',
63
+ warning: 'is-warning',
64
+ error: 'is-error',
65
+ primary: 'is-primary',
66
+ };
67
+
68
+ const TOAST_TYPE_RULE = {
69
+ type: 'string',
70
+ enum: ['info', 'success', 'warning', 'error', 'primary'],
71
+ };
72
+
73
+ const TOAST_DURATION_RULE = {
74
+ type: 'number',
75
+ validate: (value: unknown) => typeof value === 'number' && value >= 0,
76
+ message: 'expects a positive number or 0.',
77
+ };
78
+
79
+ const LITE_DURATION_RULE = {
80
+ type: 'number',
81
+ validate: (value: unknown) => typeof value === 'number' && value > 0,
82
+ message: 'expects a number greater than 0.',
83
+ };
84
+
85
+ function mergeClassNames(className?: ToastClassNameConfig): ToastClassNames {
86
+ return { ...DEFAULT_CLASS_NAMES, ...className };
87
+ }
88
+
89
+ function joinClasses(
90
+ ...classes: Array<string | null | undefined | false>
91
+ ): string {
92
+ return classes.filter(Boolean).join(' ');
93
+ }
94
+
95
+ /**
96
+ * Toast 消息提示工具。
97
+ *
98
+ * 以静态方法方式使用,支持多类型堆叠消息和单实例轻提示。
99
+ */
100
+ export class Toast {
101
+ static timers = new Set<string>();
102
+ static disposers = new Map<HTMLElement, () => void>();
103
+ private static classNames = new WeakMap<HTMLElement, ToastClassNames>();
104
+ private static options: ResolvedToastOptions = {
105
+ className: DEFAULT_CLASS_NAMES,
106
+ };
107
+
108
+ static configure(options: ToastOptions = {}): ToastOptions {
109
+ Toast.options = {
110
+ className: mergeClassNames(options.className),
111
+ };
112
+ return Toast.options;
113
+ }
114
+
115
+ private static resolveClassNames(options?: ToastOptions): ToastClassNames {
116
+ return mergeClassNames({
117
+ ...Toast.options.className,
118
+ ...options?.className,
119
+ });
120
+ }
121
+
122
+ static show(
123
+ message = '',
124
+ duration = 3000,
125
+ type: ToastType = 'info',
126
+ options: ToastOptions = {}
127
+ ): HTMLElement {
128
+ validateParam('message', message, 'string', 'Toast.show');
129
+ validateParam('duration', duration, TOAST_DURATION_RULE, 'Toast.show');
130
+ validateParam('type', type, TOAST_TYPE_RULE, 'Toast.show');
131
+
132
+ const className = Toast.resolveClassNames(options);
133
+ const toastContainer = Toast.getOrCreateContainer(className);
134
+ const id = randomId();
135
+ const toast = jsx('div', {
136
+ className: joinClasses(className.toast, className[type]),
137
+ 'data-toast': id,
138
+ children: [
139
+ jsx('span', {
140
+ className: className.icon,
141
+ 'data-toast-icon': id,
142
+ children: icon(type === 'primary' ? 'info' : type),
143
+ }),
144
+ jsx('span', {
145
+ className: className.message,
146
+ 'data-toast-message': id,
147
+ children: message,
148
+ }),
149
+ ],
150
+ }) as HTMLElement;
151
+
152
+ Toast.classNames.set(toast, className);
153
+ toastContainer.appendChild(toast);
154
+
155
+ Toast.setTimer(id, 'show', () => toast.classList.add(className.shown), 10);
156
+
157
+ if (duration > 0) {
158
+ Toast.setTimer(id, 'hide', () => Toast.hide(toast), duration);
159
+ }
160
+
161
+ const disposeClick = listen(toast, 'click', () => Toast.hide(toast));
162
+ Toast.disposers.set(toast, disposeClick);
163
+
164
+ return toast;
165
+ }
166
+
167
+ static success(
168
+ message = '',
169
+ duration = 3000,
170
+ options: ToastOptions = {}
171
+ ): HTMLElement {
172
+ return Toast.show(message, duration, 'success', options);
173
+ }
174
+
175
+ static info(
176
+ message = '',
177
+ duration = 3000,
178
+ options: ToastOptions = {}
179
+ ): HTMLElement {
180
+ return Toast.show(message, duration, 'info', options);
181
+ }
182
+
183
+ static primary(
184
+ message = '',
185
+ duration = 3000,
186
+ options: ToastOptions = {}
187
+ ): HTMLElement {
188
+ return Toast.show(message, duration, 'primary', options);
189
+ }
190
+
191
+ static warning(
192
+ message = '',
193
+ duration = 3000,
194
+ options: ToastOptions = {}
195
+ ): HTMLElement {
196
+ return Toast.show(message, duration, 'warning', options);
197
+ }
198
+
199
+ static error(
200
+ message = '',
201
+ duration = 3000,
202
+ options: ToastOptions = {}
203
+ ): HTMLElement {
204
+ return Toast.show(message, duration, 'error', options);
205
+ }
206
+
207
+ static hide(toast: HTMLElement | null | undefined): void {
208
+ if (!toast) return;
209
+
210
+ const className = Toast.classNames.get(toast) || Toast.options.className;
211
+ Toast.disposers.get(toast)?.();
212
+ Toast.disposers.delete(toast);
213
+
214
+ toast.classList.remove(className.shown);
215
+ toast.classList.add(className.hidden);
216
+
217
+ const id = toast.dataset.toast || randomId();
218
+ Toast.setTimer(
219
+ id,
220
+ 'remove',
221
+ () => {
222
+ toast.remove();
223
+
224
+ const container = q<HTMLElement>('[data-toast-container]');
225
+ if (container && container.children.length === 0) {
226
+ container.remove();
227
+ }
228
+ },
229
+ 300
230
+ );
231
+ }
232
+
233
+ static lite(
234
+ message = '',
235
+ duration = 2000,
236
+ options: ToastOptions = {}
237
+ ): HTMLElement {
238
+ validateParam('message', message, 'string', 'Toast.lite');
239
+ validateParam('duration', duration, LITE_DURATION_RULE, 'Toast.lite');
240
+
241
+ const className = Toast.resolveClassNames(options);
242
+ q<HTMLElement>('[data-toast-lite]')?.remove();
243
+
244
+ const id = randomId();
245
+ const lite = jsx('div', {
246
+ className: className.lite,
247
+ 'data-toast': id,
248
+ 'data-toast-lite': '',
249
+ children: message,
250
+ }) as HTMLElement;
251
+
252
+ Toast.classNames.set(lite, className);
253
+ document.body.appendChild(lite);
254
+
255
+ Toast.setTimer(id, 'show', () => lite.classList.add(className.shown), 10);
256
+ Toast.setTimer(
257
+ id,
258
+ 'hide',
259
+ () => {
260
+ lite.classList.remove(className.shown);
261
+ lite.classList.add(className.hidden);
262
+ Toast.setTimer(id, 'remove', () => lite.remove(), 300);
263
+ },
264
+ duration
265
+ );
266
+
267
+ return lite;
268
+ }
269
+
270
+ static action(message = '', props: ToastActionProps = {}): HTMLElement {
271
+ validateParam('message', message, 'string', 'Toast.action');
272
+
273
+ const className = Toast.resolveClassNames(props);
274
+ const toastContainer = Toast.getOrCreateContainer(className);
275
+ const id = randomId();
276
+ const action = jsx('div', {
277
+ className: joinClasses(className.toast, className.action),
278
+ 'data-toast': id,
279
+ 'data-toast-action': '',
280
+ children: [
281
+ jsx('div', {
282
+ className: className.message,
283
+ 'data-toast-message': id,
284
+ children: message,
285
+ }),
286
+ jsx('div', {
287
+ className: className.actions,
288
+ 'data-toast-actions': id,
289
+ children: [
290
+ jsx('button', {
291
+ className: joinClasses(className.button, className.cancelButton),
292
+ children: props.text?.cancel || 'cancel',
293
+ 'data-action': 'cancel',
294
+ onClick: () => Toast.hide(action),
295
+ }),
296
+ jsx('button', {
297
+ className: joinClasses(className.button, className.actionButton),
298
+ children: props.text?.action || 'action',
299
+ 'data-action': 'toast-action',
300
+ onClick: async () => {
301
+ await props.onAction?.();
302
+ Toast.hide(action);
303
+ },
304
+ }),
305
+ ],
306
+ }),
307
+ ],
308
+ }) as HTMLElement;
309
+
310
+ Toast.classNames.set(action, className);
311
+ toastContainer.appendChild(action);
312
+
313
+ Toast.setTimer(
314
+ id,
315
+ 'show',
316
+ () => {
317
+ action.classList.add(className.shown);
318
+ q<HTMLButtonElement>('[data-action="toast-action"]', action)?.focus();
319
+ },
320
+ 10
321
+ );
322
+
323
+ return action;
324
+ }
325
+
326
+ private static getOrCreateContainer(className: ToastClassNames): HTMLElement {
327
+ let toastContainer = q<HTMLElement>('[data-toast-container]');
328
+ if (!toastContainer) {
329
+ toastContainer = jsx('div', {
330
+ className: className.container,
331
+ 'data-toast-container': '',
332
+ }) as HTMLElement;
333
+ document.body.appendChild(toastContainer);
334
+ } else {
335
+ toastContainer.className = className.container;
336
+ }
337
+ return toastContainer;
338
+ }
339
+
340
+ private static setTimer(
341
+ id: string,
342
+ action: string,
343
+ callback: () => void,
344
+ delay: number
345
+ ): string {
346
+ const key = `${id}-${action}`;
347
+ Toast.timers.add(key);
348
+ timer.start(key, delay, () => {
349
+ Toast.timers.delete(key);
350
+ callback();
351
+ });
352
+ return key;
353
+ }
354
+
355
+ static clearAll(): void {
356
+ for (const key of Toast.timers) {
357
+ timer.cancel(key);
358
+ }
359
+ Toast.timers.clear();
360
+
361
+ for (const dispose of Toast.disposers.values()) dispose();
362
+ Toast.disposers.clear();
363
+
364
+ q<HTMLElement>('[data-toast-container]')?.remove();
365
+ q<HTMLElement>('[data-toast-lite]')?.remove();
366
+ }
367
+
368
+ static destroyAll(): void {
369
+ Toast.clearAll();
370
+ }
371
+ }