vanilla-jui 1.4.4 → 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.
Files changed (103) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +1273 -1896
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +22 -3
  7. package/src/components/accordion.ts +297 -216
  8. package/src/components/flow.ts +222 -258
  9. package/src/components/form.ts +44 -102
  10. package/src/components/menu.ts +268 -356
  11. package/src/components/modal.ts +181 -641
  12. package/src/components/offcanvas.ts +225 -106
  13. package/src/components/pagination.ts +296 -326
  14. package/src/components/sticky.ts +55 -54
  15. package/src/components/swiper.ts +237 -247
  16. package/src/components/tabs.ts +278 -271
  17. package/src/components/toc.ts +42 -46
  18. package/src/core/Component.ts +69 -47
  19. package/src/css/accordion.css +2 -2
  20. package/src/css/animation.css +1 -5
  21. package/src/css/avatar.css +1 -1
  22. package/src/css/badge.css +1 -1
  23. package/src/css/breadcrumb.css +9 -10
  24. package/src/css/button.css +1 -1
  25. package/src/css/card.css +2 -2
  26. package/src/css/color.css +974 -1002
  27. package/src/css/color_p3.css +974 -1004
  28. package/src/css/components.css +1 -1
  29. package/src/css/content.css +72 -63
  30. package/src/css/drop.css +1 -1
  31. package/src/css/editor.css +14 -14
  32. package/src/css/flow.css +4 -14
  33. package/src/css/font.css +5 -4
  34. package/src/css/form.css +76 -52
  35. package/src/css/gradient.css +299 -222
  36. package/src/css/icon.css +57 -57
  37. package/src/css/index.css +1 -1
  38. package/src/css/layout.css +13 -10
  39. package/src/css/loading.css +60 -12
  40. package/src/css/menu.css +262 -188
  41. package/src/css/modal.css +1 -1
  42. package/src/css/offcanvas.css +9 -13
  43. package/src/css/pagination.css +5 -1
  44. package/src/css/popup.css +1 -1
  45. package/src/css/skeleton.css +3 -2
  46. package/src/css/status.css +2 -3
  47. package/src/css/swiper.css +1 -1
  48. package/src/css/table.css +1 -1
  49. package/src/css/tabs.css +12 -11
  50. package/src/css/tag.css +1 -1
  51. package/src/css/theme-palette.css +19 -5
  52. package/src/css/themes/default/accordion.css +61 -61
  53. package/src/css/themes/default/avatar.css +54 -54
  54. package/src/css/themes/default/badge.css +62 -62
  55. package/src/css/themes/default/breadcrumb.css +23 -23
  56. package/src/css/themes/default/button.css +203 -203
  57. package/src/css/themes/default/content.css +53 -53
  58. package/src/css/themes/default/drop.css +10 -10
  59. package/src/css/themes/default/editor.css +9 -9
  60. package/src/css/themes/default/index.css +1 -1
  61. package/src/css/themes/default/loading.css +26 -20
  62. package/src/css/themes/default/menu.css +75 -11
  63. package/src/css/themes/default/modal.css +32 -36
  64. package/src/css/themes/default/pagination.css +7 -7
  65. package/src/css/themes/default/popup.css +6 -6
  66. package/src/css/themes/default/swiper.css +32 -32
  67. package/src/css/themes/default/table.css +16 -16
  68. package/src/css/themes/default/tabs.css +30 -30
  69. package/src/css/themes/default/tag.css +71 -71
  70. package/src/css/themes/default/theme-palette.css +11 -11
  71. package/src/css/themes/default/tip.css +42 -42
  72. package/src/css/themes/default/toast.css +60 -60
  73. package/src/css/themes/default/toolbar.css +23 -29
  74. package/src/css/themes/default/tooltip.css +43 -13
  75. package/src/css/themes/index.css +1 -1
  76. package/src/css/tip.css +2 -2
  77. package/src/css/toast.css +3 -3
  78. package/src/css/tokens.css +12 -17
  79. package/src/css/toolbar.css +9 -10
  80. package/src/css/tooltip.css +3 -2
  81. package/src/index.ts +22 -19
  82. package/src/{components → primitives}/drop.ts +58 -49
  83. package/src/{components → primitives}/icons.ts +1 -1
  84. package/src/primitives/loading.ts +23 -0
  85. package/src/primitives/parabola.ts +388 -0
  86. package/src/primitives/popup.ts +61 -0
  87. package/src/{components → primitives}/theme.ts +88 -57
  88. package/src/{components → primitives}/toast.ts +18 -25
  89. package/src/{components → primitives}/tooltip.ts +100 -25
  90. package/src/utilities/browser.ts +23 -49
  91. package/src/utilities/class-name.ts +5 -0
  92. package/src/utilities/dom.ts +14 -32
  93. package/src/utilities/function.ts +4 -0
  94. package/src/utilities/http.ts +7 -7
  95. package/src/utilities/id.ts +57 -0
  96. package/src/utilities/object.ts +5 -0
  97. package/src/utilities/timer.ts +38 -0
  98. package/src/utilities/types.ts +420 -0
  99. package/src/{components → validation}/validator.ts +64 -53
  100. package/src/components/parabola.ts +0 -335
  101. package/src/utilities/cache.ts +0 -57
  102. package/src/utilities/core.ts +0 -614
  103. 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 { getCookie, setCookie } from '../utilities/storage.ts';
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 ResolvedThemeOptions {
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: ResolvedThemeOptions = {
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 = {}): ResolvedThemeOptions {
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 joinClasses(
125
- ...classes: Array<string | null | undefined | false>
126
- ): string {
127
- return classes.filter(Boolean).join(' ');
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
- * 负责主题配置的实例化、主题面板交互和 Cookie 读写。实例初始化不修改 html
134
- * 类名,仅在面板点击交互时同步当前点击项对应的 html class。
164
+ * 负责主题配置的实例化、主题面板交互和 createStorage 持久化。实例初始化不修改
165
+ * html 类名,仅在面板点击交互时同步当前点击项对应的 html class。
135
166
  */
136
- export class Theme {
137
- props: ResolvedThemeOptions;
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
- try {
163
- const result = getCookie(this.props.key);
164
- if (!result?.trim()) return;
165
- const parsed = JSON.parse(result) as Partial<ThemeOptions>;
166
- Object.assign(this.props, parsed, {
167
- className: mergeClassNames({
168
- ...this.props.className,
169
- ...parsed.className,
170
- }),
171
- });
172
- } catch {}
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
- setCookie(
179
- this.props.key,
180
- JSON.stringify({ mode, theme, radius, shadow, font, render })
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 { randomId, timer, validateParam } from '../utilities/core.ts';
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 { icon } from './icons.ts';
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
- closeButton: string;
23
- actionButton: string;
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: 'toast-icon',
54
- message: 'toast-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
- closeButton: 'is-ghost',
61
- actionButton: 'is-outline',
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
- validate: (value: unknown) => typeof value === 'number' && value >= 0,
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
- validate: (value: unknown) => typeof value === 'number' && value > 0,
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: joinClasses(className.toast, className.action),
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.closeButton),
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.actionButton),
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 { type ResolveSchema, resolveProps } from '../utilities/core.ts';
3
+ import { joinClasses } from '../utilities/class-name.ts';
4
4
  import { type DOMReference } from '../utilities/dom.ts';
5
- import {
6
- Drop,
7
- type DropDelay,
8
- type DropMode,
9
- type DropPosition,
10
- } from './drop.ts';
11
-
12
- export interface TooltipClassNames {
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
- export type TooltipClassNameConfig = Partial<TooltipClassNames>;
45
+ interface TooltipThemeClassNames {
46
+ reverse: string;
47
+ primary: string;
48
+ success: string;
49
+ warning: string;
50
+ error: string;
51
+ }
18
52
 
19
- export interface TooltipProps extends Record<string, unknown> {
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: Drop) => void | Promise<void>) | null;
30
- onHidden?: ((drop: Drop) => void | Promise<void>) | null;
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: 'tooltip-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
- validate: (value: unknown) => typeof value === 'string' && value.length > 0,
77
- message: 'expects a non-empty string.',
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: (value: unknown) => ({
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
- export class Tooltip {
117
- drop: Drop | null;
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 = new Drop(element, {
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: settings.className.container,
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
- ): Tooltip {
246
+ ): TooltipInstance {
172
247
  return new Tooltip(element, props);
173
248
  }