@shohojdhara/atomix 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) 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 +61 -66
  8. package/dist/charts.js.map +1 -1
  9. package/dist/core.js +47 -31
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js +47 -31
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +47 -31
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +1841 -1633
  16. package/dist/index.esm.js +4975 -4113
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +5151 -4290
  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 +1572 -1442
  23. package/dist/theme.js +4816 -4080
  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 +65 -31
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +11 -4
  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/ButtonGroup.stories.tsx +315 -0
  38. package/src/components/Button/ButtonGroup.tsx +67 -0
  39. package/src/components/Button/index.ts +2 -0
  40. package/src/components/Callout/Callout.stories.tsx +8 -6
  41. package/src/components/Card/Card.stories.tsx +82 -28
  42. package/src/components/Chart/AnimatedChart.tsx +0 -1
  43. package/src/components/Chart/AreaChart.tsx +0 -1
  44. package/src/components/Chart/BarChart.tsx +0 -1
  45. package/src/components/Chart/BubbleChart.tsx +0 -1
  46. package/src/components/Chart/CandlestickChart.tsx +0 -1
  47. package/src/components/Chart/Chart.stories.tsx +5 -7
  48. package/src/components/Chart/Chart.tsx +0 -16
  49. package/src/components/Chart/ChartRenderer.tsx +1 -1
  50. package/src/components/Chart/DonutChart.tsx +0 -1
  51. package/src/components/Chart/FunnelChart.tsx +0 -1
  52. package/src/components/Chart/GaugeChart.tsx +0 -1
  53. package/src/components/Chart/HeatmapChart.tsx +0 -1
  54. package/src/components/Chart/LineChart.tsx +0 -1
  55. package/src/components/Chart/MultiAxisChart.tsx +0 -1
  56. package/src/components/Chart/PieChart.tsx +0 -1
  57. package/src/components/Chart/RadarChart.tsx +0 -1
  58. package/src/components/Chart/ScatterChart.tsx +0 -1
  59. package/src/components/Chart/WaterfallChart.tsx +0 -1
  60. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
  61. package/src/components/DataTable/DataTable.stories.tsx +23 -16
  62. package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
  63. package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
  64. package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
  65. package/src/components/Footer/Footer.stories.tsx +8 -6
  66. package/src/components/Footer/FooterLink.tsx +9 -2
  67. package/src/components/Form/Checkbox.stories.tsx +7 -0
  68. package/src/components/Form/Form.stories.tsx +7 -0
  69. package/src/components/Form/FormGroup.stories.tsx +9 -1
  70. package/src/components/Form/Input.stories.tsx +69 -16
  71. package/src/components/Form/Radio.stories.tsx +9 -1
  72. package/src/components/Form/Select.stories.tsx +9 -1
  73. package/src/components/Form/Textarea.stories.tsx +10 -2
  74. package/src/components/Hero/Hero.stories.tsx +7 -0
  75. package/src/components/List/List.stories.tsx +7 -0
  76. package/src/components/Messages/Messages.stories.tsx +8 -7
  77. package/src/components/Modal/Modal.stories.tsx +17 -6
  78. package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
  79. package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
  80. package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
  81. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
  82. package/src/components/Pagination/Pagination.stories.tsx +188 -111
  83. package/src/components/Pagination/Pagination.tsx +83 -3
  84. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
  85. package/src/components/Popover/Popover.stories.tsx +191 -115
  86. package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
  87. package/src/components/Progress/Progress.stories.tsx +79 -49
  88. package/src/components/Rating/Rating.stories.tsx +109 -84
  89. package/src/components/River/River.stories.tsx +194 -114
  90. package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
  91. package/src/components/Slider/Slider.stories.tsx +7 -0
  92. package/src/components/Spinner/Spinner.stories.tsx +15 -11
  93. package/src/components/Steps/Steps.stories.tsx +132 -98
  94. package/src/components/Tabs/Tabs.stories.tsx +163 -112
  95. package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
  96. package/src/components/Todo/Todo.stories.tsx +38 -12
  97. package/src/components/Toggle/Toggle.stories.tsx +61 -28
  98. package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
  99. package/src/components/Upload/Upload.stories.tsx +122 -84
  100. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
  101. package/src/components/index.ts +1 -0
  102. package/src/lib/composables/useAtomixGlass.ts +2 -3
  103. package/src/lib/composables/useNavbar.ts +0 -10
  104. package/src/lib/config/loader.ts +2 -1
  105. package/src/lib/constants/components.ts +10 -0
  106. package/src/lib/hooks/useComponentCustomization.ts +1 -1
  107. package/src/lib/theme/README.md +174 -0
  108. package/src/lib/theme/adapters/index.ts +31 -0
  109. package/src/lib/theme/adapters/themeAdapter.ts +287 -0
  110. package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
  111. package/src/lib/theme/config/configLoader.ts +254 -0
  112. package/src/lib/theme/config/loader.ts +37 -48
  113. package/src/lib/theme/config/types.ts +2 -2
  114. package/src/lib/theme/config/validator.ts +15 -91
  115. package/src/lib/theme/{constants.ts → constants/constants.ts} +0 -18
  116. package/src/lib/theme/constants/index.ts +8 -0
  117. package/src/lib/theme/core/ThemeRegistry.ts +19 -6
  118. package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
  119. package/src/lib/theme/core/composeTheme.ts +155 -0
  120. package/src/lib/theme/core/createTheme.ts +94 -0
  121. package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +10 -6
  122. package/src/lib/theme/core/index.ts +5 -19
  123. package/src/lib/theme/devtools/Comparator.tsx +346 -22
  124. package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
  125. package/src/lib/theme/devtools/Inspector.tsx +335 -51
  126. package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
  127. package/src/lib/theme/devtools/Preview.tsx +471 -221
  128. package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
  129. package/src/lib/theme/devtools/index.ts +14 -4
  130. package/src/lib/theme/devtools/useHistory.ts +130 -0
  131. package/src/lib/theme/errors/index.ts +12 -0
  132. package/src/lib/theme/generators/cssFile.ts +79 -0
  133. package/src/lib/theme/generators/generateCSS.ts +89 -0
  134. package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
  135. package/src/lib/theme/generators/index.ts +19 -0
  136. package/src/lib/theme/i18n/rtl.ts +5 -6
  137. package/src/lib/theme/index.ts +120 -15
  138. package/src/lib/theme/runtime/ThemeApplicator.ts +52 -111
  139. package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
  140. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +1 -1
  141. package/src/lib/theme/runtime/ThemeProvider.tsx +456 -179
  142. package/src/lib/theme/runtime/index.ts +1 -2
  143. package/src/lib/theme/runtime/useTheme.ts +1 -2
  144. package/src/lib/theme/test/testTheme.ts +385 -0
  145. package/src/lib/theme/tokens/index.ts +12 -0
  146. package/src/lib/theme/tokens/tokens.ts +721 -0
  147. package/src/lib/theme/types.ts +6 -42
  148. package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
  149. package/src/lib/theme/utils/index.ts +11 -0
  150. package/src/lib/theme/utils/injectCSS.ts +90 -0
  151. package/src/lib/theme/utils/themeHelpers.ts +78 -0
  152. package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +1 -1
  153. package/src/lib/theme-tools.ts +7 -8
  154. package/src/lib/types/components.ts +40 -130
  155. package/src/lib/utils/componentUtils.ts +1 -1
  156. package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
  157. package/src/styles/02-tools/_tools.button.scss +66 -79
  158. package/src/styles/06-components/_components.atomix-glass.scss +13 -3
  159. package/src/styles/06-components/_components.pagination.scss +88 -0
  160. package/scripts/sync-theme-config.js +0 -309
  161. package/src/lib/theme/composeTheme.ts +0 -370
  162. package/src/lib/theme/core/ThemeCache.ts +0 -283
  163. package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
  164. package/src/lib/theme/core/ThemeEngine.ts +0 -665
  165. package/src/lib/theme/createThemeFromConfig.ts +0 -132
  166. package/src/lib/theme/devtools/CLI.ts +0 -364
  167. package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
  168. package/src/lib/theme/runtime/ThemeManager.ts +0 -446
  169. package/src/styles/03-generic/_generated-root.css +0 -26
  170. package/src/themes/README.md +0 -442
  171. package/src/themes/themes.config.js +0 -68
  172. /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
  173. /package/src/lib/theme/{errors.ts → errors/errors.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
- }