vanilla-jui 1.4.5 → 1.4.6
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/README.md +6 -0
- package/dist/index.d.ts +1273 -1896
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +21 -2
- package/src/components/accordion.ts +297 -216
- package/src/components/flow.ts +222 -258
- package/src/components/form.ts +44 -102
- package/src/components/menu.ts +268 -356
- package/src/components/modal.ts +181 -641
- package/src/components/offcanvas.ts +225 -106
- package/src/components/pagination.ts +296 -326
- package/src/components/sticky.ts +55 -54
- package/src/components/swiper.ts +237 -247
- package/src/components/tabs.ts +278 -271
- package/src/components/toc.ts +42 -46
- package/src/core/Component.ts +69 -47
- package/src/css/accordion.css +2 -2
- package/src/css/animation.css +1 -5
- package/src/css/avatar.css +1 -1
- package/src/css/badge.css +1 -1
- package/src/css/breadcrumb.css +9 -10
- package/src/css/button.css +1 -1
- package/src/css/card.css +2 -2
- package/src/css/color.css +974 -1002
- package/src/css/color_p3.css +974 -1004
- package/src/css/components.css +1 -1
- package/src/css/content.css +72 -63
- package/src/css/drop.css +1 -1
- package/src/css/editor.css +14 -14
- package/src/css/flow.css +4 -14
- package/src/css/font.css +5 -4
- package/src/css/form.css +76 -52
- package/src/css/gradient.css +299 -222
- package/src/css/icon.css +57 -57
- package/src/css/index.css +1 -1
- package/src/css/layout.css +13 -10
- package/src/css/loading.css +60 -12
- package/src/css/menu.css +262 -188
- package/src/css/modal.css +1 -1
- package/src/css/offcanvas.css +9 -13
- package/src/css/pagination.css +5 -1
- package/src/css/popup.css +1 -1
- package/src/css/skeleton.css +3 -2
- package/src/css/status.css +2 -3
- package/src/css/swiper.css +1 -1
- package/src/css/table.css +1 -1
- package/src/css/tabs.css +12 -11
- package/src/css/tag.css +1 -1
- package/src/css/theme-palette.css +19 -5
- package/src/css/themes/default/accordion.css +61 -61
- package/src/css/themes/default/avatar.css +54 -54
- package/src/css/themes/default/badge.css +62 -62
- package/src/css/themes/default/breadcrumb.css +23 -23
- package/src/css/themes/default/button.css +203 -203
- package/src/css/themes/default/content.css +53 -53
- package/src/css/themes/default/drop.css +10 -10
- package/src/css/themes/default/editor.css +9 -9
- package/src/css/themes/default/index.css +1 -1
- package/src/css/themes/default/loading.css +26 -20
- package/src/css/themes/default/menu.css +75 -11
- package/src/css/themes/default/modal.css +32 -36
- package/src/css/themes/default/pagination.css +7 -7
- package/src/css/themes/default/popup.css +6 -6
- package/src/css/themes/default/swiper.css +32 -32
- package/src/css/themes/default/table.css +16 -16
- package/src/css/themes/default/tabs.css +30 -30
- package/src/css/themes/default/tag.css +71 -71
- package/src/css/themes/default/theme-palette.css +11 -11
- package/src/css/themes/default/tip.css +42 -42
- package/src/css/themes/default/toast.css +60 -60
- package/src/css/themes/default/toolbar.css +23 -29
- package/src/css/themes/default/tooltip.css +43 -13
- package/src/css/themes/index.css +1 -1
- package/src/css/tip.css +2 -2
- package/src/css/toast.css +3 -3
- package/src/css/tokens.css +12 -17
- package/src/css/toolbar.css +9 -10
- package/src/css/tooltip.css +3 -2
- package/src/index.ts +22 -19
- package/src/{components → primitives}/drop.ts +58 -49
- package/src/{components → primitives}/icons.ts +1 -1
- package/src/primitives/loading.ts +23 -0
- package/src/primitives/parabola.ts +388 -0
- package/src/primitives/popup.ts +61 -0
- package/src/{components → primitives}/theme.ts +88 -57
- package/src/{components → primitives}/toast.ts +18 -25
- package/src/{components → primitives}/tooltip.ts +100 -25
- package/src/utilities/browser.ts +23 -49
- package/src/utilities/class-name.ts +5 -0
- package/src/utilities/dom.ts +14 -32
- package/src/utilities/function.ts +4 -0
- package/src/utilities/http.ts +7 -7
- package/src/utilities/id.ts +57 -0
- package/src/utilities/object.ts +5 -0
- package/src/utilities/timer.ts +38 -0
- package/src/utilities/types.ts +420 -0
- package/src/{components → validation}/validator.ts +64 -53
- package/src/components/parabola.ts +0 -335
- package/src/utilities/cache.ts +0 -57
- package/src/utilities/core.ts +0 -614
- package/src/utilities/storage.ts +0 -46
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx } from 'vanilla-signal';
|
|
2
|
+
|
|
3
|
+
import { type RenderableContent } from '../utilities/dom.ts';
|
|
4
|
+
import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
|
|
5
|
+
|
|
6
|
+
export interface PopupProps extends Record<string, unknown> {
|
|
7
|
+
className?: string;
|
|
8
|
+
position?: string;
|
|
9
|
+
component?: string;
|
|
10
|
+
labelledby?: string;
|
|
11
|
+
content?: RenderableContent;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ResolvedPopupProps extends Record<string, unknown> {
|
|
15
|
+
className?: string;
|
|
16
|
+
position: string;
|
|
17
|
+
component: string;
|
|
18
|
+
labelledby: string;
|
|
19
|
+
content: RenderableContent;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const POPUP_PROPS_SCHEMA = {
|
|
23
|
+
className: { default: 'j-popup-layout', type: 'string' },
|
|
24
|
+
position: { default: 'center', type: 'string' },
|
|
25
|
+
component: { default: '', type: 'string' },
|
|
26
|
+
labelledby: { default: '', type: 'string' },
|
|
27
|
+
content: {
|
|
28
|
+
default: '',
|
|
29
|
+
type: 'renderable',
|
|
30
|
+
},
|
|
31
|
+
} satisfies ResolveSchema<PopupProps>;
|
|
32
|
+
|
|
33
|
+
function normalizeProps(input: PopupProps): ResolvedPopupProps {
|
|
34
|
+
const props = resolveProps(input, POPUP_PROPS_SCHEMA, 'Popup');
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
className: props.className as string,
|
|
38
|
+
position: props.position as string,
|
|
39
|
+
component: props.component as string,
|
|
40
|
+
labelledby: props.labelledby as string,
|
|
41
|
+
content: props.content as RenderableContent,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createPopup(props: PopupProps = {}): HTMLElement {
|
|
46
|
+
const { className, position, component, labelledby, content } =
|
|
47
|
+
normalizeProps(props);
|
|
48
|
+
const attrs: Record<string, unknown> = {
|
|
49
|
+
className: `${className} is-${position}`,
|
|
50
|
+
role: 'dialog',
|
|
51
|
+
children: content,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (component) {
|
|
55
|
+
attrs[`data-${component}`] = 'root';
|
|
56
|
+
attrs[`aria-${component}`] = 'true';
|
|
57
|
+
}
|
|
58
|
+
if (labelledby) attrs['aria-labelledby'] = labelledby;
|
|
59
|
+
|
|
60
|
+
return jsx('div', attrs) as HTMLElement;
|
|
61
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createStorage, type Storage } from 'vanilla-create-storage';
|
|
1
2
|
import {
|
|
2
3
|
bindAttr,
|
|
3
4
|
bindClass,
|
|
@@ -9,18 +10,16 @@ import {
|
|
|
9
10
|
import { t } from 'vanilla-signal-i18n';
|
|
10
11
|
|
|
11
12
|
import locales from '../locales/index.ts';
|
|
13
|
+
import { joinClasses } from '../utilities/class-name.ts';
|
|
12
14
|
import { all } from '../utilities/dom.ts';
|
|
13
15
|
import { createEventManager, type IEventManager } from '../utilities/events.ts';
|
|
14
|
-
import {
|
|
16
|
+
import { isPlainObject } from '../utilities/object.ts';
|
|
15
17
|
|
|
16
18
|
export type ThemeConfigKey = 'mode' | 'theme' | 'radius' | 'shadow' | 'font';
|
|
17
19
|
|
|
18
20
|
export interface ThemeClassNames {
|
|
19
21
|
panel: string;
|
|
20
|
-
closeWrap: string;
|
|
21
|
-
closeButton: string;
|
|
22
22
|
title: string;
|
|
23
|
-
description: string;
|
|
24
23
|
container: string;
|
|
25
24
|
item: string;
|
|
26
25
|
itemTitle: string;
|
|
@@ -50,7 +49,7 @@ export interface ThemePanelGroup {
|
|
|
50
49
|
buttons: Array<[string, string]>;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
interface
|
|
52
|
+
export interface ThemeResolvedOptions {
|
|
54
53
|
mode: string;
|
|
55
54
|
theme: string;
|
|
56
55
|
radius: string;
|
|
@@ -65,12 +64,23 @@ interface ThemeCleanup {
|
|
|
65
64
|
events: IEventManager;
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
export interface ThemeInstance {
|
|
68
|
+
props: ThemeResolvedOptions;
|
|
69
|
+
createPanel(
|
|
70
|
+
containerClass?: string | null,
|
|
71
|
+
panelConfig?: ThemePanelGroup[] | null
|
|
72
|
+
): HTMLElement;
|
|
73
|
+
setConfig(newConfig: ThemeOptions): void;
|
|
74
|
+
destroy(): void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type ThemeStoredConfig = Partial<Record<ThemeConfigKey, string>> & {
|
|
78
|
+
render?: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
68
81
|
const DEFAULT_CLASS_NAMES: ThemeClassNames = {
|
|
69
82
|
panel: 'j-theme-palette',
|
|
70
|
-
closeWrap: 'theme-palette-close',
|
|
71
|
-
closeButton: 'j-button is-text',
|
|
72
83
|
title: 'theme-palette-title',
|
|
73
|
-
description: 'theme-palette-description',
|
|
74
84
|
container: 'palette-container',
|
|
75
85
|
item: 'palette-item',
|
|
76
86
|
itemTitle: 'item-title',
|
|
@@ -82,7 +92,7 @@ const DEFAULT_CLASS_NAMES: ThemeClassNames = {
|
|
|
82
92
|
buttonText: 'button-text',
|
|
83
93
|
};
|
|
84
94
|
|
|
85
|
-
const DEFAULT_OPTIONS:
|
|
95
|
+
const DEFAULT_OPTIONS: ThemeResolvedOptions = {
|
|
86
96
|
mode: 'dark',
|
|
87
97
|
theme: 'indigo',
|
|
88
98
|
radius: 'sm',
|
|
@@ -103,7 +113,7 @@ function mergeClassNames(className?: ThemeClassNameConfig): ThemeClassNames {
|
|
|
103
113
|
return { ...DEFAULT_CLASS_NAMES, ...className };
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
function normalizeOptions(options: ThemeOptions = {}):
|
|
116
|
+
function normalizeOptions(options: ThemeOptions = {}): ThemeResolvedOptions {
|
|
107
117
|
return {
|
|
108
118
|
...DEFAULT_OPTIONS,
|
|
109
119
|
...options,
|
|
@@ -121,29 +131,52 @@ function isThemeConfigKey(value: string | undefined): value is ThemeConfigKey {
|
|
|
121
131
|
);
|
|
122
132
|
}
|
|
123
133
|
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
function createThemeStorage(): Storage {
|
|
135
|
+
return createStorage({
|
|
136
|
+
driver: 'cookie',
|
|
137
|
+
codec: 'json',
|
|
138
|
+
driverOptions: {
|
|
139
|
+
path: '/',
|
|
140
|
+
sameSite: 'Lax',
|
|
141
|
+
secure: typeof location !== 'undefined' && location.protocol === 'https:',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeStoredConfig(value: unknown): Partial<ThemeOptions> | null {
|
|
147
|
+
if (!isPlainObject(value)) return null;
|
|
148
|
+
|
|
149
|
+
const source = value as Record<string, unknown>;
|
|
150
|
+
const config: Partial<ThemeOptions> = {};
|
|
151
|
+
const keys: ThemeConfigKey[] = ['mode', 'theme', 'radius', 'shadow', 'font'];
|
|
152
|
+
|
|
153
|
+
for (const key of keys) {
|
|
154
|
+
const item = source[key];
|
|
155
|
+
if (typeof item === 'string') config[key] = item;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return Object.keys(config).length ? config : null;
|
|
128
159
|
}
|
|
129
160
|
|
|
130
161
|
/**
|
|
131
162
|
* 主题管理组件。
|
|
132
163
|
*
|
|
133
|
-
* 负责主题配置的实例化、主题面板交互和
|
|
134
|
-
* 类名,仅在面板点击交互时同步当前点击项对应的 html class。
|
|
164
|
+
* 负责主题配置的实例化、主题面板交互和 createStorage 持久化。实例初始化不修改
|
|
165
|
+
* html 类名,仅在面板点击交互时同步当前点击项对应的 html class。
|
|
135
166
|
*/
|
|
136
|
-
|
|
137
|
-
props:
|
|
167
|
+
class Theme implements ThemeInstance {
|
|
168
|
+
props: ThemeResolvedOptions;
|
|
138
169
|
languages: typeof locales;
|
|
139
170
|
cleanup: ThemeCleanup | null;
|
|
140
|
-
runtime: { destroyed: boolean };
|
|
171
|
+
runtime: { configVersion: number; destroyed: boolean };
|
|
172
|
+
storage: Storage;
|
|
141
173
|
|
|
142
174
|
constructor(options: ThemeOptions = {}) {
|
|
143
175
|
this.props = createDeepStore(normalizeOptions(options));
|
|
144
176
|
this.languages = locales;
|
|
145
177
|
this.cleanup = null;
|
|
146
|
-
this.runtime = { destroyed: false };
|
|
178
|
+
this.runtime = { configVersion: 0, destroyed: false };
|
|
179
|
+
this.storage = createThemeStorage();
|
|
147
180
|
|
|
148
181
|
this.init();
|
|
149
182
|
}
|
|
@@ -159,26 +192,39 @@ export class Theme {
|
|
|
159
192
|
}
|
|
160
193
|
|
|
161
194
|
private loadConfig(): void {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
195
|
+
const configVersion = this.runtime.configVersion;
|
|
196
|
+
void this.storage
|
|
197
|
+
.get<unknown>(this.props.key)
|
|
198
|
+
.then((result) => {
|
|
199
|
+
if (
|
|
200
|
+
this.runtime.destroyed ||
|
|
201
|
+
this.runtime.configVersion !== configVersion
|
|
202
|
+
)
|
|
203
|
+
return;
|
|
204
|
+
|
|
205
|
+
const config = normalizeStoredConfig(result);
|
|
206
|
+
if (!config) return;
|
|
207
|
+
|
|
208
|
+
flushSync(() => {
|
|
209
|
+
Object.assign(this.props, config);
|
|
210
|
+
});
|
|
211
|
+
})
|
|
212
|
+
.catch(() => {});
|
|
173
213
|
}
|
|
174
214
|
|
|
175
215
|
private saveConfig(): void {
|
|
176
216
|
const { mode, theme, radius, shadow, font } = this.props;
|
|
177
217
|
const render = mode === 'auto' ? this.scheme() : mode;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
218
|
+
const config: ThemeStoredConfig = {
|
|
219
|
+
mode,
|
|
220
|
+
theme,
|
|
221
|
+
radius,
|
|
222
|
+
shadow,
|
|
223
|
+
font,
|
|
224
|
+
render,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
void this.storage.set(this.props.key, config).catch(() => {});
|
|
182
228
|
}
|
|
183
229
|
|
|
184
230
|
private scheme(): string {
|
|
@@ -224,6 +270,7 @@ export class Theme {
|
|
|
224
270
|
if (button.classList.contains(this.props.className.active)) return;
|
|
225
271
|
|
|
226
272
|
const previous = this.props[type];
|
|
273
|
+
this.runtime.configVersion += 1;
|
|
227
274
|
flushSync(() => {
|
|
228
275
|
this.props[type] = value;
|
|
229
276
|
});
|
|
@@ -260,19 +307,6 @@ export class Theme {
|
|
|
260
307
|
className: containerClass || className.panel,
|
|
261
308
|
'data-theme-palette': 'root',
|
|
262
309
|
children: [
|
|
263
|
-
// jsx('div', {
|
|
264
|
-
// className: className.closeWrap,
|
|
265
|
-
// 'data-theme-close-wrap': '',
|
|
266
|
-
// style: {
|
|
267
|
-
// display: 'flex',
|
|
268
|
-
// justifyContent: 'center',
|
|
269
|
-
// },
|
|
270
|
-
// children: jsx('button', {
|
|
271
|
-
// className: className.closeButton,
|
|
272
|
-
// 'data-action': 'close',
|
|
273
|
-
// children: this.translate('b'),
|
|
274
|
-
// }),
|
|
275
|
-
// }),
|
|
276
310
|
jsx('h3', {
|
|
277
311
|
className: className.title,
|
|
278
312
|
'data-theme-title': '',
|
|
@@ -281,15 +315,6 @@ export class Theme {
|
|
|
281
315
|
},
|
|
282
316
|
children: this.translate('t'),
|
|
283
317
|
}),
|
|
284
|
-
// jsx('p', {
|
|
285
|
-
// className: className.description,
|
|
286
|
-
// 'data-theme-description': '',
|
|
287
|
-
// style: {
|
|
288
|
-
// marginTop: 0,
|
|
289
|
-
// fontSize: 'var(--text-sm, 0.875rem)',
|
|
290
|
-
// },
|
|
291
|
-
// children: this.translate('d'),
|
|
292
|
-
// }),
|
|
293
318
|
jsx('div', {
|
|
294
319
|
className: className.container,
|
|
295
320
|
'data-theme-container': '',
|
|
@@ -353,6 +378,7 @@ export class Theme {
|
|
|
353
378
|
}
|
|
354
379
|
|
|
355
380
|
setConfig(newConfig: ThemeOptions): void {
|
|
381
|
+
this.runtime.configVersion += 1;
|
|
356
382
|
flushSync(() => {
|
|
357
383
|
Object.assign(this.props, newConfig, {
|
|
358
384
|
className: mergeClassNames({
|
|
@@ -371,6 +397,7 @@ export class Theme {
|
|
|
371
397
|
this.cleanup?.bindings.forEach((dispose) => dispose());
|
|
372
398
|
this.cleanup?.bindings.clear();
|
|
373
399
|
this.cleanup = null;
|
|
400
|
+
void this.storage.close().catch(() => {});
|
|
374
401
|
}
|
|
375
402
|
|
|
376
403
|
private defaultPanelConfig(): ThemePanelGroup[] {
|
|
@@ -438,3 +465,7 @@ export class Theme {
|
|
|
438
465
|
];
|
|
439
466
|
}
|
|
440
467
|
}
|
|
468
|
+
|
|
469
|
+
export function createTheme(options: ThemeOptions = {}): ThemeInstance {
|
|
470
|
+
return new Theme(options);
|
|
471
|
+
}
|
|
@@ -2,10 +2,13 @@ import { jsx } from 'vanilla-signal';
|
|
|
2
2
|
import { t } from 'vanilla-signal-i18n';
|
|
3
3
|
|
|
4
4
|
import locales from '../locales/index.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { icon } from '../primitives/icons.ts';
|
|
6
|
+
import { joinClasses } from '../utilities/class-name.ts';
|
|
6
7
|
import { q } from '../utilities/dom.ts';
|
|
7
8
|
import { listen } from '../utilities/events.ts';
|
|
8
|
-
import {
|
|
9
|
+
import { randomId } from '../utilities/id.ts';
|
|
10
|
+
import { timer } from '../utilities/timer.ts';
|
|
11
|
+
import { validateParam } from '../utilities/types.ts';
|
|
9
12
|
|
|
10
13
|
export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'primary';
|
|
11
14
|
|
|
@@ -14,13 +17,12 @@ export interface ToastClassNames {
|
|
|
14
17
|
toast: string;
|
|
15
18
|
icon: string;
|
|
16
19
|
message: string;
|
|
17
|
-
hidden: string;
|
|
18
20
|
lite: string;
|
|
19
21
|
action: string;
|
|
20
22
|
actions: string;
|
|
21
23
|
button: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
closeBtn: string;
|
|
25
|
+
actionBtn: string;
|
|
24
26
|
info: string;
|
|
25
27
|
success: string;
|
|
26
28
|
warning: string;
|
|
@@ -50,20 +52,19 @@ interface ResolvedToastOptions {
|
|
|
50
52
|
const DEFAULT_CLASS_NAMES: ToastClassNames = {
|
|
51
53
|
container: 'j-toast-container',
|
|
52
54
|
toast: 'j-toast',
|
|
53
|
-
icon: '
|
|
54
|
-
message: '
|
|
55
|
-
hidden: 'is-hidden',
|
|
55
|
+
icon: 'el-icon',
|
|
56
|
+
message: 'el-text',
|
|
56
57
|
lite: 'j-toast-lite',
|
|
57
|
-
action: 'is-action',
|
|
58
|
+
action: 'j-toast is-action',
|
|
58
59
|
actions: 'toast-actions',
|
|
59
60
|
button: 'j-button is-sm',
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
closeBtn: 'is-ghost',
|
|
62
|
+
actionBtn: 'is-outline',
|
|
62
63
|
info: 'is-info',
|
|
64
|
+
primary: 'is-primary',
|
|
63
65
|
success: 'is-success',
|
|
64
66
|
warning: 'is-warning',
|
|
65
67
|
error: 'is-error',
|
|
66
|
-
primary: 'is-primary',
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
const TOAST_TYPE_RULE = {
|
|
@@ -73,26 +74,18 @@ const TOAST_TYPE_RULE = {
|
|
|
73
74
|
|
|
74
75
|
const TOAST_DURATION_RULE = {
|
|
75
76
|
type: 'number',
|
|
76
|
-
|
|
77
|
-
message: 'expects a positive number or 0.',
|
|
77
|
+
min: 0,
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const LITE_DURATION_RULE = {
|
|
81
81
|
type: 'number',
|
|
82
|
-
|
|
83
|
-
message: 'expects a number greater than 0.',
|
|
82
|
+
greaterThan: 0,
|
|
84
83
|
};
|
|
85
84
|
|
|
86
85
|
function mergeClassNames(className?: ToastClassNameConfig): ToastClassNames {
|
|
87
86
|
return { ...DEFAULT_CLASS_NAMES, ...className };
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
function joinClasses(
|
|
91
|
-
...classes: Array<string | null | undefined | false>
|
|
92
|
-
): string {
|
|
93
|
-
return classes.filter(Boolean).join(' ');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
89
|
/**
|
|
97
90
|
* Toast 消息提示工具。
|
|
98
91
|
*
|
|
@@ -284,7 +277,7 @@ export class Toast {
|
|
|
284
277
|
const toastContainer = Toast.getOrCreateContainer(className);
|
|
285
278
|
const id = randomId();
|
|
286
279
|
const action = jsx('div', {
|
|
287
|
-
className:
|
|
280
|
+
className: className.action,
|
|
288
281
|
'data-toast': id,
|
|
289
282
|
'data-toast-action': '',
|
|
290
283
|
children: [
|
|
@@ -298,7 +291,7 @@ export class Toast {
|
|
|
298
291
|
'data-toast-actions': id,
|
|
299
292
|
children: [
|
|
300
293
|
jsx('button', {
|
|
301
|
-
className: joinClasses(className.button, className.
|
|
294
|
+
className: joinClasses(className.button, className.closeBtn),
|
|
302
295
|
children: props.text?.close || c,
|
|
303
296
|
'data-action': 'close',
|
|
304
297
|
'aria-label': props.text?.close || c,
|
|
@@ -308,7 +301,7 @@ export class Toast {
|
|
|
308
301
|
},
|
|
309
302
|
}),
|
|
310
303
|
jsx('button', {
|
|
311
|
-
className: joinClasses(className.button, className.
|
|
304
|
+
className: joinClasses(className.button, className.actionBtn),
|
|
312
305
|
children: props.text?.action || a,
|
|
313
306
|
'data-action': 'toast-action',
|
|
314
307
|
'aria-label': props.text?.action || a,
|
|
@@ -1,33 +1,79 @@
|
|
|
1
1
|
import { jsx } from 'vanilla-signal';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { joinClasses } from '../utilities/class-name.ts';
|
|
4
4
|
import { type DOMReference } from '../utilities/dom.ts';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
|
|
6
|
+
import { createDrop } from './drop.ts';
|
|
7
|
+
|
|
8
|
+
type DropInstance = ReturnType<typeof createDrop>;
|
|
9
|
+
type DropMode = 'hover' | 'click';
|
|
10
|
+
type DropPosition =
|
|
11
|
+
| 'auto'
|
|
12
|
+
| 'top-left'
|
|
13
|
+
| 'top-center'
|
|
14
|
+
| 'top-right'
|
|
15
|
+
| 'bottom-left'
|
|
16
|
+
| 'bottom-center'
|
|
17
|
+
| 'bottom-right'
|
|
18
|
+
| 'left'
|
|
19
|
+
| 'right';
|
|
20
|
+
|
|
21
|
+
interface DropDelay {
|
|
22
|
+
show?: number;
|
|
23
|
+
hide?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface TooltipDom {
|
|
27
|
+
root: HTMLElement | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface TooltipInstance {
|
|
31
|
+
dom: TooltipDom;
|
|
32
|
+
drop: DropInstance | null;
|
|
33
|
+
show(useDelay?: boolean): void;
|
|
34
|
+
hide(useDelay?: boolean): void;
|
|
35
|
+
toggle(): void;
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface TooltipClassNames {
|
|
13
40
|
container: string;
|
|
14
41
|
message: string;
|
|
42
|
+
ui: TooltipThemeClassNames;
|
|
15
43
|
}
|
|
16
44
|
|
|
17
|
-
|
|
45
|
+
interface TooltipThemeClassNames {
|
|
46
|
+
reverse: string;
|
|
47
|
+
primary: string;
|
|
48
|
+
success: string;
|
|
49
|
+
warning: string;
|
|
50
|
+
error: string;
|
|
51
|
+
}
|
|
18
52
|
|
|
19
|
-
|
|
53
|
+
type TooltipTheme =
|
|
54
|
+
| false
|
|
55
|
+
| 'reverse'
|
|
56
|
+
| 'primary'
|
|
57
|
+
| 'success'
|
|
58
|
+
| 'warning'
|
|
59
|
+
| 'error';
|
|
60
|
+
type TooltipClassNameConfig = Partial<Omit<TooltipClassNames, 'ui'>> & {
|
|
61
|
+
ui?: Partial<TooltipThemeClassNames>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
interface TooltipProps extends Record<string, unknown> {
|
|
20
65
|
name?: string | null;
|
|
21
66
|
mode?: DropMode;
|
|
22
67
|
position?: DropPosition;
|
|
23
68
|
offset?: number;
|
|
24
69
|
message?: string;
|
|
70
|
+
theme?: TooltipTheme;
|
|
25
71
|
className?: TooltipClassNameConfig;
|
|
26
72
|
id?: string | null;
|
|
27
73
|
delay?: number | DropDelay;
|
|
28
74
|
hoverIntent?: boolean;
|
|
29
|
-
onShown?: ((drop:
|
|
30
|
-
onHidden?: ((drop:
|
|
75
|
+
onShown?: ((drop: DropInstance) => void | Promise<void>) | null;
|
|
76
|
+
onHidden?: ((drop: DropInstance) => void | Promise<void>) | null;
|
|
31
77
|
}
|
|
32
78
|
|
|
33
79
|
interface ResolvedTooltipProps extends Record<string, unknown> {
|
|
@@ -36,6 +82,7 @@ interface ResolvedTooltipProps extends Record<string, unknown> {
|
|
|
36
82
|
position: DropPosition;
|
|
37
83
|
offset: number;
|
|
38
84
|
message: string;
|
|
85
|
+
theme: TooltipTheme;
|
|
39
86
|
className: TooltipClassNames;
|
|
40
87
|
id: string | null;
|
|
41
88
|
delay: number | DropDelay;
|
|
@@ -46,9 +93,30 @@ interface ResolvedTooltipProps extends Record<string, unknown> {
|
|
|
46
93
|
|
|
47
94
|
const DEFAULT_CLASS_NAMES: TooltipClassNames = {
|
|
48
95
|
container: 'j-tooltip',
|
|
49
|
-
message: '
|
|
96
|
+
message: 'el-text',
|
|
97
|
+
ui: {
|
|
98
|
+
reverse: 'is-reverse',
|
|
99
|
+
primary: 'is-primary',
|
|
100
|
+
success: 'is-success',
|
|
101
|
+
warning: 'is-warning',
|
|
102
|
+
error: 'is-error',
|
|
103
|
+
},
|
|
50
104
|
};
|
|
51
105
|
|
|
106
|
+
function normalizeClassNames(value: unknown): TooltipClassNames {
|
|
107
|
+
const input = value && typeof value === 'object' ? value : {};
|
|
108
|
+
const ui =
|
|
109
|
+
'ui' in input && input.ui && typeof input.ui === 'object' ? input.ui : {};
|
|
110
|
+
return {
|
|
111
|
+
...DEFAULT_CLASS_NAMES,
|
|
112
|
+
...input,
|
|
113
|
+
ui: {
|
|
114
|
+
...DEFAULT_CLASS_NAMES.ui,
|
|
115
|
+
...ui,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
52
120
|
const TOOLTIP_OPTIONS_SCHEMA = {
|
|
53
121
|
name: { default: null, types: ['string', 'null'] },
|
|
54
122
|
mode: { default: 'hover', type: 'string', enum: ['hover', 'click'] },
|
|
@@ -73,16 +141,17 @@ const TOOLTIP_OPTIONS_SCHEMA = {
|
|
|
73
141
|
type: 'string',
|
|
74
142
|
normalize: (value: unknown) =>
|
|
75
143
|
typeof value === 'string' ? value.trim() : value,
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
nonEmpty: true,
|
|
145
|
+
},
|
|
146
|
+
theme: {
|
|
147
|
+
default: false,
|
|
148
|
+
types: ['string', 'boolean'],
|
|
149
|
+
enum: [false, 'reverse', 'primary', 'success', 'warning', 'error'],
|
|
78
150
|
},
|
|
79
151
|
className: {
|
|
80
152
|
default: DEFAULT_CLASS_NAMES,
|
|
81
153
|
type: 'object',
|
|
82
|
-
normalize:
|
|
83
|
-
...DEFAULT_CLASS_NAMES,
|
|
84
|
-
...(value && typeof value === 'object' ? value : {}),
|
|
85
|
-
}),
|
|
154
|
+
normalize: normalizeClassNames,
|
|
86
155
|
},
|
|
87
156
|
id: { default: null, types: ['string', 'null'] },
|
|
88
157
|
delay: { default: 100, types: ['number', 'object'] },
|
|
@@ -99,6 +168,7 @@ function normalizeProps(input: TooltipProps): ResolvedTooltipProps {
|
|
|
99
168
|
position: props.position as DropPosition,
|
|
100
169
|
offset: props.offset as number,
|
|
101
170
|
message: props.message as string,
|
|
171
|
+
theme: props.theme as TooltipTheme,
|
|
102
172
|
className: props.className as TooltipClassNames,
|
|
103
173
|
id: props.id as string | null,
|
|
104
174
|
delay: props.delay as number | DropDelay,
|
|
@@ -113,14 +183,15 @@ function normalizeProps(input: TooltipProps): ResolvedTooltipProps {
|
|
|
113
183
|
*
|
|
114
184
|
* 基于 Drop 实现,提供更轻量的文本提示封装。
|
|
115
185
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
186
|
+
class Tooltip implements TooltipInstance {
|
|
187
|
+
dom: TooltipDom;
|
|
188
|
+
drop: DropInstance | null;
|
|
118
189
|
props: ResolvedTooltipProps | null;
|
|
119
190
|
|
|
120
191
|
constructor(element: DOMReference, props: TooltipProps = {}) {
|
|
121
192
|
const settings = normalizeProps(props);
|
|
122
193
|
this.props = settings;
|
|
123
|
-
this.drop =
|
|
194
|
+
this.drop = createDrop(element, {
|
|
124
195
|
name: settings.name,
|
|
125
196
|
mode: settings.mode,
|
|
126
197
|
position: settings.position,
|
|
@@ -132,11 +203,15 @@ export class Tooltip {
|
|
|
132
203
|
onHidden: settings.onHidden,
|
|
133
204
|
content: this.buildContent(settings),
|
|
134
205
|
});
|
|
206
|
+
this.dom = this.drop.dom;
|
|
135
207
|
}
|
|
136
208
|
|
|
137
209
|
private buildContent(settings: ResolvedTooltipProps): HTMLElement {
|
|
138
210
|
return jsx('div', {
|
|
139
|
-
className:
|
|
211
|
+
className: joinClasses(
|
|
212
|
+
settings.className.container,
|
|
213
|
+
settings.theme && settings.className.ui[settings.theme]
|
|
214
|
+
),
|
|
140
215
|
'data-tooltip': settings.name || settings.id || '',
|
|
141
216
|
children: jsx('div', {
|
|
142
217
|
className: settings.className.message,
|
|
@@ -168,6 +243,6 @@ export class Tooltip {
|
|
|
168
243
|
export function createTooltip(
|
|
169
244
|
element: DOMReference,
|
|
170
245
|
props: TooltipProps = {}
|
|
171
|
-
):
|
|
246
|
+
): TooltipInstance {
|
|
172
247
|
return new Tooltip(element, props);
|
|
173
248
|
}
|