@shohojdhara/atomix 0.3.5 → 0.3.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 (182) hide show
  1. package/README.md +101 -199
  2. package/atomix.config.ts +241 -0
  3. package/dist/atomix.css +260 -179
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +250 -179
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/charts.js +69 -166
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.js +184 -263
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js +55 -131
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +184 -263
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +1831 -1657
  16. package/dist/index.esm.js +4497 -4318
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +4510 -4328
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.min.js +1 -1
  21. package/dist/index.min.js.map +1 -1
  22. package/dist/theme.d.ts +1431 -1472
  23. package/dist/theme.js +4175 -4138
  24. package/dist/theme.js.map +1 -1
  25. package/package.json +6 -20
  26. package/src/components/Accordion/Accordion.stories.tsx +50 -17
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +128 -322
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +12 -5
  29. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
  31. package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
  32. package/src/components/Avatar/Avatar.stories.tsx +7 -0
  33. package/src/components/Badge/Badge.stories.tsx +91 -13
  34. package/src/components/Block/Block.stories.tsx +7 -23
  35. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
  36. package/src/components/Button/Button.stories.tsx +141 -22
  37. package/src/components/Button/Button.tsx +85 -167
  38. package/src/components/Button/ButtonGroup.stories.tsx +315 -0
  39. package/src/components/Button/ButtonGroup.tsx +67 -0
  40. package/src/components/Button/index.ts +2 -0
  41. package/src/components/Callout/Callout.stories.tsx +8 -6
  42. package/src/components/Card/Card.stories.tsx +82 -28
  43. package/src/components/Chart/AnimatedChart.tsx +0 -1
  44. package/src/components/Chart/AreaChart.tsx +0 -1
  45. package/src/components/Chart/BarChart.tsx +0 -1
  46. package/src/components/Chart/BubbleChart.tsx +0 -1
  47. package/src/components/Chart/CandlestickChart.tsx +0 -1
  48. package/src/components/Chart/Chart.stories.tsx +5 -7
  49. package/src/components/Chart/Chart.tsx +0 -16
  50. package/src/components/Chart/ChartRenderer.tsx +1 -1
  51. package/src/components/Chart/DonutChart.tsx +0 -1
  52. package/src/components/Chart/FunnelChart.tsx +0 -1
  53. package/src/components/Chart/GaugeChart.tsx +0 -1
  54. package/src/components/Chart/HeatmapChart.tsx +0 -1
  55. package/src/components/Chart/LineChart.tsx +0 -1
  56. package/src/components/Chart/MultiAxisChart.tsx +0 -1
  57. package/src/components/Chart/PieChart.tsx +0 -1
  58. package/src/components/Chart/RadarChart.tsx +0 -1
  59. package/src/components/Chart/ScatterChart.tsx +0 -1
  60. package/src/components/Chart/WaterfallChart.tsx +0 -1
  61. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
  62. package/src/components/DataTable/DataTable.stories.tsx +23 -16
  63. package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
  64. package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
  65. package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
  66. package/src/components/Footer/Footer.stories.tsx +8 -6
  67. package/src/components/Footer/FooterLink.tsx +9 -2
  68. package/src/components/Form/Checkbox.stories.tsx +7 -0
  69. package/src/components/Form/Form.stories.tsx +7 -0
  70. package/src/components/Form/FormGroup.stories.tsx +9 -1
  71. package/src/components/Form/Input.stories.tsx +69 -16
  72. package/src/components/Form/Radio.stories.tsx +9 -1
  73. package/src/components/Form/Select.stories.tsx +9 -1
  74. package/src/components/Form/Textarea.stories.tsx +10 -2
  75. package/src/components/Hero/Hero.stories.tsx +7 -0
  76. package/src/components/List/List.stories.tsx +7 -0
  77. package/src/components/Messages/Messages.stories.tsx +8 -7
  78. package/src/components/Modal/Modal.stories.tsx +17 -6
  79. package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
  80. package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
  81. package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
  82. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
  83. package/src/components/Pagination/Pagination.stories.tsx +188 -111
  84. package/src/components/Pagination/Pagination.tsx +83 -3
  85. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
  86. package/src/components/Popover/Popover.stories.tsx +191 -115
  87. package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
  88. package/src/components/Progress/Progress.stories.tsx +79 -49
  89. package/src/components/Rating/Rating.stories.tsx +109 -84
  90. package/src/components/River/River.stories.tsx +194 -114
  91. package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
  92. package/src/components/Slider/Slider.stories.tsx +7 -0
  93. package/src/components/Spinner/Spinner.stories.tsx +15 -11
  94. package/src/components/Steps/Steps.stories.tsx +132 -98
  95. package/src/components/Tabs/Tabs.stories.tsx +163 -112
  96. package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
  97. package/src/components/Todo/Todo.stories.tsx +38 -12
  98. package/src/components/Toggle/Toggle.stories.tsx +61 -28
  99. package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
  100. package/src/components/Upload/Upload.stories.tsx +122 -84
  101. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
  102. package/src/components/index.ts +1 -0
  103. package/src/lib/composables/useAtomixGlass.ts +9 -10
  104. package/src/lib/composables/useNavbar.ts +0 -10
  105. package/src/lib/config/loader.ts +4 -4
  106. package/src/lib/constants/components.ts +17 -0
  107. package/src/lib/hooks/useComponentCustomization.ts +1 -1
  108. package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
  109. package/src/lib/hooks/useThemeTokens.ts +105 -0
  110. package/src/lib/theme/README.md +174 -0
  111. package/src/lib/theme/adapters/index.ts +31 -0
  112. package/src/lib/theme/adapters/themeAdapter.ts +287 -0
  113. package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
  114. package/src/lib/theme/config/configLoader.ts +95 -0
  115. package/src/lib/theme/config/loader.ts +37 -54
  116. package/src/lib/theme/config/types.ts +2 -2
  117. package/src/lib/theme/config/validator.ts +15 -91
  118. package/src/lib/theme/{constants.ts → constants/constants.ts} +1 -19
  119. package/src/lib/theme/constants/index.ts +8 -0
  120. package/src/lib/theme/core/ThemeRegistry.ts +75 -266
  121. package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
  122. package/src/lib/theme/core/composeTheme.ts +105 -0
  123. package/src/lib/theme/core/createTheme.ts +108 -0
  124. package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +12 -8
  125. package/src/lib/theme/core/index.ts +19 -19
  126. package/src/lib/theme/devtools/Comparator.tsx +346 -22
  127. package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
  128. package/src/lib/theme/devtools/Inspector.tsx +335 -51
  129. package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
  130. package/src/lib/theme/devtools/Preview.tsx +471 -221
  131. package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
  132. package/src/lib/theme/devtools/index.ts +14 -4
  133. package/src/lib/theme/devtools/useHistory.ts +130 -0
  134. package/src/lib/theme/{errors.ts → errors/errors.ts} +1 -1
  135. package/src/lib/theme/errors/index.ts +12 -0
  136. package/src/lib/theme/generators/cssFile.ts +79 -0
  137. package/src/lib/theme/generators/generateCSS.ts +89 -0
  138. package/src/lib/theme/generators/generateCSSNested.ts +130 -0
  139. package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
  140. package/src/lib/theme/generators/index.ts +25 -0
  141. package/src/lib/theme/i18n/rtl.ts +5 -6
  142. package/src/lib/theme/index.ts +149 -19
  143. package/src/lib/theme/runtime/ThemeApplicator.ts +53 -112
  144. package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
  145. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +5 -5
  146. package/src/lib/theme/runtime/ThemeProvider.tsx +266 -282
  147. package/src/lib/theme/runtime/index.ts +2 -2
  148. package/src/lib/theme/runtime/useTheme.ts +1 -2
  149. package/src/lib/theme/runtime/useThemeTokens.ts +131 -0
  150. package/src/lib/theme/test/testTheme.ts +385 -0
  151. package/src/lib/theme/tokens/index.ts +12 -0
  152. package/src/lib/theme/tokens/tokens.ts +721 -0
  153. package/src/lib/theme/types.ts +6 -42
  154. package/src/lib/theme/utils/componentTheming.ts +132 -0
  155. package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
  156. package/src/lib/theme/utils/index.ts +11 -0
  157. package/src/lib/theme/utils/injectCSS.ts +90 -0
  158. package/src/lib/theme/utils/naming.ts +100 -0
  159. package/src/lib/theme/utils/themeHelpers.ts +78 -0
  160. package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +7 -7
  161. package/src/lib/theme-tools.ts +7 -8
  162. package/src/lib/types/components.ts +40 -130
  163. package/src/lib/utils/componentUtils.ts +2 -2
  164. package/src/lib/utils/memoryMonitor.ts +3 -3
  165. package/src/lib/utils/themeNaming.ts +135 -0
  166. package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
  167. package/src/styles/02-tools/_tools.button.scss +66 -79
  168. package/src/styles/06-components/_components.atomix-glass.scss +13 -3
  169. package/src/styles/06-components/_components.pagination.scss +88 -0
  170. package/scripts/sync-theme-config.js +0 -309
  171. package/src/lib/theme/composeTheme.ts +0 -370
  172. package/src/lib/theme/core/ThemeCache.ts +0 -283
  173. package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
  174. package/src/lib/theme/core/ThemeEngine.ts +0 -665
  175. package/src/lib/theme/createThemeFromConfig.ts +0 -132
  176. package/src/lib/theme/devtools/CLI.ts +0 -364
  177. package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
  178. package/src/lib/theme/runtime/ThemeManager.ts +0 -446
  179. package/src/styles/03-generic/_generated-root.css +0 -26
  180. package/src/themes/README.md +0 -442
  181. package/src/themes/themes.config.js +0 -68
  182. /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
@@ -1,665 +0,0 @@
1
- /**
2
- * Theme Engine
3
- *
4
- * Core engine for unified CSS and JS theme support
5
- */
6
-
7
- import type { Theme } from '../types';
8
- import type { ThemeDefinition } from '../config/types';
9
- import { ThemeRegistry } from './ThemeRegistry';
10
- import { ThemeCache } from './ThemeCache';
11
- import { ThemeValidator } from './ThemeValidator';
12
- import { isBrowser, isServer, loadThemeCSS, removeThemeCSS, applyThemeAttributes } from '../utils';
13
- import { generateCSSVariables, injectCSS, removeInjectedCSS } from '../generateCSSVariables';
14
- import { isJSTheme } from '../themeUtils';
15
- import { ThemeError, ThemeErrorCode, getLogger } from '../errors';
16
- import {
17
- DEFAULT_BASE_PATH,
18
- DEFAULT_DATA_ATTRIBUTE,
19
- DEFAULT_STYLE_ID,
20
- DEFAULT_ENGINE_CACHE_CONFIG,
21
- } from '../constants';
22
-
23
- /**
24
- * Theme change event
25
- */
26
- export interface ThemeChangeEvent {
27
- /** Previous theme ID */
28
- previousTheme: string | null;
29
- /** Current theme ID */
30
- currentTheme: string;
31
- /** Theme object (for JS themes) */
32
- themeObject?: Theme | null;
33
- /** Timestamp */
34
- timestamp: number;
35
- /** Source of change */
36
- source: 'user' | 'system' | 'storage';
37
- }
38
-
39
- /**
40
- * Theme load options
41
- */
42
- export interface ThemeLoadOptions {
43
- /** Force reload even if already loaded */
44
- force?: boolean;
45
- /** Preload without applying */
46
- preload?: boolean;
47
- /** Remove previous theme CSS */
48
- removePrevious?: boolean;
49
- /** Custom CSS path override */
50
- customPath?: string;
51
- /** Fallback to default theme on error */
52
- fallbackOnError?: boolean;
53
- }
54
-
55
- /**
56
- * Theme revert event
57
- */
58
- export interface ThemeRevertEvent {
59
- /** Theme ID that was attempted */
60
- attemptedTheme: string;
61
- /** Theme ID that was reverted to (null if no previous theme) */
62
- revertedToTheme: string | null;
63
- /** Error that caused the revert */
64
- error: Error;
65
- /** Timestamp */
66
- timestamp: number;
67
- }
68
-
69
- /**
70
- * Event listener types
71
- */
72
- export type ThemeChangeListener = (event: ThemeChangeEvent) => void;
73
- export type ThemeLoadListener = (themeId: string) => void;
74
- export type ThemeErrorListener = (error: Error, themeId: string) => void;
75
- export type ThemeRevertListener = (event: ThemeRevertEvent) => void;
76
-
77
- /**
78
- * Theme Engine Configuration
79
- */
80
- export interface ThemeEngineConfig {
81
- /** Base path for CSS themes */
82
- basePath?: string;
83
- /** CDN path for CSS themes */
84
- cdnPath?: string | null;
85
- /** Use minified CSS */
86
- useMinified?: boolean;
87
- /** Data attribute name */
88
- dataAttribute?: string;
89
- /** Enable caching */
90
- enableCache?: boolean;
91
- /** Cache configuration */
92
- cacheConfig?: {
93
- maxSize?: number;
94
- ttl?: number;
95
- };
96
- /** Custom style ID for JS theme CSS injection */
97
- styleId?: string;
98
- }
99
-
100
- /**
101
- * Theme Engine
102
- *
103
- * Unified engine for managing CSS and JS themes
104
- */
105
- export class ThemeEngine {
106
- private registry: ThemeRegistry;
107
- private cache: ThemeCache;
108
- private validator: ThemeValidator;
109
- private config: Required<Omit<ThemeEngineConfig, 'cdnPath' | 'styleId'>> & {
110
- cdnPath: string | null;
111
- styleId: string;
112
- };
113
-
114
- private currentTheme: string | null = null;
115
- private activeTheme: Theme | null = null;
116
- private loadedThemes: Set<string> = new Set();
117
- private loadingThemes: Map<string, Promise<void>> = new Map();
118
- private failedThemes: Set<string> = new Set(); // Track failed themes to prevent infinite retries
119
-
120
- private changeListeners: ThemeChangeListener[] = [];
121
- private loadListeners: ThemeLoadListener[] = [];
122
- private errorListeners: ThemeErrorListener[] = [];
123
- private revertListeners: ThemeRevertListener[] = [];
124
- private logger = getLogger();
125
-
126
- constructor(config: ThemeEngineConfig = {}) {
127
- this.registry = new ThemeRegistry();
128
- this.cache = new ThemeCache(config.cacheConfig);
129
- this.validator = new ThemeValidator();
130
-
131
- this.config = {
132
- basePath: config.basePath || DEFAULT_BASE_PATH,
133
- cdnPath: config.cdnPath ?? null,
134
- useMinified: config.useMinified ?? false,
135
- dataAttribute: config.dataAttribute || DEFAULT_DATA_ATTRIBUTE,
136
- enableCache: config.enableCache ?? true,
137
- cacheConfig: config.cacheConfig ?? DEFAULT_ENGINE_CACHE_CONFIG,
138
- styleId: config.styleId || DEFAULT_STYLE_ID,
139
- };
140
- }
141
-
142
- /**
143
- * Initialize engine with theme registry
144
- */
145
- async initialize(): Promise<void> {
146
- await this.registry.initialize();
147
- }
148
-
149
- /**
150
- * Get current theme ID
151
- */
152
- getCurrentTheme(): string | null {
153
- return this.currentTheme;
154
- }
155
-
156
- /**
157
- * Get active theme object
158
- */
159
- getActiveTheme(): Theme | null {
160
- return this.activeTheme;
161
- }
162
-
163
- /**
164
- * Check if theme is loaded
165
- */
166
- isThemeLoaded(themeId: string): boolean {
167
- return this.loadedThemes.has(themeId);
168
- }
169
-
170
- /**
171
- * Load and apply theme
172
- */
173
- async setTheme(themeId: string | Theme, options: ThemeLoadOptions = {}): Promise<void> {
174
- const {
175
- force = false,
176
- preload = false,
177
- removePrevious = true,
178
- fallbackOnError = true,
179
- } = options;
180
-
181
- // Handle Theme object directly
182
- if (typeof themeId !== 'string') {
183
- if (isJSTheme(themeId)) {
184
- await this.applyJSTheme(themeId, removePrevious);
185
- return;
186
- } else {
187
- throw new Error('Invalid theme object provided');
188
- }
189
- }
190
-
191
- // Check if theme exists
192
- if (!this.registry.has(themeId)) {
193
- const error = new ThemeError(
194
- `Theme "${themeId}" not found in registry`,
195
- ThemeErrorCode.THEME_NOT_FOUND,
196
- { themeId }
197
- );
198
- // Mark as failed to prevent infinite retries
199
- this.failedThemes.add(themeId);
200
- this.emitError(error, themeId);
201
- if (fallbackOnError && this.currentTheme) {
202
- // Emit revert event
203
- this.emitRevert({
204
- attemptedTheme: themeId,
205
- revertedToTheme: this.currentTheme,
206
- error,
207
- timestamp: Date.now(),
208
- });
209
- return;
210
- }
211
- throw error;
212
- }
213
-
214
- // Check if theme has previously failed (unless forcing)
215
- if (!force && this.failedThemes.has(themeId)) {
216
- const error = new ThemeError(
217
- `Theme "${themeId}" previously failed to load. Use force: true to retry.`,
218
- ThemeErrorCode.THEME_LOAD_FAILED,
219
- { themeId, previouslyFailed: true }
220
- );
221
- this.emitError(error, themeId);
222
- if (fallbackOnError && this.currentTheme) {
223
- return;
224
- }
225
- throw error;
226
- }
227
-
228
- // Check if already loaded and not forcing
229
- if (!force && this.isThemeLoaded(themeId) && !preload) {
230
- if (this.currentTheme !== themeId) {
231
- await this.applyTheme(themeId, removePrevious);
232
- }
233
- return;
234
- }
235
-
236
- // Check if already loading
237
- const existingLoad = this.loadingThemes.get(themeId);
238
- if (existingLoad) {
239
- await existingLoad;
240
- if (!preload && this.currentTheme !== themeId) {
241
- await this.applyTheme(themeId, removePrevious);
242
- }
243
- return;
244
- }
245
-
246
- // Start loading
247
- const loadPromise = this.loadTheme(themeId, options);
248
- this.loadingThemes.set(themeId, loadPromise);
249
-
250
- try {
251
- await loadPromise;
252
- this.loadingThemes.delete(themeId);
253
- // Remove from failed themes if it successfully loads
254
- this.failedThemes.delete(themeId);
255
-
256
- if (!preload) {
257
- await this.applyTheme(themeId, removePrevious);
258
- }
259
- } catch (error) {
260
- this.loadingThemes.delete(themeId);
261
- const errorObj = error instanceof Error ? error : new Error(String(error));
262
-
263
- // Only emit error and mark as failed if not already failed (prevent infinite logging)
264
- const wasAlreadyFailed = this.failedThemes.has(themeId);
265
- if (!wasAlreadyFailed || force) {
266
- // Mark theme as failed to prevent infinite retries
267
- this.failedThemes.add(themeId);
268
- // Emit error only on first failure
269
- if (!wasAlreadyFailed) {
270
- this.emitError(errorObj, themeId);
271
- }
272
- }
273
-
274
- if (fallbackOnError && this.currentTheme) {
275
- // Emit revert event only on first failure
276
- if (!wasAlreadyFailed) {
277
- this.emitRevert({
278
- attemptedTheme: themeId,
279
- revertedToTheme: this.currentTheme,
280
- error: errorObj,
281
- timestamp: Date.now(),
282
- });
283
- }
284
- return;
285
- }
286
- throw error;
287
- }
288
- }
289
-
290
- /**
291
- * Load theme (CSS or JS)
292
- */
293
- private async loadTheme(themeId: string, options: ThemeLoadOptions): Promise<void> {
294
- const definition = this.registry.getDefinition(themeId);
295
- if (!definition) {
296
- throw new Error(`Theme definition not found: ${themeId}`);
297
- }
298
-
299
- if (definition.type === 'css') {
300
- await this.loadCSSTheme(themeId, definition, options);
301
- } else {
302
- await this.loadJSTheme(themeId, definition);
303
- }
304
-
305
- this.loadedThemes.add(themeId);
306
- this.emitLoad(themeId);
307
- }
308
-
309
- /**
310
- * Load CSS theme
311
- */
312
- private async loadCSSTheme(
313
- themeId: string,
314
- definition: ThemeDefinition,
315
- options: ThemeLoadOptions
316
- ): Promise<void> {
317
- // Check cache
318
- if (this.config.enableCache) {
319
- const cached = this.cache.getCSS(themeId);
320
- if (cached?.loaded) {
321
- return;
322
- }
323
- }
324
-
325
- if (isServer()) {
326
- return;
327
- }
328
-
329
- const cssPath = options.customPath ||
330
- (definition.type === 'css' && definition.cssPath) ||
331
- `${this.config.basePath}/${themeId}${this.config.useMinified ? '.min' : ''}.css`;
332
-
333
- const fullPath = this.config.cdnPath || cssPath;
334
-
335
- // Mark as loading
336
- if (this.config.enableCache) {
337
- this.cache.setCSS(themeId, { loading: Promise.resolve(), loaded: false });
338
- }
339
-
340
- try {
341
- await loadThemeCSS(fullPath, getThemeLinkId(themeId));
342
-
343
- if (this.config.enableCache) {
344
- this.cache.setCSS(themeId, { loaded: true, loading: null });
345
- }
346
- } catch (error) {
347
- if (this.config.enableCache) {
348
- this.cache.delete(themeId);
349
- }
350
- // Re-throw error to be handled by setTheme
351
- throw error;
352
- }
353
- }
354
-
355
- /**
356
- * Load JS theme
357
- */
358
- private async loadJSTheme(themeId: string, definition: ThemeDefinition): Promise<void> {
359
- if (definition.type !== 'js') {
360
- return;
361
- }
362
-
363
- // Check cache
364
- if (this.config.enableCache) {
365
- const cached = this.cache.getJS(themeId);
366
- if (cached) {
367
- this.registry.setTheme(themeId, cached.theme);
368
- return;
369
- }
370
- }
371
-
372
- // Create theme
373
- const theme = definition.createTheme();
374
-
375
- // Validate theme
376
- const metadata = this.registry.get(themeId);
377
- const validation = this.validator.validate(theme, {
378
- ...definition,
379
- name: themeId,
380
- });
381
-
382
- if (!validation.valid && validation.errors.length > 0) {
383
- this.logger.warn(`Theme validation errors for "${themeId}"`, {
384
- themeId,
385
- errors: validation.errors,
386
- warnings: validation.warnings,
387
- });
388
- }
389
-
390
- // Cache and register
391
- if (this.config.enableCache) {
392
- this.cache.setJS(themeId, theme);
393
- }
394
- this.registry.setTheme(themeId, theme);
395
- }
396
-
397
- /**
398
- * Apply theme (set as active)
399
- */
400
- private async applyTheme(themeId: string, removePrevious: boolean): Promise<void> {
401
- const previousTheme = this.currentTheme;
402
-
403
- // Remove previous theme if requested
404
- if (removePrevious && previousTheme && previousTheme !== themeId) {
405
- await this.removeTheme(previousTheme);
406
- }
407
-
408
- const definition = this.registry.getDefinition(themeId);
409
- if (!definition) {
410
- throw new Error(`Theme definition not found: ${themeId}`);
411
- }
412
-
413
- if (definition.type === 'css') {
414
- await this.applyCSSTheme(themeId);
415
- } else {
416
- const theme = this.registry.getTheme(themeId);
417
- if (theme) {
418
- await this.applyJSTheme(theme, false);
419
- }
420
- }
421
-
422
- this.currentTheme = themeId;
423
- this.emitChange({
424
- previousTheme,
425
- currentTheme: themeId,
426
- themeObject: this.activeTheme,
427
- timestamp: Date.now(),
428
- source: 'user',
429
- });
430
- }
431
-
432
- /**
433
- * Apply CSS theme
434
- */
435
- private async applyCSSTheme(themeId: string): Promise<void> {
436
- if (isServer()) {
437
- return;
438
- }
439
-
440
- const definition = this.registry.getDefinition(themeId);
441
- const className = definition?.class || themeId;
442
-
443
- applyThemeAttributes(this.config.dataAttribute, className);
444
- this.activeTheme = null;
445
- }
446
-
447
- /**
448
- * Apply JS theme
449
- */
450
- private async applyJSTheme(theme: Theme, removePrevious: boolean): Promise<void> {
451
- if (isServer()) {
452
- return;
453
- }
454
-
455
- // Remove previous JS theme CSS
456
- if (removePrevious) {
457
- removeInjectedCSS(this.config.styleId);
458
- }
459
-
460
- // Generate and inject CSS variables
461
- const css = generateCSSVariables(theme, {
462
- selector: ':root',
463
- prefix: 'atomix',
464
- });
465
-
466
- injectCSS(css, this.config.styleId);
467
-
468
- // Apply data attribute
469
- const themeId = theme.name || 'js-theme';
470
- applyThemeAttributes(this.config.dataAttribute, themeId);
471
-
472
- this.activeTheme = theme;
473
- this.currentTheme = themeId;
474
- }
475
-
476
- /**
477
- * Remove theme
478
- */
479
- private async removeTheme(themeId: string): Promise<void> {
480
- const definition = this.registry.getDefinition(themeId);
481
- if (!definition) {
482
- return;
483
- }
484
-
485
- if (definition.type === 'css') {
486
- if (isBrowser()) {
487
- removeThemeCSS(getThemeLinkId(themeId));
488
- }
489
- } else {
490
- if (isBrowser()) {
491
- removeInjectedCSS(this.config.styleId);
492
- }
493
- }
494
- }
495
-
496
- /**
497
- * Preload theme
498
- */
499
- async preloadTheme(themeId: string): Promise<void> {
500
- await this.setTheme(themeId, { preload: true });
501
- }
502
-
503
- /**
504
- * Get registry
505
- */
506
- getRegistry(): ThemeRegistry {
507
- return this.registry;
508
- }
509
-
510
- /**
511
- * Get cache
512
- */
513
- getCache(): ThemeCache {
514
- return this.cache;
515
- }
516
-
517
- /**
518
- * Clear failed theme tracking (allows retry of previously failed themes)
519
- */
520
- clearFailedThemes(): void {
521
- this.failedThemes.clear();
522
- }
523
-
524
- /**
525
- * Clear specific failed theme (allows retry of a specific theme)
526
- */
527
- clearFailedTheme(themeId: string): void {
528
- this.failedThemes.delete(themeId);
529
- }
530
-
531
- /**
532
- * Check if theme has failed to load
533
- */
534
- hasFailedTheme(themeId: string): boolean {
535
- return this.failedThemes.has(themeId);
536
- }
537
-
538
- /**
539
- * Add change listener
540
- */
541
- // @ts-ignore - TypeScript overloads are valid, ESLint doesn't understand them
542
- on(event: 'change', listener: ThemeChangeListener): void;
543
- // @ts-ignore
544
- on(event: 'load', listener: ThemeLoadListener): void;
545
- // @ts-ignore
546
- on(event: 'error', listener: ThemeErrorListener): void;
547
- // @ts-ignore
548
- on(event: 'revert', listener: ThemeRevertListener): void;
549
- on(
550
- event: 'change' | 'load' | 'error' | 'revert',
551
- listener: ThemeChangeListener | ThemeLoadListener | ThemeErrorListener | ThemeRevertListener
552
- ): void {
553
- if (event === 'change') {
554
- this.changeListeners.push(listener as ThemeChangeListener);
555
- } else if (event === 'load') {
556
- this.loadListeners.push(listener as ThemeLoadListener);
557
- } else if (event === 'error') {
558
- this.errorListeners.push(listener as ThemeErrorListener);
559
- } else if (event === 'revert') {
560
- this.revertListeners.push(listener as ThemeRevertListener);
561
- }
562
- }
563
-
564
- /**
565
- * Remove listener
566
- */
567
- // @ts-ignore - TypeScript overloads are valid, ESLint doesn't understand them
568
- off(event: 'change', listener: ThemeChangeListener): void;
569
- // @ts-ignore
570
- off(event: 'load', listener: ThemeLoadListener): void;
571
- // @ts-ignore
572
- off(event: 'error', listener: ThemeErrorListener): void;
573
- // @ts-ignore
574
- off(event: 'revert', listener: ThemeRevertListener): void;
575
- off(
576
- event: 'change' | 'load' | 'error' | 'revert',
577
- listener: ThemeChangeListener | ThemeLoadListener | ThemeErrorListener | ThemeRevertListener
578
- ): void {
579
- if (event === 'change') {
580
- this.changeListeners = this.changeListeners.filter(l => l !== listener);
581
- } else if (event === 'load') {
582
- this.loadListeners = this.loadListeners.filter(l => l !== listener);
583
- } else if (event === 'error') {
584
- this.errorListeners = this.errorListeners.filter(l => l !== listener);
585
- } else if (event === 'revert') {
586
- this.revertListeners = this.revertListeners.filter(l => l !== listener);
587
- }
588
- }
589
-
590
- /**
591
- * Emit change event
592
- */
593
- private emitChange(event: ThemeChangeEvent): void {
594
- for (const listener of this.changeListeners) {
595
- try {
596
- listener(event);
597
- } catch (error) {
598
- this.logger.error(
599
- 'Error in theme change listener',
600
- error instanceof Error ? error : new Error(String(error)),
601
- { event }
602
- );
603
- }
604
- }
605
- }
606
-
607
- /**
608
- * Emit load event
609
- */
610
- private emitLoad(themeId: string): void {
611
- for (const listener of this.loadListeners) {
612
- try {
613
- listener(themeId);
614
- } catch (error) {
615
- this.logger.error(
616
- 'Error in theme load listener',
617
- error instanceof Error ? error : new Error(String(error)),
618
- { themeId }
619
- );
620
- }
621
- }
622
- }
623
-
624
- /**
625
- * Emit error event
626
- * Emits error to listeners (error emission is controlled at call site)
627
- */
628
- private emitError(error: Error, themeId: string): void {
629
- for (const listener of this.errorListeners) {
630
- try {
631
- listener(error, themeId);
632
- } catch (err) {
633
- this.logger.error(
634
- 'Error in theme error listener',
635
- err instanceof Error ? err : new Error(String(err)),
636
- { themeId, originalError: error.message }
637
- );
638
- }
639
- }
640
- }
641
-
642
- /**
643
- * Emit revert event
644
- */
645
- private emitRevert(event: ThemeRevertEvent): void {
646
- for (const listener of this.revertListeners) {
647
- try {
648
- listener(event);
649
- } catch (error) {
650
- this.logger.error(
651
- 'Error in theme revert listener',
652
- error instanceof Error ? error : new Error(String(error)),
653
- { event }
654
- );
655
- }
656
- }
657
- }
658
- }
659
-
660
- /**
661
- * Helper to get theme link ID
662
- */
663
- function getThemeLinkId(themeName: string): string {
664
- return `atomix-theme-${themeName}`;
665
- }