kimu-core 0.4.1 → 0.4.2
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/.editorconfig +116 -30
- package/.gitattributes +81 -11
- package/.github/FUNDING.yml +8 -8
- package/.github/kimu-copilot-instructions.md +3779 -3779
- package/.github/workflows/deploy-demo.yml +39 -39
- package/.nvmrc +1 -0
- package/.prettierignore +44 -0
- package/.prettierrc +16 -0
- package/FUNDING.md +31 -31
- package/icon.svg +10 -10
- package/package.json +9 -2
- package/scripts/minify-css-assets.js +82 -82
- package/src/core/index.ts +47 -47
- package/src/core/kimu-global-styles.ts +136 -136
- package/src/core/kimu-reactive.ts +196 -196
- package/src/modules-repository/api-axios/CHANGELOG.md +48 -48
- package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -178
- package/src/modules-repository/api-axios/README.md +304 -304
- package/src/modules-repository/api-axios/api-axios-service.ts +355 -355
- package/src/modules-repository/api-axios/examples.ts +293 -293
- package/src/modules-repository/api-axios/index.ts +19 -19
- package/src/modules-repository/api-axios/interfaces.ts +71 -71
- package/src/modules-repository/api-axios/module.ts +41 -41
- package/src/modules-repository/api-core/CHANGELOG.md +42 -42
- package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -192
- package/src/modules-repository/api-core/README.md +435 -435
- package/src/modules-repository/api-core/api-core-service.ts +289 -289
- package/src/modules-repository/api-core/examples.ts +432 -432
- package/src/modules-repository/api-core/index.ts +8 -8
- package/src/modules-repository/api-core/interfaces.ts +83 -83
- package/src/modules-repository/api-core/module.ts +30 -30
- package/src/modules-repository/event-bus/README.md +273 -273
- package/src/modules-repository/event-bus/event-bus-service.ts +176 -176
- package/src/modules-repository/event-bus/module.ts +30 -30
- package/src/modules-repository/notification/README.md +423 -423
- package/src/modules-repository/notification/module.ts +30 -30
- package/src/modules-repository/notification/notification-service.ts +436 -436
- package/src/modules-repository/router/README.it.md +61 -10
- package/src/modules-repository/router/README.md +61 -10
- package/src/modules-repository/router/router-config.ts.example +61 -0
- package/src/modules-repository/router/router.ts +18 -0
- package/src/modules-repository/state/README.md +409 -409
- package/src/modules-repository/state/module.ts +30 -30
- package/src/modules-repository/state/state-service.ts +296 -296
- package/src/modules-repository/theme/README.md +311 -267
- package/src/modules-repository/theme/module.ts +30 -30
- package/src/modules-repository/theme/pre-build.js +40 -40
- package/src/modules-repository/theme/theme-service.ts +411 -389
- package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -78
- package/src/modules-repository/theme/themes/theme-cozy.css +111 -111
- package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -150
- package/src/modules-repository/theme/themes/theme-dark.css +79 -79
- package/src/modules-repository/theme/themes/theme-forest.css +171 -171
- package/src/modules-repository/theme/themes/theme-gold.css +100 -100
- package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -126
- package/src/modules-repository/theme/themes/theme-lava.css +101 -101
- package/src/modules-repository/theme/themes/theme-lavender.css +90 -90
- package/src/modules-repository/theme/themes/theme-light.css +79 -79
- package/src/modules-repository/theme/themes/theme-matrix.css +103 -103
- package/src/modules-repository/theme/themes/theme-midnight.css +81 -81
- package/src/modules-repository/theme/themes/theme-nord.css +94 -94
- package/src/modules-repository/theme/themes/theme-ocean.css +84 -84
- package/src/modules-repository/theme/themes/theme-retro80s.css +343 -343
- package/src/modules-repository/theme/themes/theme-sunset.css +62 -62
- package/src/modules-repository/theme/themes-config-default.json +19 -0
- package/src/modules-repository/theme/themes-config.d.ts +27 -27
- package/src/modules-repository/theme/{themes-config.json → themes-config.json.example} +223 -213
|
@@ -1,389 +1,411 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Service for KIMU-Core
|
|
3
|
-
*
|
|
4
|
-
* Provides theme management with CSS-based themes, custom CSS variables,
|
|
5
|
-
* persistent storage, and system preference detection.
|
|
6
|
-
*
|
|
7
|
-
* @module ThemeService
|
|
8
|
-
* @version 2.0.0
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { KimuGlobalStyles } from '../../core/kimu-global-styles';
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
*
|
|
341
|
-
*/
|
|
342
|
-
private
|
|
343
|
-
if (!
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Theme Service for KIMU-Core
|
|
3
|
+
*
|
|
4
|
+
* Provides theme management with CSS-based themes, custom CSS variables,
|
|
5
|
+
* persistent storage, and system preference detection.
|
|
6
|
+
*
|
|
7
|
+
* @module ThemeService
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { KimuGlobalStyles } from '../../core/kimu-global-styles';
|
|
12
|
+
import themesConfigDefault from './themes-config-default.json';
|
|
13
|
+
|
|
14
|
+
// Try to load themes configuration from user's config folder
|
|
15
|
+
// If not found, fallback to default configuration
|
|
16
|
+
let themesConfig: any;
|
|
17
|
+
try {
|
|
18
|
+
// User should copy themes-config.json.example to src/config/theme/themes-config.json
|
|
19
|
+
// This path is relative to the installed module location: src/modules/theme/
|
|
20
|
+
themesConfig = require('../../config/theme/themes-config.json');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.warn(
|
|
23
|
+
'\n⚠️ KIMU Theme Module: No custom themes configuration found!\n\n' +
|
|
24
|
+
'Expected location: src/config/theme/themes-config.json\n\n' +
|
|
25
|
+
'To create a custom configuration:\n' +
|
|
26
|
+
'1. Create config folder: mkdir -p src/config/theme\n' +
|
|
27
|
+
'2. Copy the example: cp src/modules/theme/themes-config.json.example src/config/theme/themes-config.json\n' +
|
|
28
|
+
'3. Edit src/config/theme/themes-config.json to customize your themes\n\n' +
|
|
29
|
+
'Using default themes (light, dark) as fallback.\n'
|
|
30
|
+
);
|
|
31
|
+
// Use default configuration as fallback
|
|
32
|
+
themesConfig = themesConfigDefault;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ThemeMode can be any theme name loaded from config, plus 'auto' for system preference
|
|
36
|
+
export type ThemeMode = 'auto' | string;
|
|
37
|
+
|
|
38
|
+
export interface ThemeColors {
|
|
39
|
+
background: string;
|
|
40
|
+
backgroundEnd: string;
|
|
41
|
+
text: string;
|
|
42
|
+
border: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CssTheme {
|
|
46
|
+
name: string;
|
|
47
|
+
mode: 'light' | 'dark';
|
|
48
|
+
cssPath: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
icon?: string;
|
|
51
|
+
colors?: ThemeColors;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ThemeChangeListener = (themeName: string, mode: ThemeMode) => void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ThemeService - Centralized theme management with CSS-based themes
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import { themeService } from './modules/theme/theme-service';
|
|
62
|
+
*
|
|
63
|
+
* // Set theme mode
|
|
64
|
+
* themeService.setMode('dark');
|
|
65
|
+
* themeService.setMode('ocean');
|
|
66
|
+
* themeService.setMode('cozy');
|
|
67
|
+
*
|
|
68
|
+
* // Register a custom CSS theme
|
|
69
|
+
* themeService.registerCssTheme({
|
|
70
|
+
* name: 'custom',
|
|
71
|
+
* mode: 'dark',
|
|
72
|
+
* cssPath: '/themes/custom.css'
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // Listen to theme changes
|
|
76
|
+
* themeService.onChange((themeName, mode) => {
|
|
77
|
+
* console.log('Theme changed:', themeName);
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export class ThemeService {
|
|
82
|
+
private currentMode: ThemeMode = 'auto';
|
|
83
|
+
private currentThemeName: string = 'light';
|
|
84
|
+
private listeners: Set<ThemeChangeListener> = new Set();
|
|
85
|
+
private storageKey = 'kimu-theme-mode';
|
|
86
|
+
private systemPreferenceQuery?: MediaQueryList;
|
|
87
|
+
private cssThemes: Map<string, CssTheme> = new Map();
|
|
88
|
+
private debugMode: boolean = true; // Enable debug by default for development
|
|
89
|
+
private activeThemeLink?: HTMLLinkElement;
|
|
90
|
+
|
|
91
|
+
constructor() {
|
|
92
|
+
console.log('[Theme] ThemeService initializing...');
|
|
93
|
+
this.initDefaultThemes();
|
|
94
|
+
this.loadFromStorage();
|
|
95
|
+
this.initSystemPreference();
|
|
96
|
+
this.applyTheme();
|
|
97
|
+
console.log('[Theme] ThemeService initialized. Current mode:', this.currentMode);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Initialize themes from configuration file (user config or default fallback)
|
|
102
|
+
*/
|
|
103
|
+
private initDefaultThemes(): void {
|
|
104
|
+
// Theme CSS files are in public/themes/ folder
|
|
105
|
+
// In development: Vite serves public/ from root → /themes/
|
|
106
|
+
// In production: Vite copies public/ to dist/ → /dist/themes/
|
|
107
|
+
const basePath = this.getBasePath();
|
|
108
|
+
|
|
109
|
+
const themesPath = basePath === '/'
|
|
110
|
+
? '/themes/' // Development: public/themes/ → /themes/
|
|
111
|
+
: `${basePath}themes/`; // Production: public/themes/ → /dist/themes/
|
|
112
|
+
|
|
113
|
+
// Load themes from configuration (either user config or default fallback)
|
|
114
|
+
if (themesConfig?.themes) {
|
|
115
|
+
themesConfig.themes.forEach((theme: any) => {
|
|
116
|
+
this.cssThemes.set(theme.name, {
|
|
117
|
+
name: theme.name,
|
|
118
|
+
mode: theme.mode as 'light' | 'dark',
|
|
119
|
+
cssPath: `${themesPath}${theme.cssPath}`,
|
|
120
|
+
description: theme.description,
|
|
121
|
+
icon: theme.icon,
|
|
122
|
+
colors: theme.colors
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (this.debugMode) {
|
|
127
|
+
console.log('[Theme] Initialized themes from config with base path:', basePath);
|
|
128
|
+
console.log('[Theme] Themes path:', themesPath);
|
|
129
|
+
console.log('[Theme] Loaded themes:', Array.from(this.cssThemes.keys()));
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
console.error('[Theme] No themes configuration available - this should not happen!');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get base path for theme files
|
|
138
|
+
*/
|
|
139
|
+
private getBasePath(): string {
|
|
140
|
+
// In development with Vite, files are served from root
|
|
141
|
+
// In production, files are in /dist/
|
|
142
|
+
if (typeof window !== 'undefined') {
|
|
143
|
+
// Check if we're running in production (built files)
|
|
144
|
+
const scripts = document.getElementsByTagName('script');
|
|
145
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
146
|
+
const src = scripts[i].src;
|
|
147
|
+
// If we find a script in /assets/ or with hash, we're in production
|
|
148
|
+
if (src.includes('/assets/') || src.match(/\-[a-zA-Z0-9]{8}\.(js|css)$/)) {
|
|
149
|
+
return '/dist/';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Development mode (Vite serves from root)
|
|
154
|
+
return '/';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Enable or disable debug mode
|
|
159
|
+
*/
|
|
160
|
+
setDebugMode(enabled: boolean): void {
|
|
161
|
+
this.debugMode = enabled;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Set theme mode
|
|
166
|
+
*/
|
|
167
|
+
setMode(mode: ThemeMode, persist: boolean = true): void {
|
|
168
|
+
if (this.debugMode) {
|
|
169
|
+
console.log('[Theme] Setting mode:', mode);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.currentMode = mode;
|
|
173
|
+
|
|
174
|
+
if (persist) {
|
|
175
|
+
this.saveToStorage();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// updateTheme() will handle loading the CSS and notifying listeners
|
|
179
|
+
this.updateTheme();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get current theme mode
|
|
184
|
+
*/
|
|
185
|
+
getMode(): ThemeMode {
|
|
186
|
+
return this.currentMode;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get current theme name
|
|
191
|
+
*/
|
|
192
|
+
getCurrentThemeName(): string {
|
|
193
|
+
return this.currentThemeName;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get effective mode (resolves 'auto' to actual theme)
|
|
198
|
+
*/
|
|
199
|
+
getEffectiveMode(): string {
|
|
200
|
+
if (this.currentMode === 'auto') {
|
|
201
|
+
const systemPref = this.getSystemPreference();
|
|
202
|
+
return systemPref;
|
|
203
|
+
}
|
|
204
|
+
return this.currentMode;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Toggle between light and dark mode
|
|
209
|
+
*/
|
|
210
|
+
toggle(): void {
|
|
211
|
+
const effectiveMode = this.getEffectiveMode();
|
|
212
|
+
const currentTheme = this.cssThemes.get(effectiveMode);
|
|
213
|
+
|
|
214
|
+
if (!currentTheme) {
|
|
215
|
+
this.setMode('light');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Toggle between light and dark modes
|
|
220
|
+
if (currentTheme.mode === 'light') {
|
|
221
|
+
this.setMode('dark');
|
|
222
|
+
} else {
|
|
223
|
+
this.setMode('light');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Register a custom CSS theme
|
|
229
|
+
*/
|
|
230
|
+
registerCssTheme(theme: CssTheme): void {
|
|
231
|
+
this.cssThemes.set(theme.name, theme);
|
|
232
|
+
|
|
233
|
+
if (this.debugMode) {
|
|
234
|
+
console.log('[Theme] Registered CSS theme:', theme.name);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get all available theme names
|
|
240
|
+
*/
|
|
241
|
+
getAvailableThemes(): string[] {
|
|
242
|
+
return Array.from(this.cssThemes.keys());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get theme info by name
|
|
247
|
+
*/
|
|
248
|
+
getThemeInfo(name: string): CssTheme | undefined {
|
|
249
|
+
return this.cssThemes.get(name);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Listen to theme changes
|
|
254
|
+
*/
|
|
255
|
+
onChange(callback: ThemeChangeListener): void {
|
|
256
|
+
this.listeners.add(callback);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Remove theme change listener
|
|
261
|
+
*/
|
|
262
|
+
offChange(callback: ThemeChangeListener): void {
|
|
263
|
+
this.listeners.delete(callback);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Update theme based on current mode
|
|
268
|
+
*/
|
|
269
|
+
private updateTheme(): void {
|
|
270
|
+
const effectiveMode = this.getEffectiveMode();
|
|
271
|
+
const theme = this.cssThemes.get(effectiveMode);
|
|
272
|
+
|
|
273
|
+
if (!theme) {
|
|
274
|
+
console.error(`[Theme] Theme "${effectiveMode}" not found`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.currentThemeName = theme.name;
|
|
279
|
+
this.loadCssTheme(theme);
|
|
280
|
+
|
|
281
|
+
// Notify listeners after theme is loaded
|
|
282
|
+
this.notifyListeners();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Load CSS theme file
|
|
287
|
+
*/
|
|
288
|
+
private loadCssTheme(theme: CssTheme): void {
|
|
289
|
+
// Remove existing theme link from main document
|
|
290
|
+
if (this.activeThemeLink) {
|
|
291
|
+
this.activeThemeLink.remove();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Create new link element for main document
|
|
295
|
+
const link = document.createElement('link');
|
|
296
|
+
link.rel = 'stylesheet';
|
|
297
|
+
link.href = theme.cssPath;
|
|
298
|
+
link.id = 'kimu-theme';
|
|
299
|
+
link.setAttribute('data-theme', theme.name);
|
|
300
|
+
|
|
301
|
+
// Wait for CSS to load before applying (optional, for smoother transitions)
|
|
302
|
+
link.onload = () => {
|
|
303
|
+
if (this.debugMode) {
|
|
304
|
+
console.log('[Theme] CSS theme loaded:', theme.name, theme.cssPath);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
link.onerror = () => {
|
|
309
|
+
console.error('[Theme] Failed to load CSS theme:', theme.name, theme.cssPath);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Add to document head
|
|
313
|
+
document.head.appendChild(link);
|
|
314
|
+
this.activeThemeLink = link;
|
|
315
|
+
|
|
316
|
+
// Register theme CSS as global style for all extensions
|
|
317
|
+
// This will make the theme available in all shadow roots
|
|
318
|
+
KimuGlobalStyles.registerGlobalStyle('kimu-theme-active', theme.cssPath);
|
|
319
|
+
|
|
320
|
+
// Update theme in all currently loaded extensions
|
|
321
|
+
// This ensures that existing extensions get the new theme immediately
|
|
322
|
+
KimuGlobalStyles.updateStyleInAllExtensions('kimu-theme-active').catch(error => {
|
|
323
|
+
console.error('[Theme] Failed to update theme in extensions:', error);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (this.debugMode) {
|
|
327
|
+
console.log('[Theme] Loading CSS theme:', theme.name, theme.cssPath);
|
|
328
|
+
console.log('[Theme] Registered theme as global style for all extensions');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Apply current theme
|
|
334
|
+
*/
|
|
335
|
+
private applyTheme(): void {
|
|
336
|
+
this.updateTheme();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Initialize system preference detection
|
|
341
|
+
*/
|
|
342
|
+
private initSystemPreference(): void {
|
|
343
|
+
if (typeof window === 'undefined' || !window.matchMedia) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.systemPreferenceQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
348
|
+
|
|
349
|
+
// Listen for system preference changes
|
|
350
|
+
this.systemPreferenceQuery.addEventListener('change', (e) => {
|
|
351
|
+
if (this.currentMode === 'auto') {
|
|
352
|
+
if (this.debugMode) {
|
|
353
|
+
console.log('[Theme] System preference changed:', e.matches ? 'dark' : 'light');
|
|
354
|
+
}
|
|
355
|
+
this.updateTheme();
|
|
356
|
+
this.notifyListeners();
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get system color scheme preference
|
|
363
|
+
*/
|
|
364
|
+
private getSystemPreference(): 'light' | 'dark' {
|
|
365
|
+
if (!this.systemPreferenceQuery) {
|
|
366
|
+
return 'light';
|
|
367
|
+
}
|
|
368
|
+
return this.systemPreferenceQuery.matches ? 'dark' : 'light';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Save theme mode to localStorage
|
|
373
|
+
*/
|
|
374
|
+
private saveToStorage(): void {
|
|
375
|
+
try {
|
|
376
|
+
localStorage.setItem(this.storageKey, this.currentMode);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error('[Theme] Failed to save to localStorage:', error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Load theme mode from localStorage
|
|
384
|
+
*/
|
|
385
|
+
private loadFromStorage(): void {
|
|
386
|
+
try {
|
|
387
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
388
|
+
if (stored) {
|
|
389
|
+
this.currentMode = stored as ThemeMode;
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error('[Theme] Failed to load from localStorage:', error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Notify all listeners of theme change
|
|
398
|
+
*/
|
|
399
|
+
private notifyListeners(): void {
|
|
400
|
+
this.listeners.forEach(listener => {
|
|
401
|
+
try {
|
|
402
|
+
listener(this.currentThemeName, this.currentMode);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('[Theme] Error in listener:', error);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Export singleton instance
|
|
411
|
+
export const themeService = new ThemeService();
|