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.
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 +21 -2
  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 +89 -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,53 @@ 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
+ 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
- * 负责主题配置的实例化、主题面板交互和 Cookie 读写。实例初始化不修改 html
134
- * 类名,仅在面板点击交互时同步当前点击项对应的 html class。
165
+ * 负责主题配置的实例化、主题面板交互和 createStorage 持久化。实例初始化不修改
166
+ * html 类名,仅在面板点击交互时同步当前点击项对应的 html class。
135
167
  */
136
- export class Theme {
137
- props: ResolvedThemeOptions;
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
- 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 {}
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
- setCookie(
179
- this.props.key,
180
- JSON.stringify({ mode, theme, radius, shadow, font, render })
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 { 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
  }