@agions/taroviz 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,628 @@
1
+ /**
2
+ * TaroViz 主题系统
3
+ * 支持 CSS 变量、动态主题切换、自定义主题
4
+ */
5
+ import type { EChartsOption } from 'echarts';
6
+
7
+ // ============================================================================
8
+ // 类型定义
9
+ // ============================================================================
10
+
11
+ /**
12
+ * 主题类型
13
+ */
14
+ export type ThemeType = 'light' | 'dark' | 'custom';
15
+
16
+ /**
17
+ * 主题配置
18
+ */
19
+ export interface ThemeConfig {
20
+ /** 主题名称 */
21
+ name: string;
22
+ /** 主题类型 */
23
+ type: ThemeType;
24
+ /** 主题变量 */
25
+ variables: ThemeVariables;
26
+ /** ECharts 主题配置 */
27
+ echartsTheme?: Record<string, unknown>;
28
+ /** 是否为暗色主题 */
29
+ isDark?: boolean;
30
+ }
31
+
32
+ /**
33
+ * 主题变量 (CSS 变量)
34
+ */
35
+ export interface ThemeVariables {
36
+ /** 背景色 */
37
+ '--tv-bg-color': string;
38
+ '--tv-bg-color-secondary': string;
39
+ /** 文字颜色 */
40
+ '--tv-text-color': string;
41
+ '--tv-text-color-secondary': string;
42
+ /** 主色调 */
43
+ '--tv-primary-color': string;
44
+ '--tv-primary-color-hover': string;
45
+ '--tv-primary-color-active': string;
46
+ /** 成功色 */
47
+ '--tv-success-color': string;
48
+ /** 警告色 */
49
+ '--tv-warning-color': string;
50
+ /** 错误色 */
51
+ '--tv-error-color': string;
52
+ /** 边框颜色 */
53
+ '--tv-border-color': string;
54
+ /** 分割线颜色 */
55
+ '--tv-divider-color': string;
56
+ /** 阴影颜色 */
57
+ '--tv-shadow-color': string;
58
+ /** 图表颜色系列 */
59
+ '--tv-chart-color-1': string;
60
+ '--tv-chart-color-2': string;
61
+ '--tv-chart-color-3': string;
62
+ '--tv-chart-color-4': string;
63
+ '--tv-chart-color-5': string;
64
+ '--tv-chart-color-6': string;
65
+ '--tv-chart-color-7': string;
66
+ '--tv-chart-color-8': string;
67
+ /** 字体 */
68
+ '--tv-font-family': string;
69
+ '--tv-font-size': string;
70
+ '--tv-font-size-small': string;
71
+ '--tv-font-size-large': string;
72
+ /** 圆角 */
73
+ '--tv-border-radius': string;
74
+ '--tv-border-radius-small': string;
75
+ /** 动画 */
76
+ '--tv-transition-duration': string;
77
+ }
78
+
79
+ /**
80
+ * 预设主题
81
+ */
82
+ export type PresetThemeName = 'default' | 'dark' | 'vintage' | 'macarons' | 'infographic' | 'helianthus' | 'blue' | 'red' | 'green' | 'purple';
83
+
84
+ // ============================================================================
85
+ // 预设主题配置
86
+ // ============================================================================
87
+
88
+ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
89
+ default: {
90
+ name: 'default',
91
+ type: 'light',
92
+ isDark: false,
93
+ variables: {
94
+ '--tv-bg-color': '#ffffff',
95
+ '--tv-bg-color-secondary': '#fafafa',
96
+ '--tv-text-color': '#333333',
97
+ '--tv-text-color-secondary': '#666666',
98
+ '--tv-primary-color': '#1890ff',
99
+ '--tv-primary-color-hover': '#40a9ff',
100
+ '--tv-primary-color-active': '#096dd9',
101
+ '--tv-success-color': '#52c41a',
102
+ '--tv-warning-color': '#faad14',
103
+ '--tv-error-color': '#ff4d4f',
104
+ '--tv-border-color': '#d9d9d9',
105
+ '--tv-divider-color': '#f0f0f0',
106
+ '--tv-shadow-color': 'rgba(0, 0, 0, 0.1)',
107
+ '--tv-chart-color-1': '#5470c6',
108
+ '--tv-chart-color-2': '#91cc75',
109
+ '--tv-chart-color-3': '#fac858',
110
+ '--tv-chart-color-4': '#ee6666',
111
+ '--tv-chart-color-5': '#73c0de',
112
+ '--tv-chart-color-6': '#3ba272',
113
+ '--tv-chart-color-7': '#fc8452',
114
+ '--tv-chart-color-8': '#9a60b4',
115
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
116
+ '--tv-font-size': '14px',
117
+ '--tv-font-size-small': '12px',
118
+ '--tv-font-size-large': '16px',
119
+ '--tv-border-radius': '4px',
120
+ '--tv-border-radius-small': '2px',
121
+ '--tv-transition-duration': '0.3s',
122
+ },
123
+ },
124
+ dark: {
125
+ name: 'dark',
126
+ type: 'dark',
127
+ isDark: true,
128
+ variables: {
129
+ '--tv-bg-color': '#1a1a2e',
130
+ '--tv-bg-color-secondary': '#16213e',
131
+ '--tv-text-color': '#e0e0e0',
132
+ '--tv-text-color-secondary': '#a0a0a0',
133
+ '--tv-primary-color': '#1890ff',
134
+ '--tv-primary-color-hover': '#40a9ff',
135
+ '--tv-primary-color-active': '#096dd9',
136
+ '--tv-success-color': '#52c41a',
137
+ '--tv-warning-color': '#faad14',
138
+ '--tv-error-color': '#ff4d4f',
139
+ '--tv-border-color': '#404040',
140
+ '--tv-divider-color': '#303030',
141
+ '--tv-shadow-color': 'rgba(0, 0, 0, 0.3)',
142
+ '--tv-chart-color-1': '#5470c6',
143
+ '--tv-chart-color-2': '#91cc75',
144
+ '--tv-chart-color-3': '#fac858',
145
+ '--tv-chart-color-4': '#ee6666',
146
+ '--tv-chart-color-5': '#73c0de',
147
+ '--tv-chart-color-6': '#3ba272',
148
+ '--tv-chart-color-7': '#fc8452',
149
+ '--tv-chart-color-8': '#9a60b4',
150
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
151
+ '--tv-font-size': '14px',
152
+ '--tv-font-size-small': '12px',
153
+ '--tv-font-size-large': '16px',
154
+ '--tv-border-radius': '4px',
155
+ '--tv-border-radius-small': '2px',
156
+ '--tv-transition-duration': '0.3s',
157
+ },
158
+ },
159
+ vintage: {
160
+ name: 'vintage',
161
+ type: 'custom',
162
+ isDark: false,
163
+ variables: {
164
+ '--tv-bg-color': '#fef9ef',
165
+ '--tv-bg-color-secondary': '#fcf5e9',
166
+ '--tv-text-color': '#5c4d3d',
167
+ '--tv-text-color-secondary': '#8b7355',
168
+ '--tv-primary-color': '#d4a574',
169
+ '--tv-primary-color-hover': '#c49566',
170
+ '--tv-primary-color-active': '#b8895a',
171
+ '--tv-success-color': '#8db78e',
172
+ '--tv-warning-color': '#e6c87a',
173
+ '--tv-error-color': '#c97c6d',
174
+ '--tv-border-color': '#e0d5c7',
175
+ '--tv-divider-color': '#f0e8de',
176
+ '--tv-shadow-color': 'rgba(92, 77, 61, 0.1)',
177
+ '--tv-chart-color-1': '#d4a574',
178
+ '--tv-chart-color-2': '#8db78e',
179
+ '--tv-chart-color-3': '#e6c87a',
180
+ '--tv-chart-color-4': '#c97c6d',
181
+ '--tv-chart-color-5': '#9ab5a8',
182
+ '--tv-chart-color-6': '#c9b8d4',
183
+ '--tv-chart-color-7': '#a8c4d4',
184
+ '--tv-chart-color-8': '#d4c49a',
185
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
186
+ '--tv-font-size': '14px',
187
+ '--tv-font-size-small': '12px',
188
+ '--tv-font-size-large': '16px',
189
+ '--tv-border-radius': '4px',
190
+ '--tv-border-radius-small': '2px',
191
+ '--tv-transition-duration': '0.3s',
192
+ },
193
+ },
194
+ macarons: {
195
+ name: 'macarons',
196
+ type: 'custom',
197
+ isDark: false,
198
+ variables: {
199
+ '--tv-bg-color': '#fefcf9',
200
+ '--tv-bg-color-secondary': '#f9f6f2',
201
+ '--tv-text-color': '#505050',
202
+ '--tv-text-color-secondary': '#757575',
203
+ '--tv-primary-color': '#60acf2',
204
+ '--tv-primary-color-hover': '#4d9de0',
205
+ '--tv-primary-color-active': '#3d8bd0',
206
+ '--tv-success-color': '#62d17a',
207
+ '--tv-warning-color': '#f7c752',
208
+ '--tv-error-color': '#f4645a',
209
+ '--tv-border-color': '#e8e4e0',
210
+ '--tv-divider-color': '#f0ece8',
211
+ '--tv-shadow-color': 'rgba(80, 80, 80, 0.08)',
212
+ '--tv-chart-color-1': '#60acf2',
213
+ '--tv-chart-color-2': '#62d17a',
214
+ '--tv-chart-color-3': '#f7c752',
215
+ '--tv-chart-color-4': '#f4645a',
216
+ '--tv-chart-color-5': '#95d9f2',
217
+ '--tv-chart-color-6': '#a8e6cf',
218
+ '--tv-chart-color-7': '#ffd3b6',
219
+ '--tv-chart-color-8': '#ffaaa5',
220
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
221
+ '--tv-font-size': '14px',
222
+ '--tv-font-size-small': '12px',
223
+ '--tv-font-size-large': '16px',
224
+ '--tv-border-radius': '12px',
225
+ '--tv-border-radius-small': '8px',
226
+ '--tv-transition-duration': '0.3s',
227
+ },
228
+ },
229
+ infographic: {
230
+ name: 'infographic',
231
+ type: 'custom',
232
+ isDark: false,
233
+ variables: {
234
+ '--tv-bg-color': '#ffffff',
235
+ '--tv-bg-color-secondary': '#f5f7fa',
236
+ '--tv-text-color': '#1a1a1a',
237
+ '--tv-text-color-secondary': '#666666',
238
+ '--tv-primary-color': '#277 ace',
239
+ '--tv-primary-color-hover': '#3a8ee6',
240
+ '--tv-primary-color-active': '#146bb3',
241
+ '--tv-success-color': '#2fc25b',
242
+ '--tv-warning-color': '#fbd438',
243
+ '--tv-error-color': '#e8352e',
244
+ '--tv-border-color': '#e0e6ed',
245
+ '--tv-divider-color': '#f0f2f5',
246
+ '--tv-shadow-color': 'rgba(26, 26, 26, 0.06)',
247
+ '--tv-chart-color-1': '#277ace',
248
+ '--tv-chart-color-2': '#31cce8',
249
+ '--tv-chart-color-3': '#23d3a2',
250
+ '--tv-chart-color-4': '#fbd438',
251
+ '--tv-chart-color-5': '#f87f50',
252
+ '--tv-chart-color-6': '#e8352e',
253
+ '--tv-chart-color-7': '#b02ad3',
254
+ '--tv-chart-color-8': '#6475d4',
255
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
256
+ '--tv-font-size': '14px',
257
+ '--tv-font-size-small': '12px',
258
+ '--tv-font-size-large': '16px',
259
+ '--tv-border-radius': '2px',
260
+ '--tv-border-radius-small': '1px',
261
+ '--tv-transition-duration': '0.2s',
262
+ },
263
+ },
264
+ helianthus: {
265
+ name: 'helianthus',
266
+ type: 'custom',
267
+ isDark: false,
268
+ variables: {
269
+ '--tv-bg-color': '#fffbf5',
270
+ '--tv-bg-color-secondary': '#fef7f0',
271
+ '--tv-text-color': '#5c4d3d',
272
+ '--tv-text-color-secondary': '#8b7355',
273
+ '--tv-primary-color': '#f5c242',
274
+ '--tv-primary-color-hover': '#e6b53e',
275
+ '--tv-primary-color-active': '#d4a435',
276
+ '--tv-success-color': '#7ec890',
277
+ '--tv-warning-color': '#f5a623',
278
+ '--tv-error-color': '#e74c3c',
279
+ '--tv-border-color': '#e8dccf',
280
+ '--tv-divider-color': '#f0e8de',
281
+ '--tv-shadow-color': 'rgba(92, 77, 61, 0.08)',
282
+ '--tv-chart-color-1': '#f5c242',
283
+ '--tv-chart-color-2': '#7ec890',
284
+ '--tv-chart-color-3': '#5eb8d9',
285
+ '--tv-chart-color-4': '#e74c3c',
286
+ '--tv-chart-color-5': '#9b7ed4',
287
+ '--tv-chart-color-6': '#f5a623',
288
+ '--tv-chart-color-7': '#6dd3ce',
289
+ '--tv-chart-color-8': '#d4778b',
290
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
291
+ '--tv-font-size': '14px',
292
+ '--tv-font-size-small': '12px',
293
+ '--tv-font-size-large': '16px',
294
+ '--tv-border-radius': '6px',
295
+ '--tv-border-radius-small': '4px',
296
+ '--tv-transition-duration': '0.3s',
297
+ },
298
+ },
299
+ blue: {
300
+ name: 'blue',
301
+ type: 'custom',
302
+ isDark: false,
303
+ variables: {
304
+ '--tv-bg-color': '#f0f7ff',
305
+ '--tv-bg-color-secondary': '#e6f0ff',
306
+ '--tv-text-color': '#1a3a5c',
307
+ '--tv-text-color-secondary': '#4a6a8c',
308
+ '--tv-primary-color': '#1890ff',
309
+ '--tv-primary-color-hover': '#40a9ff',
310
+ '--tv-primary-color-active': '#096dd9',
311
+ '--tv-success-color': '#52c41a',
312
+ '--tv-warning-color': '#faad14',
313
+ '--tv-error-color': '#ff4d4f',
314
+ '--tv-border-color': '#bfd9f2',
315
+ '--tv-divider-color': '#d9e8fc',
316
+ '--tv-shadow-color': 'rgba(24, 144, 255, 0.15)',
317
+ '--tv-chart-color-1': '#1890ff',
318
+ '--tv-chart-color-2': '#91cc75',
319
+ '--tv-chart-color-3': '#fac858',
320
+ '--tv-chart-color-4': '#ee6666',
321
+ '--tv-chart-color-5': '#73c0de',
322
+ '--tv-chart-color-6': '#3ba272',
323
+ '--tv-chart-color-7': '#fc8452',
324
+ '--tv-chart-color-8': '#9a60b4',
325
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
326
+ '--tv-font-size': '14px',
327
+ '--tv-font-size-small': '12px',
328
+ '--tv-font-size-large': '16px',
329
+ '--tv-border-radius': '4px',
330
+ '--tv-border-radius-small': '2px',
331
+ '--tv-transition-duration': '0.3s',
332
+ },
333
+ },
334
+ red: {
335
+ name: 'red',
336
+ type: 'custom',
337
+ isDark: false,
338
+ variables: {
339
+ '--tv-bg-color': '#fff5f5',
340
+ '--tv-bg-color-secondary': '#ffe6e6',
341
+ '--tv-text-color': '#5c1a1a',
342
+ '--tv-text-color-secondary': '#8c4a4a',
343
+ '--tv-primary-color': '#ff4d4f',
344
+ '--tv-primary-color-hover': '#ff7875',
345
+ '--tv-primary-color-active': '#d9363e',
346
+ '--tv-success-color': '#52c41a',
347
+ '--tv-warning-color': '#faad14',
348
+ '--tv-error-color': '#ff4d4f',
349
+ '--tv-border-color': '#f2bfbf',
350
+ '--tv-divider-color': '#fcd9d9',
351
+ '--tv-shadow-color': 'rgba(255, 77, 79, 0.15)',
352
+ '--tv-chart-color-1': '#ff4d4f',
353
+ '--tv-chart-color-2': '#ffd700',
354
+ '--tv-chart-color-3': '#ff7c4d',
355
+ '--tv-chart-color-4': '#9c4dff',
356
+ '--tv-chart-color-5': '#00d0ff',
357
+ '--tv-chart-color-6': '#52c41a',
358
+ '--tv-chart-color-7': '#faad14',
359
+ '--tv-chart-color-8': '#8b5cf6',
360
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
361
+ '--tv-font-size': '14px',
362
+ '--tv-font-size-small': '12px',
363
+ '--tv-font-size-large': '16px',
364
+ '--tv-border-radius': '4px',
365
+ '--tv-border-radius-small': '2px',
366
+ '--tv-transition-duration': '0.3s',
367
+ },
368
+ },
369
+ green: {
370
+ name: 'green',
371
+ type: 'custom',
372
+ isDark: false,
373
+ variables: {
374
+ '--tv-bg-color': '#f5fff5',
375
+ '--tv-bg-color-secondary': '#e6ffe6',
376
+ '--tv-text-color': '#1a3d1a',
377
+ '--tv-text-color-secondary': '#4a6c4a',
378
+ '--tv-primary-color': '#52c41a',
379
+ '--tv-primary-color-hover': '#73d13d',
380
+ '--tv-primary-color-active': '#389e0d',
381
+ '--tv-success-color': '#52c41a',
382
+ '--tv-warning-color': '#faad14',
383
+ '--tv-error-color': '#ff4d4f',
384
+ '--tv-border-color': '#bff2bf',
385
+ '--tv-divider-color': '#d9fcd9',
386
+ '--tv-shadow-color': 'rgba(82, 196, 26, 0.15)',
387
+ '--tv-chart-color-1': '#52c41a',
388
+ '--tv-chart-color-2': '#1890ff',
389
+ '--tv-chart-color-3': '#faad14',
390
+ '--tv-chart-color-4': '#ff4d4f',
391
+ '--tv-chart-color-5': '#722ed1',
392
+ '--tv-chart-color-6': '#13c2c2',
393
+ '--tv-chart-color-7': '#fa8c16',
394
+ '--tv-chart-color-8': '#eb2f96',
395
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
396
+ '--tv-font-size': '14px',
397
+ '--tv-font-size-small': '12px',
398
+ '--tv-font-size-large': '16px',
399
+ '--tv-border-radius': '4px',
400
+ '--tv-border-radius-small': '2px',
401
+ '--tv-transition-duration': '0.3s',
402
+ },
403
+ },
404
+ purple: {
405
+ name: 'purple',
406
+ type: 'custom',
407
+ isDark: false,
408
+ variables: {
409
+ '--tv-bg-color': '#faf5ff',
410
+ '--tv-bg-color-secondary': '#f0e6ff',
411
+ '--tv-text-color': '#3d1a5c',
412
+ '--tv-text-color-secondary': '#6a4a8c',
413
+ '--tv-primary-color': '#722ed1',
414
+ '--tv-primary-color-hover': '#9254de',
415
+ '--tv-primary-color-active': '#531dab',
416
+ '--tv-success-color': '#52c41a',
417
+ '--tv-warning-color': '#faad14',
418
+ '--tv-error-color': '#ff4d4f',
419
+ '--tv-border-color': '#d9bff2',
420
+ '--tv-divider-color': '#ecd9fc',
421
+ '--tv-shadow-color': 'rgba(114, 46, 209, 0.15)',
422
+ '--tv-chart-color-1': '#722ed1',
423
+ '--tv-chart-color-2': '#eb2f96',
424
+ '--tv-chart-color-3': '#1890ff',
425
+ '--tv-chart-color-4': '#52c41a',
426
+ '--tv-chart-color-5': '#faad14',
427
+ '--tv-chart-color-6': '#ff4d4f',
428
+ '--tv-chart-color-7': '#13c2c2',
429
+ '--tv-chart-color-8': '#fa8c16',
430
+ '--tv-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
431
+ '--tv-font-size': '14px',
432
+ '--tv-font-size-small': '12px',
433
+ '--tv-font-size-large': '16px',
434
+ '--tv-border-radius': '4px',
435
+ '--tv-border-radius-small': '2px',
436
+ '--tv-transition-duration': '0.3s',
437
+ },
438
+ },
439
+ };
440
+
441
+ // ============================================================================
442
+ // 主题管理器
443
+ // ============================================================================
444
+
445
+ class ThemeManager {
446
+ private static instance: ThemeManager | null = null;
447
+ private currentTheme: ThemeConfig = PRESET_THEMES.default;
448
+ private listeners: Set<(theme: ThemeConfig) => void> = new Set();
449
+ private cssVarPrefix = '--tv-';
450
+
451
+ private constructor() {}
452
+
453
+ /**
454
+ * 获取单例实例
455
+ */
456
+ public static getInstance(): ThemeManager {
457
+ if (!ThemeManager.instance) {
458
+ ThemeManager.instance = new ThemeManager();
459
+ }
460
+ return ThemeManager.instance;
461
+ }
462
+
463
+ /**
464
+ * 获取当前主题
465
+ */
466
+ public getCurrentTheme(): ThemeConfig {
467
+ return this.currentTheme;
468
+ }
469
+
470
+ /**
471
+ * 获取预设主题
472
+ */
473
+ public getPresetTheme(name: PresetThemeName): ThemeConfig | undefined {
474
+ return PRESET_THEMES[name];
475
+ }
476
+
477
+ /**
478
+ * 获取所有预设主题名称
479
+ */
480
+ public getPresetThemeNames(): PresetThemeName[] {
481
+ return Object.keys(PRESET_THEMES) as PresetThemeName[];
482
+ }
483
+
484
+ /**
485
+ * 设置主题
486
+ */
487
+ public setTheme(name: PresetThemeName | ThemeConfig): void {
488
+ const theme = typeof name === 'string' ? PRESET_THEMES[name] : name;
489
+ if (theme) {
490
+ this.currentTheme = theme;
491
+ this.applyThemeVariables(theme);
492
+ this.notifyListeners();
493
+ }
494
+ }
495
+
496
+ /**
497
+ * 应用主题变量到 CSS
498
+ */
499
+ public applyThemeVariables(theme: ThemeConfig): void {
500
+ if (typeof document === 'undefined') return;
501
+
502
+ const root = document.documentElement;
503
+ const variables = theme.variables;
504
+
505
+ Object.entries(variables).forEach(([key, value]) => {
506
+ root.style.setProperty(key, value);
507
+ });
508
+
509
+ // 设置 data 属性用于 JavaScript 检测
510
+ root.setAttribute('data-theme', theme.name);
511
+ root.setAttribute('data-theme-type', theme.type);
512
+
513
+ if (theme.isDark) {
514
+ root.setAttribute('data-theme-dark', 'true');
515
+ } else {
516
+ root.removeAttribute('data-theme-dark');
517
+ }
518
+ }
519
+
520
+ /**
521
+ * 应用 ECharts 主题
522
+ */
523
+ public getEChartsTheme(): Record<string, unknown> {
524
+ const theme = this.currentTheme;
525
+ return {
526
+ color: [
527
+ theme.variables['--tv-chart-color-1'],
528
+ theme.variables['--tv-chart-color-2'],
529
+ theme.variables['--tv-chart-color-3'],
530
+ theme.variables['--tv-chart-color-4'],
531
+ theme.variables['--tv-chart-color-5'],
532
+ theme.variables['--tv-chart-color-6'],
533
+ theme.variables['--tv-chart-color-7'],
534
+ theme.variables['--tv-chart-color-8'],
535
+ ],
536
+ backgroundColor: theme.variables['--tv-bg-color'],
537
+ textStyle: {
538
+ color: theme.variables['--tv-text-color'],
539
+ fontFamily: theme.variables['--tv-font-family'],
540
+ },
541
+ };
542
+ }
543
+
544
+ /**
545
+ * 切换暗色/亮色主题
546
+ */
547
+ public toggleDarkMode(): void {
548
+ if (this.currentTheme.type === 'dark') {
549
+ this.setTheme('default');
550
+ } else {
551
+ this.setTheme('dark');
552
+ }
553
+ }
554
+
555
+ /**
556
+ * 判断是否为暗色主题
557
+ */
558
+ public isDarkMode(): boolean {
559
+ return this.currentTheme.isDark || this.currentTheme.type === 'dark';
560
+ }
561
+
562
+ /**
563
+ * 注册主题变更监听器
564
+ */
565
+ public onThemeChange(listener: (theme: ThemeConfig) => void): () => void {
566
+ this.listeners.add(listener);
567
+ return () => this.listeners.delete(listener);
568
+ }
569
+
570
+ /**
571
+ * 通知所有监听器
572
+ */
573
+ private notifyListeners(): void {
574
+ this.listeners.forEach((listener) => {
575
+ try {
576
+ listener(this.currentTheme);
577
+ } catch (error) {
578
+ console.error('[TaroViz] Theme change listener error:', error);
579
+ }
580
+ });
581
+ }
582
+
583
+ /**
584
+ * 创建自定义主题
585
+ */
586
+ public createCustomTheme(options: Partial<ThemeVariables>, name = 'custom'): ThemeConfig {
587
+ return {
588
+ name,
589
+ type: 'custom',
590
+ isDark: false,
591
+ variables: {
592
+ ...PRESET_THEMES.default.variables,
593
+ ...options,
594
+ },
595
+ };
596
+ }
597
+
598
+ /**
599
+ * 导出主题变量为 CSS 字符串
600
+ */
601
+ public exportThemeAsCSS(theme?: ThemeConfig): string {
602
+ const targetTheme = theme || this.currentTheme;
603
+ const variables = targetTheme.variables;
604
+
605
+ return `:root[data-theme="${targetTheme.name}"] {\n${Object.entries(variables)
606
+ .map(([key, value]) => ` ${key}: ${value};`)
607
+ .join('\n')}\n}`;
608
+ }
609
+
610
+ /**
611
+ * 导出主题变量为 JSON
612
+ */
613
+ public exportThemeAsJSON(theme?: ThemeConfig): string {
614
+ const targetTheme = theme || this.currentTheme;
615
+ return JSON.stringify(targetTheme, null, 2);
616
+ }
617
+ }
618
+
619
+ // 导出单例实例
620
+ export const themeManager = ThemeManager.getInstance();
621
+
622
+ // 导出类型
623
+ export type { ThemeConfig, ThemeVariables, PresetThemeName };
624
+
625
+ // 导出预设主题
626
+ export { PRESET_THEMES };
627
+
628
+ export default themeManager;