@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,370 +0,0 @@
1
- /**
2
- * Theme Composition Utilities
3
- *
4
- * Utilities for composing, merging, and extending themes.
5
- */
6
-
7
- import type { Theme, ThemeOptions } from './types';
8
- import { createTheme } from './createTheme';
9
-
10
- // ============================================================================
11
- // Deep Merge Utility
12
- // ============================================================================
13
-
14
- /**
15
- * Check if value is an object
16
- */
17
- function isObject(item: any): item is Record<string, any> {
18
- return item && typeof item === 'object' && !Array.isArray(item) && typeof item !== 'function';
19
- }
20
-
21
- /**
22
- * Deep merge multiple objects
23
- * Later objects override earlier ones
24
- */
25
- export function deepMerge<T extends Record<string, any>>(...objects: Partial<T>[]): T {
26
- if (objects.length === 0) return {} as T;
27
- if (objects.length === 1) return objects[0] as T;
28
-
29
- const [target, ...sources] = objects;
30
- const result = { ...target } as T;
31
-
32
- for (const source of sources) {
33
- if (!source) continue;
34
-
35
- for (const key in source) {
36
- if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
37
-
38
- const targetValue = result[key];
39
- const sourceValue = source[key];
40
-
41
- if (isObject(targetValue) && isObject(sourceValue)) {
42
- // Recursively merge objects
43
- result[key] = deepMerge(targetValue as any, sourceValue as any) as any;
44
- } else {
45
- // Override with source value
46
- result[key] = sourceValue as any;
47
- }
48
- }
49
- }
50
-
51
- return result;
52
- }
53
-
54
- // ============================================================================
55
- // Theme Merging
56
- // ============================================================================
57
-
58
- /**
59
- * Merge multiple theme options into a single theme options object
60
- *
61
- * @param themes - Theme options to merge
62
- * @returns Merged theme options
63
- *
64
- * @example
65
- * ```typescript
66
- * const baseTheme = { palette: { primary: { main: '#000' } } };
67
- * const customTheme = { palette: { secondary: { main: '#fff' } } };
68
- * const merged = mergeTheme(baseTheme, customTheme);
69
- * ```
70
- */
71
- export function mergeTheme(...themes: ThemeOptions[]): ThemeOptions {
72
- return deepMerge({}, ...themes);
73
- }
74
-
75
- /**
76
- * Extend an existing theme with new options
77
- *
78
- * @param baseTheme - Base theme to extend (can be Theme or ThemeOptions)
79
- * @param extension - Theme options to extend with
80
- * @returns New theme with extended options
81
- *
82
- * @example
83
- * ```typescript
84
- * const base = createTheme({ palette: { primary: { main: '#000' } } });
85
- * const extended = extendTheme(base, {
86
- * palette: { secondary: { main: '#fff' } }
87
- * });
88
- * ```
89
- */
90
- export function extendTheme(baseTheme: Theme | ThemeOptions, extension: ThemeOptions): Theme {
91
- // If baseTheme is a complete Theme, extract the options
92
- const baseOptions: ThemeOptions = (baseTheme as any).__isJSTheme
93
- ? extractThemeOptions(baseTheme as Theme)
94
- : (baseTheme as ThemeOptions);
95
-
96
- const merged = mergeTheme(baseOptions, extension);
97
- return createTheme(merged);
98
- }
99
-
100
- /**
101
- * Extract theme options from a complete Theme object
102
- */
103
- function extractThemeOptions(theme: Theme): ThemeOptions {
104
- return {
105
- name: theme.name,
106
- class: theme.class,
107
- description: theme.description,
108
- author: theme.author,
109
- version: theme.version,
110
- tags: theme.tags,
111
- supportsDarkMode: theme.supportsDarkMode,
112
- status: theme.status,
113
- a11y: theme.a11y,
114
- color: theme.color,
115
- features: theme.features,
116
- dependencies: theme.dependencies,
117
- palette: {
118
- primary: theme.palette.primary,
119
- secondary: theme.palette.secondary,
120
- error: theme.palette.error,
121
- warning: theme.palette.warning,
122
- info: theme.palette.info,
123
- success: theme.palette.success,
124
- background: theme.palette.background,
125
- text: theme.palette.text,
126
- },
127
- typography: {
128
- fontFamily: theme.typography.fontFamily,
129
- fontSize: theme.typography.fontSize,
130
- fontWeightLight: theme.typography.fontWeightLight,
131
- fontWeightRegular: theme.typography.fontWeightRegular,
132
- fontWeightMedium: theme.typography.fontWeightMedium,
133
- fontWeightSemiBold: theme.typography.fontWeightSemiBold,
134
- fontWeightBold: theme.typography.fontWeightBold,
135
- h1: theme.typography.h1,
136
- h2: theme.typography.h2,
137
- h3: theme.typography.h3,
138
- h4: theme.typography.h4,
139
- h5: theme.typography.h5,
140
- h6: theme.typography.h6,
141
- body1: theme.typography.body1,
142
- body2: theme.typography.body2,
143
- },
144
- shadows: theme.shadows,
145
- transitions: theme.transitions,
146
- zIndex: theme.zIndex,
147
- custom: theme.custom,
148
- };
149
- }
150
-
151
- // ============================================================================
152
- // Theme Variants
153
- // ============================================================================
154
-
155
- /**
156
- * Create light and dark variants from a base theme
157
- *
158
- * @param baseTheme - Base theme options
159
- * @returns Object with light and dark theme variants
160
- *
161
- * @example
162
- * ```typescript
163
- * const { light, dark } = createThemeVariants({
164
- * palette: { primary: { main: '#7AFFD7' } }
165
- * });
166
- * ```
167
- */
168
- export function createThemeVariants(baseTheme: ThemeOptions): {
169
- light: Theme;
170
- dark: Theme;
171
- } {
172
- // Light theme (use base as-is or with light adjustments)
173
- const lightTheme = createTheme({
174
- ...baseTheme,
175
- name: `${baseTheme.name || 'Custom'} Light`,
176
- supportsDarkMode: false,
177
- });
178
-
179
- // Dark theme (invert colors)
180
- const darkTheme = createTheme({
181
- ...baseTheme,
182
- name: `${baseTheme.name || 'Custom'} Dark`,
183
- supportsDarkMode: true,
184
- palette: {
185
- ...baseTheme.palette,
186
- background: {
187
- default: '#121212',
188
- subtle: '#2A2A2A',
189
- ...baseTheme.palette?.background,
190
- },
191
- text: {
192
- primary: 'rgba(255, 255, 255, 0.87)',
193
- secondary: 'rgba(255, 255, 255, 0.6)',
194
- disabled: 'rgba(255, 255, 255, 0.38)',
195
- ...baseTheme.palette?.text,
196
- },
197
- },
198
- });
199
-
200
- return { light: lightTheme, dark: darkTheme };
201
- }
202
-
203
- // ============================================================================
204
- // Theme Overrides
205
- // ============================================================================
206
-
207
- /**
208
- * Create a theme with specific overrides
209
- *
210
- * @param baseTheme - Base theme
211
- * @param overrides - Specific overrides to apply
212
- * @returns New theme with overrides
213
- *
214
- * @example
215
- * ```typescript
216
- * const theme = overrideTheme(baseTheme, {
217
- * 'palette.primary.main': '#FF0000',
218
- * 'typography.fontSize': 16,
219
- * });
220
- * ```
221
- */
222
- export function overrideTheme(
223
- baseTheme: Theme | ThemeOptions,
224
- overrides: Record<string, any>
225
- ): Theme {
226
- const baseOptions: ThemeOptions = (baseTheme as any).__isJSTheme
227
- ? extractThemeOptions(baseTheme as Theme)
228
- : (baseTheme as ThemeOptions);
229
-
230
- // Convert dot notation overrides to nested object
231
- const nestedOverrides: any = {};
232
- for (const [path, value] of Object.entries(overrides)) {
233
- const keys = path.split('.');
234
- let current = nestedOverrides;
235
-
236
- for (let i = 0; i < keys.length - 1; i++) {
237
- const key = keys[i] as string;
238
- if (typeof key !== 'string' || key === '') {
239
- throw new Error('Invalid override key in theme override: ' + String(key));
240
- }
241
- if (typeof current !== 'object' || current === null) {
242
- throw new Error('Cannot set override for path due to non-object path segment');
243
- }
244
- if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
245
- current[key] = {};
246
- }
247
- current = current[key] as Record<string, any>;
248
- }
249
-
250
- const lastKey = keys[keys.length - 1] as string;
251
- if (typeof lastKey === 'string') {
252
- current[lastKey] = value;
253
- }
254
-
255
- }
256
-
257
- return createTheme(deepMerge(baseOptions, nestedOverrides));
258
- }
259
-
260
- // ============================================================================
261
- // Theme Composition Helpers
262
- // ============================================================================
263
-
264
- /**
265
- * Compose multiple themes by merging them in order
266
- *
267
- * @param themes - Themes to compose (later themes override earlier ones)
268
- * @returns Composed theme
269
- *
270
- * @example
271
- * ```typescript
272
- * const theme = composeThemes(
273
- * baseTheme,
274
- * brandTheme,
275
- * customizationTheme
276
- * );
277
- * ```
278
- */
279
- export function composeThemes(...themes: (Theme | ThemeOptions)[]): Theme {
280
- const options = themes.map((theme) =>
281
- (theme as any).__isJSTheme ? extractThemeOptions(theme as Theme) : (theme as ThemeOptions)
282
- );
283
-
284
- return createTheme(mergeTheme(...options));
285
- }
286
-
287
- /**
288
- * Create a theme preset with common configurations
289
- *
290
- * @param preset - Preset name
291
- * @param customizations - Additional customizations
292
- * @returns Theme with preset applied
293
- */
294
- export function createThemePreset(
295
- preset: 'minimal' | 'modern' | 'classic' | 'vibrant',
296
- customizations?: ThemeOptions
297
- ): Theme {
298
- const presets: Record<string, ThemeOptions> = {
299
- minimal: {
300
- name: 'Minimal',
301
- palette: {
302
- primary: { main: '#000000' },
303
- secondary: { main: '#FFFFFF' },
304
- background: {
305
- default: '#FFFFFF',
306
- subtle: '#FAFAFA',
307
- },
308
- },
309
- typography: {
310
- fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
311
- },
312
- },
313
- modern: {
314
- name: 'Modern',
315
- palette: {
316
- primary: { main: '#7AFFD7' },
317
- secondary: { main: '#FF5733' },
318
- background: {
319
- default: '#FAFAFA',
320
- subtle: '#F5F5F5',
321
- },
322
- },
323
- typography: {
324
- fontFamily: '"Inter", "Roboto", sans-serif',
325
- },
326
- },
327
- classic: {
328
- name: 'Classic',
329
- palette: {
330
- primary: { main: '#1976D2' },
331
- secondary: { main: '#DC004E' },
332
- background: {
333
- default: '#FFFFFF',
334
- subtle: '#EEEEEE',
335
- },
336
- },
337
- typography: {
338
- fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
339
- },
340
- },
341
- vibrant: {
342
- name: 'Vibrant',
343
- palette: {
344
- primary: { main: '#FF6B6B' },
345
- secondary: { main: '#4ECDC4' },
346
- background: {
347
- default: '#FFF8F0',
348
- subtle: '#FFF0E0',
349
- },
350
- },
351
- typography: {
352
- fontFamily: '"Poppins", "Roboto", sans-serif',
353
- },
354
- },
355
- };
356
-
357
- const basePreset: ThemeOptions = (presets[preset] ?? presets['modern']) as ThemeOptions;
358
- const customThemeOptions: ThemeOptions = customizations ?? ({} as ThemeOptions);
359
- return createTheme(mergeTheme(basePreset, customThemeOptions));
360
- }
361
-
362
- export default {
363
- deepMerge,
364
- mergeTheme,
365
- extendTheme,
366
- createThemeVariants,
367
- overrideTheme,
368
- composeThemes,
369
- createThemePreset,
370
- };
@@ -1,283 +0,0 @@
1
- /**
2
- * Theme Cache
3
- *
4
- * Performance caching layer for loaded themes (CSS and JS)
5
- */
6
-
7
- import type { Theme } from '../types';
8
- import { DEFAULT_CACHE_CONFIG } from '../constants';
9
-
10
- /**
11
- * Cache entry for CSS theme
12
- */
13
- interface CSSCacheEntry {
14
- type: 'css' | 'js';
15
- themeId: string;
16
- cssContent?: string;
17
- loaded: boolean;
18
- loading: Promise<void> | null;
19
- timestamp: number;
20
- }
21
-
22
- /**
23
- * Cache entry for JS theme
24
- */
25
- interface JSCacheEntry {
26
- type: 'js';
27
- themeId: string;
28
- theme: Theme;
29
- loaded: boolean;
30
- timestamp: number;
31
- }
32
-
33
- type CacheEntry = CSSCacheEntry | JSCacheEntry;
34
-
35
- /**
36
- * Theme cache configuration
37
- */
38
- interface CacheConfig {
39
- /** Maximum cache size (number of themes) */
40
- maxSize?: number;
41
- /** Cache TTL in milliseconds (0 = no expiration) */
42
- ttl?: number;
43
- /** Enable cache */
44
- enabled?: boolean;
45
- }
46
-
47
- /**
48
- * Default cache configuration
49
- */
50
- const DEFAULT_CONFIG: Required<CacheConfig> = {
51
- ...DEFAULT_CACHE_CONFIG,
52
- enabled: true,
53
- };
54
-
55
- /**
56
- * Theme Cache
57
- *
58
- * Manages caching of loaded themes for performance optimization
59
- */
60
- export class ThemeCache {
61
- private cache: Map<string, CacheEntry> = new Map();
62
- private config: Required<CacheConfig>;
63
- private accessOrder: string[] = []; // For LRU eviction
64
-
65
- constructor(config: CacheConfig = {}) {
66
- this.config = { ...DEFAULT_CONFIG, ...config };
67
- }
68
-
69
- /**
70
- * Get cached CSS theme
71
- */
72
- getCSS(themeId: string): CSSCacheEntry | null {
73
- if (!this.config.enabled) {
74
- return null;
75
- }
76
-
77
- const entry = this.cache.get(themeId);
78
- if (!entry || entry.type !== 'css') {
79
- return null;
80
- }
81
-
82
- // Check TTL
83
- if (this.config.ttl > 0) {
84
- const age = Date.now() - entry.timestamp;
85
- if (age > this.config.ttl) {
86
- this.cache.delete(themeId);
87
- this.removeFromAccessOrder(themeId);
88
- return null;
89
- }
90
- }
91
-
92
- // Update access order (LRU)
93
- this.updateAccessOrder(themeId);
94
-
95
- return entry;
96
- }
97
-
98
- /**
99
- * Get cached JS theme
100
- */
101
- getJS(themeId: string): JSCacheEntry | null {
102
- if (!this.config.enabled) {
103
- return null;
104
- }
105
-
106
- const entry = this.cache.get(themeId);
107
- if (!entry || entry.type !== 'js') {
108
- return null;
109
- }
110
-
111
- // Check TTL
112
- if (this.config.ttl > 0) {
113
- const age = Date.now() - entry.timestamp;
114
- if (age > this.config.ttl) {
115
- this.cache.delete(themeId);
116
- this.removeFromAccessOrder(themeId);
117
- return null;
118
- }
119
- }
120
-
121
- // Update access order (LRU)
122
- this.updateAccessOrder(themeId);
123
-
124
- return entry as JSCacheEntry;
125
- }
126
-
127
- /**
128
- * Set CSS theme cache entry
129
- */
130
- setCSS(themeId: string, entry: Partial<CSSCacheEntry>): void {
131
- if (!this.config.enabled) {
132
- return;
133
- }
134
-
135
- // Evict if needed
136
- this.evictIfNeeded();
137
-
138
- const existing = this.cache.get(themeId);
139
- const cacheEntry: CSSCacheEntry = {
140
- type: 'css',
141
- themeId,
142
- loaded: false,
143
- loading: null,
144
- timestamp: Date.now(),
145
- ...existing,
146
- ...entry,
147
- };
148
-
149
- this.cache.set(themeId, cacheEntry);
150
- this.updateAccessOrder(themeId);
151
- }
152
-
153
- /**
154
- * Set JS theme cache entry
155
- */
156
- setJS(themeId: string, theme: Theme): void {
157
- if (!this.config.enabled) {
158
- return;
159
- }
160
-
161
- // Evict if needed
162
- this.evictIfNeeded();
163
-
164
- const cacheEntry: JSCacheEntry = {
165
- type: 'js',
166
- themeId,
167
- theme,
168
- loaded: true,
169
- timestamp: Date.now(),
170
- };
171
-
172
- this.cache.set(themeId, cacheEntry);
173
- this.updateAccessOrder(themeId);
174
- }
175
-
176
- /**
177
- * Check if theme is cached
178
- */
179
- has(themeId: string): boolean {
180
- if (!this.config.enabled) {
181
- return false;
182
- }
183
-
184
- const entry = this.cache.get(themeId);
185
- if (!entry) {
186
- return false;
187
- }
188
-
189
- // Check TTL
190
- if (this.config.ttl > 0) {
191
- const age = Date.now() - entry.timestamp;
192
- if (age > this.config.ttl) {
193
- this.cache.delete(themeId);
194
- this.removeFromAccessOrder(themeId);
195
- return false;
196
- }
197
- }
198
-
199
- return true;
200
- }
201
-
202
- /**
203
- * Remove theme from cache
204
- */
205
- delete(themeId: string): boolean {
206
- const deleted = this.cache.delete(themeId);
207
- if (deleted) {
208
- this.removeFromAccessOrder(themeId);
209
- }
210
- return deleted;
211
- }
212
-
213
- /**
214
- * Clear all cache
215
- */
216
- clear(): void {
217
- this.cache.clear();
218
- this.accessOrder = [];
219
- }
220
-
221
- /**
222
- * Get cache statistics
223
- */
224
- getStats(): {
225
- size: number;
226
- maxSize: number;
227
- cssThemes: number;
228
- jsThemes: number;
229
- } {
230
- let cssThemes = 0;
231
- let jsThemes = 0;
232
-
233
- for (const entry of this.cache.values()) {
234
- if (entry.type === 'css') {
235
- cssThemes++;
236
- } else {
237
- jsThemes++;
238
- }
239
- }
240
-
241
- return {
242
- size: this.cache.size,
243
- maxSize: this.config.maxSize,
244
- cssThemes,
245
- jsThemes,
246
- };
247
- }
248
-
249
- /**
250
- * Update access order for LRU
251
- */
252
- private updateAccessOrder(themeId: string): void {
253
- this.removeFromAccessOrder(themeId);
254
- this.accessOrder.push(themeId);
255
- }
256
-
257
- /**
258
- * Remove from access order
259
- */
260
- private removeFromAccessOrder(themeId: string): void {
261
- const index = this.accessOrder.indexOf(themeId);
262
- if (index > -1) {
263
- this.accessOrder.splice(index, 1);
264
- }
265
- }
266
-
267
- /**
268
- * Evict least recently used entries if cache is full
269
- */
270
- private evictIfNeeded(): void {
271
- if (this.cache.size < this.config.maxSize) {
272
- return;
273
- }
274
-
275
- // Evict least recently used (first in access order)
276
- while (this.cache.size >= this.config.maxSize && this.accessOrder.length > 0) {
277
- const lruThemeId = this.accessOrder.shift();
278
- if (lruThemeId) {
279
- this.cache.delete(lruThemeId);
280
- }
281
- }
282
- }
283
- }