vanilla-jui 1.4.5 → 1.4.7
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 +89 -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,53 @@ 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
|
+
ttl: 864e5,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function normalizeStoredConfig(value: unknown): Partial<ThemeOptions> | null {
|
|
148
|
+
if (!isPlainObject(value)) return null;
|
|
149
|
+
|
|
150
|
+
const source = value as Record<string, unknown>;
|
|
151
|
+
const config: Partial<ThemeOptions> = {};
|
|
152
|
+
const keys: ThemeConfigKey[] = ['mode', 'theme', 'radius', 'shadow', 'font'];
|
|
153
|
+
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const item = source[key];
|
|
156
|
+
if (typeof item === 'string') config[key] = item;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return Object.keys(config).length ? config : null;
|
|
128
160
|
}
|
|
129
161
|
|
|
130
162
|
/**
|
|
131
163
|
* 主题管理组件。
|
|
132
164
|
*
|
|
133
|
-
* 负责主题配置的实例化、主题面板交互和
|
|
134
|
-
* 类名,仅在面板点击交互时同步当前点击项对应的 html class。
|
|
165
|
+
* 负责主题配置的实例化、主题面板交互和 createStorage 持久化。实例初始化不修改
|
|
166
|
+
* html 类名,仅在面板点击交互时同步当前点击项对应的 html class。
|
|
135
167
|
*/
|
|
136
|
-
|
|
137
|
-
props:
|
|
168
|
+
class Theme implements ThemeInstance {
|
|
169
|
+
props: ThemeResolvedOptions;
|
|
138
170
|
languages: typeof locales;
|
|
139
171
|
cleanup: ThemeCleanup | null;
|
|
140
|
-
runtime: { destroyed: boolean };
|
|
172
|
+
runtime: { configVersion: number; destroyed: boolean };
|
|
173
|
+
storage: Storage;
|
|
141
174
|
|
|
142
175
|
constructor(options: ThemeOptions = {}) {
|
|
143
176
|
this.props = createDeepStore(normalizeOptions(options));
|
|
144
177
|
this.languages = locales;
|
|
145
178
|
this.cleanup = null;
|
|
146
|
-
this.runtime = { destroyed: false };
|
|
179
|
+
this.runtime = { configVersion: 0, destroyed: false };
|
|
180
|
+
this.storage = createThemeStorage();
|
|
147
181
|
|
|
148
182
|
this.init();
|
|
149
183
|
}
|
|
@@ -159,26 +193,39 @@ export class Theme {
|
|
|
159
193
|
}
|
|
160
194
|
|
|
161
195
|
private loadConfig(): void {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
const configVersion = this.runtime.configVersion;
|
|
197
|
+
void this.storage
|
|
198
|
+
.get<unknown>(this.props.key)
|
|
199
|
+
.then((result) => {
|
|
200
|
+
if (
|
|
201
|
+
this.runtime.destroyed ||
|
|
202
|
+
this.runtime.configVersion !== configVersion
|
|
203
|
+
)
|
|
204
|
+
return;
|
|
205
|
+
|
|
206
|
+
const config = normalizeStoredConfig(result);
|
|
207
|
+
if (!config) return;
|
|
208
|
+
|
|
209
|
+
flushSync(() => {
|
|
210
|
+
Object.assign(this.props, config);
|
|
211
|
+
});
|
|
212
|
+
})
|
|
213
|
+
.catch(() => {});
|
|
173
214
|
}
|
|
174
215
|
|
|
175
216
|
private saveConfig(): void {
|
|
176
217
|
const { mode, theme, radius, shadow, font } = this.props;
|
|
177
218
|
const render = mode === 'auto' ? this.scheme() : mode;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
219
|
+
const config: ThemeStoredConfig = {
|
|
220
|
+
mode,
|
|
221
|
+
theme,
|
|
222
|
+
radius,
|
|
223
|
+
shadow,
|
|
224
|
+
font,
|
|
225
|
+
render,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
void this.storage.set(this.props.key, config).catch(() => {});
|
|
182
229
|
}
|
|
183
230
|
|
|
184
231
|
private scheme(): string {
|
|
@@ -224,6 +271,7 @@ export class Theme {
|
|
|
224
271
|
if (button.classList.contains(this.props.className.active)) return;
|
|
225
272
|
|
|
226
273
|
const previous = this.props[type];
|
|
274
|
+
this.runtime.configVersion += 1;
|
|
227
275
|
flushSync(() => {
|
|
228
276
|
this.props[type] = value;
|
|
229
277
|
});
|
|
@@ -260,19 +308,6 @@ export class Theme {
|
|
|
260
308
|
className: containerClass || className.panel,
|
|
261
309
|
'data-theme-palette': 'root',
|
|
262
310
|
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
311
|
jsx('h3', {
|
|
277
312
|
className: className.title,
|
|
278
313
|
'data-theme-title': '',
|
|
@@ -281,15 +316,6 @@ export class Theme {
|
|
|
281
316
|
},
|
|
282
317
|
children: this.translate('t'),
|
|
283
318
|
}),
|
|
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
319
|
jsx('div', {
|
|
294
320
|
className: className.container,
|
|
295
321
|
'data-theme-container': '',
|
|
@@ -353,6 +379,7 @@ export class Theme {
|
|
|
353
379
|
}
|
|
354
380
|
|
|
355
381
|
setConfig(newConfig: ThemeOptions): void {
|
|
382
|
+
this.runtime.configVersion += 1;
|
|
356
383
|
flushSync(() => {
|
|
357
384
|
Object.assign(this.props, newConfig, {
|
|
358
385
|
className: mergeClassNames({
|
|
@@ -371,6 +398,7 @@ export class Theme {
|
|
|
371
398
|
this.cleanup?.bindings.forEach((dispose) => dispose());
|
|
372
399
|
this.cleanup?.bindings.clear();
|
|
373
400
|
this.cleanup = null;
|
|
401
|
+
void this.storage.close().catch(() => {});
|
|
374
402
|
}
|
|
375
403
|
|
|
376
404
|
private defaultPanelConfig(): ThemePanelGroup[] {
|
|
@@ -438,3 +466,7 @@ export class Theme {
|
|
|
438
466
|
];
|
|
439
467
|
}
|
|
440
468
|
}
|
|
469
|
+
|
|
470
|
+
export function createTheme(options: ThemeOptions = {}): ThemeInstance {
|
|
471
|
+
return new Theme(options);
|
|
472
|
+
}
|
|
@@ -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
|
}
|