@codeleap/styles 6.2.3 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/classes/Cacher.d.ts +87 -0
  2. package/dist/classes/Cacher.d.ts.map +1 -0
  3. package/dist/classes/StaleControl.d.ts +65 -0
  4. package/dist/classes/StaleControl.d.ts.map +1 -0
  5. package/dist/classes/StyleCache.d.ts +63 -0
  6. package/dist/classes/StyleCache.d.ts.map +1 -0
  7. package/dist/classes/StylePersistor.d.ts +52 -0
  8. package/dist/classes/StylePersistor.d.ts.map +1 -0
  9. package/dist/classes/StyleRegistry.d.ts +108 -0
  10. package/dist/classes/StyleRegistry.d.ts.map +1 -0
  11. package/dist/classes/index.d.ts +3 -0
  12. package/dist/classes/index.d.ts.map +1 -0
  13. package/dist/constants.d.ts +22 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/hooks/index.d.ts +5 -0
  16. package/dist/hooks/index.d.ts.map +1 -0
  17. package/dist/hooks/useCompositionStyles.d.ts +12 -0
  18. package/dist/hooks/useCompositionStyles.d.ts.map +1 -0
  19. package/dist/hooks/useNestedStylesByKey.d.ts +11 -0
  20. package/dist/hooks/useNestedStylesByKey.d.ts.map +1 -0
  21. package/dist/hooks/useStyleObserver.d.ts +9 -0
  22. package/dist/hooks/useStyleObserver.d.ts.map +1 -0
  23. package/dist/hooks/useTheme.d.ts +19 -0
  24. package/dist/hooks/useTheme.d.ts.map +1 -0
  25. package/dist/index.d.ts +12 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/lib/calc.d.ts +27 -0
  28. package/dist/lib/calc.d.ts.map +1 -0
  29. package/dist/lib/createStyles.d.ts +30 -0
  30. package/dist/lib/createStyles.d.ts.map +1 -0
  31. package/dist/lib/createTheme.d.ts +28 -0
  32. package/dist/lib/createTheme.d.ts.map +1 -0
  33. package/dist/lib/cssVariables.d.ts +35 -0
  34. package/dist/lib/cssVariables.d.ts.map +1 -0
  35. package/dist/lib/index.d.ts +5 -0
  36. package/dist/lib/index.d.ts.map +1 -0
  37. package/dist/theme/generateColorScheme.d.ts +22 -0
  38. package/dist/theme/generateColorScheme.d.ts.map +1 -0
  39. package/dist/theme/index.d.ts +4 -0
  40. package/dist/theme/index.d.ts.map +1 -0
  41. package/dist/theme/themeStore.d.ts +106 -0
  42. package/dist/theme/themeStore.d.ts.map +1 -0
  43. package/dist/theme/validateTheme.d.ts +19 -0
  44. package/dist/theme/validateTheme.d.ts.map +1 -0
  45. package/dist/tools/colors.d.ts +70 -0
  46. package/dist/tools/colors.d.ts.map +1 -0
  47. package/dist/tools/deepClone.d.ts +7 -0
  48. package/dist/tools/deepClone.d.ts.map +1 -0
  49. package/dist/tools/deepmerge.d.ts +13 -0
  50. package/dist/tools/deepmerge.d.ts.map +1 -0
  51. package/dist/tools/hashKey.d.ts +8 -0
  52. package/dist/tools/hashKey.d.ts.map +1 -0
  53. package/dist/tools/index.d.ts +7 -0
  54. package/dist/tools/index.d.ts.map +1 -0
  55. package/dist/tools/minifier.d.ts +24 -0
  56. package/dist/tools/minifier.d.ts.map +1 -0
  57. package/dist/tools/multiplierProperty.d.ts +4 -0
  58. package/dist/tools/multiplierProperty.d.ts.map +1 -0
  59. package/dist/types/cache.d.ts +12 -0
  60. package/dist/types/cache.d.ts.map +1 -0
  61. package/dist/types/component.d.ts +58 -0
  62. package/dist/types/component.d.ts.map +1 -0
  63. package/dist/types/core.d.ts +77 -0
  64. package/dist/types/core.d.ts.map +1 -0
  65. package/dist/types/icon.d.ts +15 -0
  66. package/dist/types/icon.d.ts.map +1 -0
  67. package/dist/types/index.d.ts +6 -0
  68. package/dist/types/index.d.ts.map +1 -0
  69. package/dist/types/spacing.d.ts +28 -0
  70. package/dist/types/spacing.d.ts.map +1 -0
  71. package/dist/types/store.d.ts +12 -0
  72. package/dist/types/store.d.ts.map +1 -0
  73. package/dist/types/style.d.ts +42 -0
  74. package/dist/types/style.d.ts.map +1 -0
  75. package/dist/types/theme.d.ts +109 -0
  76. package/dist/types/theme.d.ts.map +1 -0
  77. package/dist/utils.d.ts +40 -0
  78. package/dist/utils.d.ts.map +1 -0
  79. package/dist/variants/borderCreator.d.ts +22 -0
  80. package/dist/variants/borderCreator.d.ts.map +1 -0
  81. package/dist/variants/createAppVariants.d.ts +18 -0
  82. package/dist/variants/createAppVariants.d.ts.map +1 -0
  83. package/dist/variants/defaultVariants.d.ts +140 -0
  84. package/dist/variants/defaultVariants.d.ts.map +1 -0
  85. package/dist/variants/dynamicVariants.d.ts +43 -0
  86. package/dist/variants/dynamicVariants.d.ts.map +1 -0
  87. package/dist/variants/index.d.ts +7 -0
  88. package/dist/variants/index.d.ts.map +1 -0
  89. package/dist/variants/mediaQuery.d.ts +30 -0
  90. package/dist/variants/mediaQuery.d.ts.map +1 -0
  91. package/dist/variants/spacing.d.ts +26 -0
  92. package/dist/variants/spacing.d.ts.map +1 -0
  93. package/package.json +19 -5
  94. package/src/classes/Cacher.ts +9 -9
  95. package/src/classes/StaleControl.ts +1 -1
  96. package/src/classes/StyleCache.ts +9 -3
  97. package/src/classes/StylePersistor.ts +11 -0
  98. package/src/classes/StyleRegistry.ts +124 -43
  99. package/src/classes/tests/StyleRegistry.spec.ts +169 -0
  100. package/src/constants.ts +14 -0
  101. package/src/hooks/useCompositionStyles.ts +9 -7
  102. package/src/hooks/useNestedStylesByKey.ts +8 -0
  103. package/src/hooks/useStyleObserver.ts +6 -5
  104. package/src/hooks/useTheme.ts +14 -0
  105. package/src/lib/calc.ts +13 -0
  106. package/src/lib/createStyles.ts +35 -4
  107. package/src/lib/createTheme.ts +74 -25
  108. package/src/lib/cssVariables.ts +74 -0
  109. package/src/lib/index.ts +2 -1
  110. package/src/lib/tests/createStyles.spec.ts +80 -23
  111. package/src/lib/tests/createStylesWithContext.spec.ts +108 -0
  112. package/src/tests/theme.ts +6 -2
  113. package/src/theme/generateColorScheme.ts +13 -10
  114. package/src/theme/tests/themeStore.spec.ts +72 -71
  115. package/src/theme/themeStore.ts +10 -7
  116. package/src/theme/validateTheme.ts +1 -1
  117. package/src/tools/colors.ts +24 -36
  118. package/src/tools/deepClone.ts +3 -5
  119. package/src/tools/deepmerge.ts +8 -6
  120. package/src/tools/hashKey.ts +4 -5
  121. package/src/tools/minifier.ts +11 -12
  122. package/src/tools/tests/deepClone.spec.ts +2 -2
  123. package/src/types/cache.ts +10 -0
  124. package/src/types/component.ts +41 -5
  125. package/src/types/core.ts +66 -6
  126. package/src/types/icon.ts +11 -0
  127. package/src/types/spacing.ts +21 -0
  128. package/src/types/store.ts +6 -0
  129. package/src/types/style.ts +37 -10
  130. package/src/types/theme.ts +37 -1
  131. package/src/utils.ts +34 -4
  132. package/src/variants/borderCreator.ts +14 -5
  133. package/src/variants/createAppVariants.ts +11 -0
  134. package/src/variants/defaultVariants.ts +28 -8
  135. package/src/variants/dynamicVariants.ts +76 -18
  136. package/src/variants/mediaQuery.ts +18 -0
  137. package/src/variants/spacing.ts +15 -1
  138. package/package.json.bak +0 -30
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { createStyles, createStylesWithContext, CONTEXT_FACTORY_SYMBOL } from '../createStyles'
3
+ import { themeStore } from '../../theme'
4
+ import { mockTheme, MockedTheme } from '../../tests/theme'
5
+
6
+ describe('createStylesWithContext (web — isBrowser: true)', () => {
7
+ let currentTheme: MockedTheme = null as any
8
+
9
+ beforeEach(() => {
10
+ mockTheme({ isBrowser: true })
11
+ currentTheme = themeStore.theme as unknown as MockedTheme
12
+ })
13
+
14
+ it('exposes the factory function via CONTEXT_FACTORY_SYMBOL', () => {
15
+ const factory = (theme: any, context: { isActive: boolean }) => ({
16
+ wrapper: { color: context.isActive ? theme.colors.neutralSo : theme.colors.secondary },
17
+ })
18
+
19
+ const styles = createStylesWithContext(factory as any)
20
+
21
+ expect((styles as any)[CONTEXT_FACTORY_SYMBOL]).toBe(factory)
22
+ })
23
+
24
+ it('plain createStyles proxy returns undefined for CONTEXT_FACTORY_SYMBOL', () => {
25
+ const styles = createStyles((theme: any) => ({
26
+ wrapper: { color: theme.colors.neutralSo },
27
+ }))
28
+
29
+ expect((styles as any)[CONTEXT_FACTORY_SYMBOL]).toBeUndefined()
30
+ })
31
+
32
+ it('returns expected styles when accessed with default (empty) context', () => {
33
+ const styles = createStylesWithContext((theme: any, context: { multiplier?: number }) => ({
34
+ wrapper: { padding: (context.multiplier ?? 1) * 8 },
35
+ }))
36
+
37
+ expect(styles.wrapper).toEqual({ padding: 8 })
38
+ })
39
+
40
+ it('re-evaluates on every property access (proxy behavior)', () => {
41
+ let callCount = 0
42
+
43
+ const styles = createStylesWithContext((theme: any, context: {}) => {
44
+ callCount++
45
+ return { wrapper: { color: theme.colors.neutralSo } }
46
+ })
47
+
48
+ callCount = 0
49
+
50
+ styles.wrapper
51
+ styles.wrapper
52
+ styles.wrapper
53
+
54
+ expect(callCount).toBe(3)
55
+ })
56
+
57
+ it('reflects theme changes on subsequent accesses', () => {
58
+ const styles = createStylesWithContext((theme: any, _context: {}) => ({
59
+ wrapper: { color: theme.colors.buttonRegularPrimaryBgDefault },
60
+ }))
61
+
62
+ // CSS var reference is static — same regardless of active scheme.
63
+ // The browser CSS cascade handles the actual color switch.
64
+ expect(styles.wrapper).toEqual({ color: 'var(--cl-buttonRegularPrimaryBgDefault)' })
65
+
66
+ currentTheme.setColorScheme('dark')
67
+
68
+ expect(styles.wrapper).toEqual({ color: 'var(--cl-buttonRegularPrimaryBgDefault)' })
69
+ })
70
+
71
+ it('returns undefined for non-existent keys', () => {
72
+ const styles = createStylesWithContext((_theme: any, _context: {}) => ({
73
+ wrapper: { color: 'red' },
74
+ }))
75
+
76
+ expect((styles as any).nonExistent).toBeUndefined()
77
+ })
78
+ })
79
+
80
+ describe('createStylesWithContext (RN — isBrowser: false)', () => {
81
+ let currentTheme: MockedTheme = null as any
82
+
83
+ beforeEach(() => {
84
+ mockTheme({ isBrowser: false })
85
+ currentTheme = themeStore.theme as unknown as MockedTheme
86
+ })
87
+
88
+ it('returns raw RGBA values when isBrowser is false', () => {
89
+ const styles = createStylesWithContext((_theme: any, _context: {}) => ({
90
+ wrapper: { color: _theme.colors.neutralSolid500 },
91
+ }))
92
+
93
+ expect(styles.wrapper).toEqual({ color: 'rgba(136, 136, 136, 1.00)' })
94
+ })
95
+
96
+ it('currentSchemeColors reflects active scheme on RN', () => {
97
+ const styles = createStylesWithContext((_theme: any, _context: {}) => ({
98
+ wrapper: { color: _theme.currentSchemeColors.buttonRegularPrimaryBgDefault },
99
+ }))
100
+
101
+ expect(styles.wrapper).toEqual({ color: 'rgba(43, 105, 122, 1.00)' })
102
+
103
+ currentTheme.setColorScheme('dark')
104
+
105
+ const darkValue = currentTheme.currentSchemeColors.buttonRegularPrimaryBgDefault as string
106
+ expect(styles.wrapper).toEqual({ color: darkValue })
107
+ })
108
+ })
@@ -6,7 +6,11 @@ import darkMode from './colors/darkMode'
6
6
  import { createTheme } from '../lib'
7
7
  import { validateTheme } from '../theme'
8
8
 
9
- export const mockTheme = () => {
9
+ type MockThemeOptions = {
10
+ isBrowser?: boolean
11
+ }
12
+
13
+ export const mockTheme = ({ isBrowser = true }: MockThemeOptions = {}) => {
10
14
  return createTheme(
11
15
  validateTheme({
12
16
  baseColors,
@@ -46,7 +50,7 @@ export const mockTheme = () => {
46
50
  icons: {},
47
51
  presets: {},
48
52
  values: {},
49
- isBrowser: true,
53
+ isBrowser,
50
54
  }),
51
55
  {
52
56
  set: (name, colorSchema) => {},
@@ -20,25 +20,28 @@ const defaultAlphasMap = Object.fromEntries(
20
20
  )
21
21
 
22
22
  /**
23
- * Generates a complete color scheme from an anchor color.
24
- * Creates solid variants with different lightness and transparent variants.
25
- *
26
- * @param {string} anchorHex - Base hex color for the scheme
27
- * @param {string} prefix - Prefix for generated color names
28
- * @param {object} lightnesses - Custom lightness mapping
29
- * @param {object} alphas - Custom alpha mapping
30
- * @returns {ColorMap} Complete color scheme object
23
+ * Derives a 20-token color ramp from a single anchor hex color.
24
+ *
25
+ * Produces two groups of tokens:
26
+ * - **10 solid tokens** (`{prefix}Solid100` `{prefix}Solid1000`) vary the lightness
27
+ * of the anchor's hue/saturation from ~95 L (lightest) to ~10 L (darkest).
28
+ * - **10 transparent tokens** (`{prefix}Transparent100` `{prefix}Transparent1000`)
29
+ * keep the anchor's exact RGB channels but increase alpha from 0.05 to 0.90.
30
+ *
31
+ * Default lightness steps: `[95, 85, 75, 60, 45, 30, 27, 21, 16, 10]`.
32
+ * Default alpha steps: `[0.05, 0.10, …, 0.90]`.
33
+ * Both can be overridden by passing custom maps (step → value).
31
34
  */
32
35
  export function generateColorScheme(
33
36
  anchorHex: string,
34
37
  prefix = 'primary',
35
38
  lightnesses:typeof defaultLightnessMap = defaultLightnessMap,
36
39
  alphas: typeof defaultAlphasMap = defaultAlphasMap,
37
- ): ColorMap {
40
+ ): Record<string, string> {
38
41
  const { h, s } = colorTools.hexToHSL(anchorHex)
39
42
  const baseRGB = colorTools.hexToRGB(anchorHex)
40
43
 
41
- const scheme: ColorMap = {}
44
+ const scheme: Record<string, string> = {}
42
45
 
43
46
  Object.entries(lightnesses).forEach(([step, lightness]) => {
44
47
  const rgb = colorTools.hslToRGB(h, s, lightness)
@@ -14,7 +14,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
14
14
  it('should handle theme management', () => {
15
15
  const mockTheme = {
16
16
  colors: { primary: '#007bff' },
17
- spacing: { small: '8px' }
17
+ spacing: { small: '8px' },
18
18
  }
19
19
 
20
20
  store.setTheme(mockTheme)
@@ -39,21 +39,21 @@ describe('ThemeStore - Heavy Load Tests', () => {
39
39
  secondary: `#${(i * 2).toString(16).padStart(6, '0')}`,
40
40
  background: `#${(i * 3).toString(16).padStart(6, '0')}`,
41
41
  surface: `#${(i * 4).toString(16).padStart(6, '0')}`,
42
- text: `#${(i * 5).toString(16).padStart(6, '0')}`
42
+ text: `#${(i * 5).toString(16).padStart(6, '0')}`,
43
43
  },
44
44
  spacing: {
45
45
  xs: `${i}px`,
46
46
  sm: `${i * 2}px`,
47
47
  md: `${i * 4}px`,
48
48
  lg: `${i * 8}px`,
49
- xl: `${i * 16}px`
49
+ xl: `${i * 16}px`,
50
50
  },
51
51
  typography: {
52
52
  fontSize: `${i}px`,
53
53
  fontWeight: i % 900,
54
- lineHeight: i / 100
54
+ lineHeight: i / 100,
55
55
  },
56
- iteration: i
56
+ iteration: i,
57
57
  }
58
58
 
59
59
  store.setTheme(theme)
@@ -75,27 +75,27 @@ describe('ThemeStore - Heavy Load Tests', () => {
75
75
  })
76
76
 
77
77
  it('should handle massive color schemes with deep nesting', () => {
78
- const colorSchemes = {}
78
+ const colorSchemes: Record<string, any> = {}
79
79
  const schemeCount = 1000
80
80
  const colorsPerScheme = 100
81
81
 
82
82
  // Generate massive color schemes
83
83
  for (let scheme = 0; scheme < schemeCount; scheme++) {
84
- const colors = {}
84
+ const colors: Record<string, any> = {}
85
85
  for (let color = 0; color < colorsPerScheme; color++) {
86
86
  colors[`color_${color}`] = {
87
87
  primary: `#${(scheme * color).toString(16).padStart(6, '0')}`,
88
88
  variants: {
89
89
  light: `#${(scheme * color + 100000).toString(16).padStart(6, '0')}`,
90
90
  dark: `#${(scheme * color + 200000).toString(16).padStart(6, '0')}`,
91
- medium: `#${(scheme * color + 300000).toString(16).padStart(6, '0')}`
91
+ medium: `#${(scheme * color + 300000).toString(16).padStart(6, '0')}`,
92
92
  },
93
93
  states: {
94
94
  hover: `#${(scheme * color + 400000).toString(16).padStart(6, '0')}`,
95
95
  active: `#${(scheme * color + 500000).toString(16).padStart(6, '0')}`,
96
96
  disabled: `#${(scheme * color + 600000).toString(16).padStart(6, '0')}`,
97
- focus: `#${(scheme * color + 700000).toString(16).padStart(6, '0')}`
98
- }
97
+ focus: `#${(scheme * color + 700000).toString(16).padStart(6, '0')}`,
98
+ },
99
99
  }
100
100
  }
101
101
  colorSchemes[`scheme_${scheme}`] = colors
@@ -120,8 +120,8 @@ describe('ThemeStore - Heavy Load Tests', () => {
120
120
  base: {
121
121
  primary: '#000000',
122
122
  secondary: '#111111',
123
- tertiary: '#222222'
124
- }
123
+ tertiary: '#222222',
124
+ },
125
125
  })
126
126
 
127
127
  for (let i = 0; i < operations; i++) {
@@ -130,14 +130,15 @@ describe('ThemeStore - Heavy Load Tests', () => {
130
130
  primary: `#${i.toString(16).padStart(6, '0')}`,
131
131
  accent: `#${(i * 2).toString(16).padStart(6, '0')}`,
132
132
  background: `#${(i * 3).toString(16).padStart(6, '0')}`,
133
- customColor: `#${(i * 4).toString(16).padStart(6, '0')}`
133
+ customColor: `#${(i * 4).toString(16).padStart(6, '0')}`,
134
134
  }
135
135
 
136
136
  // Inject scheme
137
137
  const injected = store.injectColorScheme(schemeName, colors)
138
- expect(injected[schemeName]).toBeDefined()
139
- expect(injected[schemeName].primary).toBe(colors.primary)
140
- expect(injected[schemeName].secondary).toBe('#111111') // inherited
138
+ const scheme = injected[schemeName] as Record<string, string>
139
+ expect(scheme).toBeDefined()
140
+ expect(scheme.primary).toBe(colors.primary)
141
+ expect(scheme.secondary).toBe('#111111') // inherited
141
142
 
142
143
  // Every 10th operation, eject some schemes
143
144
  if (i % 10 === 0 && i > 0) {
@@ -150,17 +151,17 @@ describe('ThemeStore - Heavy Load Tests', () => {
150
151
  const endTime = performance.now()
151
152
  const executionTime = endTime - startTime
152
153
 
153
- expect(executionTime).toBeLessThan(3000) // Should be fast
154
+ expect(executionTime).toBeLessThan(3500) // Should be fast
154
155
  console.log(`Color scheme operations: ${operations} inject/eject operations in ${executionTime.toFixed(2)}ms`)
155
156
  })
156
157
 
157
158
  it('should handle massive variants with complex structures', () => {
158
159
  const componentCount = 500
159
160
  const variantsPerComponent = 50
160
- const variants = {}
161
+ const variants: Record<string, any> = {}
161
162
 
162
163
  for (let comp = 0; comp < componentCount; comp++) {
163
- const componentVariants = {}
164
+ const componentVariants: Record<string, any> = {}
164
165
  for (let variant = 0; variant < variantsPerComponent; variant++) {
165
166
  componentVariants[`variant_${variant}`] = {
166
167
  className: `comp-${comp}-var-${variant}`,
@@ -172,14 +173,14 @@ describe('ThemeStore - Heavy Load Tests', () => {
172
173
  margin: `${comp * variant}px`,
173
174
  borderRadius: `${variant / 2}px`,
174
175
  transform: `rotate(${variant}deg) scale(${1 + comp / 100})`,
175
- boxShadow: `${variant}px ${comp}px ${comp + variant}px rgba(${comp}, ${variant}, ${comp + variant}, 0.${variant})`
176
+ boxShadow: `${variant}px ${comp}px ${comp + variant}px rgba(${comp}, ${variant}, ${comp + variant}, 0.${variant})`,
176
177
  },
177
178
  states: {
178
179
  hover: { opacity: 0.8 + (variant / 100) },
179
180
  active: { transform: `scale(${1 + variant / 1000})` },
180
181
  disabled: { opacity: 0.5 },
181
- focus: { outline: `${variant}px solid #${comp.toString(16).padStart(6, '0')}` }
182
- }
182
+ focus: { outline: `${variant}px solid #${comp.toString(16).padStart(6, '0')}` },
183
+ },
183
184
  }
184
185
  }
185
186
  variants[`component_${comp}`] = componentVariants
@@ -206,7 +207,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
206
207
  // Simultaneous updates
207
208
  store.setTheme({
208
209
  colors: { primary: `#${i.toString(16).padStart(6, '0')}` },
209
- iteration: i
210
+ iteration: i,
210
211
  })
211
212
 
212
213
  store.setColorScheme(`scheme_${i % 50}`)
@@ -215,21 +216,21 @@ describe('ThemeStore - Heavy Load Tests', () => {
215
216
  [`component_${i}`]: {
216
217
  [`variant_${i}`]: {
217
218
  className: `test-${i}`,
218
- styles: { color: `#${i.toString(16).padStart(6, '0')}` }
219
- }
220
- }
219
+ styles: { color: `#${i.toString(16).padStart(6, '0')}` },
220
+ },
221
+ },
221
222
  })
222
223
 
223
224
  store.injectColorScheme(`dynamic_${i % 100}`, {
224
225
  primary: `#${(i * 2).toString(16).padStart(6, '0')}`,
225
- secondary: `#${(i * 3).toString(16).padStart(6, '0')}`
226
+ secondary: `#${(i * 3).toString(16).padStart(6, '0')}`,
226
227
  })
227
228
 
228
229
  // Verify consistency every 200 iterations
229
230
  if (i % 200 === 0) {
230
231
  expect(getThemeStore()?.iteration).toBe(i)
231
232
  expect(store.colorScheme).toBe(`scheme_${i % 50}`)
232
- expect(store.variants[`component_${i}`]).toBeDefined()
233
+ expect((store.variants as any)[`component_${i}`]).toBeDefined()
233
234
  }
234
235
  }
235
236
 
@@ -246,12 +247,12 @@ describe('ThemeStore - Heavy Load Tests', () => {
246
247
  describe('function storage and execution stress tests', () => {
247
248
  it('should handle thousands of stored functions with complex computations', () => {
248
249
  const functionCount = 5000
249
- const theme = {
250
+ const theme: Record<string, Record<string, any>> = {
250
251
  colors: {},
251
252
  spacing: {},
252
253
  calculators: {},
253
254
  transformers: {},
254
- validators: {}
255
+ validators: {},
255
256
  }
256
257
 
257
258
  const startTime = performance.now()
@@ -259,23 +260,23 @@ describe('ThemeStore - Heavy Load Tests', () => {
259
260
  // Store various types of functions
260
261
  for (let i = 0; i < functionCount; i++) {
261
262
  // Simple calculators
262
- theme.calculators[`calc_${i}`] = (value) => value * (i + 1)
263
+ theme.calculators[`calc_${i}`] = (value: any) => value * (i + 1)
263
264
 
264
265
  // Complex transformers
265
- theme.transformers[`transform_${i}`] = (input) => {
266
+ theme.transformers[`transform_${i}`] = (input: any) => {
266
267
  return {
267
268
  scaled: input * Math.pow(2, i % 10),
268
269
  offset: input + (i * 3.14159),
269
270
  computed: Math.sin(input + i) * 100,
270
271
  conditional: input > i ? input * 2 : input / 2,
271
- recursive: i > 0 ? theme.calculators[`calc_${i - 1}`]?.(input) || input : input
272
+ recursive: i > 0 ? theme.calculators[`calc_${i - 1}`]?.(input) || input : input,
272
273
  }
273
274
  }
274
275
 
275
276
  // Spacing functions
276
277
  theme.spacing[`space_${i}`] = (multiplier = 1) => `${i * 8 * multiplier}px`
277
278
 
278
- // Color functions
279
+ // Color functions
279
280
  theme.colors[`color_${i}`] = (opacity = 1) => {
280
281
  const r = (i * 37) % 256
281
282
  const g = (i * 73) % 256
@@ -284,7 +285,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
284
285
  }
285
286
 
286
287
  // Validators
287
- theme.validators[`validate_${i}`] = (value) => {
288
+ theme.validators[`validate_${i}`] = (value: any) => {
288
289
  return value !== null &&
289
290
  value !== undefined &&
290
291
  (typeof value === 'number' ? value >= i : value.length >= i % 10)
@@ -324,7 +325,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
324
325
  // Test some complex chaining every 100 iterations
325
326
  if (i % 100 === 0) {
326
327
  const chainResult = getThemeStore()?.transformers[`transform_${i}`]?.(
327
- getThemeStore()?.calculators[`calc_${i}`]?.(testValue) || 0
328
+ getThemeStore()?.calculators[`calc_${i}`]?.(testValue) || 0,
328
329
  )
329
330
  if (chainResult) executionResults++
330
331
  }
@@ -347,15 +348,15 @@ describe('ThemeStore - Heavy Load Tests', () => {
347
348
 
348
349
  // Create deeply nested function chains
349
350
  const theme = {
350
- fibonacci: {},
351
- factorial: {},
352
- compose: {},
353
- pipeline: {}
351
+ fibonacci: {} as Record<string, any>,
352
+ factorial: {} as Record<string, any>,
353
+ compose: {} as Record<string, any>,
354
+ pipeline: {} as Record<string, any>,
354
355
  }
355
356
 
356
357
  // Fibonacci functions
357
358
  for (let i = 0; i < depth; i++) {
358
- theme.fibonacci[`fib_${i}`] = (n) => {
359
+ theme.fibonacci[`fib_${i}`] = (n: any) => {
359
360
  if (n <= 1) return n
360
361
  if (i > 0 && theme.fibonacci[`fib_${i - 1}`]) {
361
362
  return theme.fibonacci[`fib_${i - 1}`](n - 1) + theme.fibonacci[`fib_${i - 1}`](n - 2)
@@ -366,7 +367,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
366
367
 
367
368
  // Factorial chains
368
369
  for (let i = 0; i < depth; i++) {
369
- theme.factorial[`fact_${i}`] = (n) => {
370
+ theme.factorial[`fact_${i}`] = (n: any) => {
370
371
  if (n <= 1) return 1
371
372
  const prevFact = theme.factorial[`fact_${Math.max(0, i - 1)}`]
372
373
  return prevFact ? n * prevFact(n - 1) : n
@@ -375,7 +376,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
375
376
 
376
377
  // Function composition
377
378
  for (let i = 0; i < depth; i++) {
378
- theme.compose[`comp_${i}`] = (value) => {
379
+ theme.compose[`comp_${i}`] = (value: any) => {
379
380
  let result = value
380
381
  for (let j = 0; j <= i && j < 10; j++) {
381
382
  result = Math.sqrt(Math.abs(result * (j + 1)))
@@ -386,13 +387,13 @@ describe('ThemeStore - Heavy Load Tests', () => {
386
387
 
387
388
  // Pipeline functions
388
389
  for (let i = 0; i < depth; i++) {
389
- theme.pipeline[`pipe_${i}`] = (input) => {
390
+ theme.pipeline[`pipe_${i}`] = (input: any) => {
390
391
  const steps = [
391
- (x) => x * 2,
392
- (x) => x + i,
393
- (x) => Math.pow(x, 1.5),
394
- (x) => x % 1000,
395
- (x) => theme.compose[`comp_${Math.min(i, depth - 1)}`]?.(x) || x
392
+ (x: any) => x * 2,
393
+ (x: any) => x + i,
394
+ (x: any) => Math.pow(x, 1.5),
395
+ (x: any) => x % 1000,
396
+ (x: any) => theme.compose[`comp_${Math.min(i, depth - 1)}`]?.(x) || x,
396
397
  ]
397
398
  return steps.reduce((acc, fn) => fn(acc), input)
398
399
  }
@@ -427,7 +428,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
427
428
  // Test chained execution
428
429
  if (i % 50 === 0) {
429
430
  const chainedResult = getThemeStore()?.pipeline[`pipe_${i % depth}`]?.(
430
- getThemeStore()?.compose[`comp_${i % depth}`]?.(testValue) || 0
431
+ getThemeStore()?.compose[`comp_${i % depth}`]?.(testValue) || 0,
431
432
  )
432
433
  if (typeof chainedResult === 'number') successfulExecutions++
433
434
  }
@@ -449,27 +450,27 @@ describe('ThemeStore - Heavy Load Tests', () => {
449
450
  const factoryCount = 1000
450
451
  const generatedPerFactory = 10
451
452
 
452
- const theme = {
453
+ const theme: Record<string, Record<string, any>> = {
453
454
  factories: {},
454
455
  generated: {},
455
- dynamicComputed: {}
456
+ dynamicComputed: {},
456
457
  }
457
458
 
458
459
  const startTime = performance.now()
459
460
 
460
461
  // Create function factories
461
462
  for (let i = 0; i < factoryCount; i++) {
462
- theme.factories[`factory_${i}`] = (config) => {
463
+ theme.factories[`factory_${i}`] = (config: any) => {
463
464
  return {
464
- calculator: (value) => value * (config.multiplier || 1) + (config.offset || 0),
465
- validator: (value) => value >= (config.min || 0) && value <= (config.max || 1000),
466
- transformer: (value) => ({
465
+ calculator: (value: any) => value * (config.multiplier || 1) + (config.offset || 0),
466
+ validator: (value: any) => value >= (config.min || 0) && value <= (config.max || 1000),
467
+ transformer: (value: any) => ({
467
468
  original: value,
468
469
  scaled: value * (config.scale || 1),
469
470
  formatted: `${config.prefix || ''}${value}${config.suffix || ''}`,
470
- computed: Math.pow(value, config.power || 1)
471
+ computed: Math.pow(value, config.power || 1),
471
472
  }),
472
- composer: (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value)
473
+ composer: (...fns: any[]) => (value: any) => fns.reduce((acc, fn) => fn(acc), value),
473
474
  }
474
475
  }
475
476
 
@@ -483,14 +484,14 @@ describe('ThemeStore - Heavy Load Tests', () => {
483
484
  max: i * 100,
484
485
  prefix: `gen_${i}_`,
485
486
  suffix: `_${j}`,
486
- power: 1 + (j / 5)
487
+ power: 1 + (j / 5),
487
488
  }
488
489
 
489
490
  const generatedFunctions = theme.factories[`factory_${i}`](config)
490
491
  theme.generated[`gen_${i}_${j}`] = generatedFunctions
491
492
 
492
493
  // Create dynamic computed values
493
- theme.dynamicComputed[`comp_${i}_${j}`] = (input) => {
494
+ theme.dynamicComputed[`comp_${i}_${j}`] = (input: any) => {
494
495
  const calc = generatedFunctions.calculator(input)
495
496
  const valid = generatedFunctions.validator(calc)
496
497
  const transform = generatedFunctions.transformer(calc)
@@ -499,7 +500,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
499
500
  calculated: calc,
500
501
  isValid: valid,
501
502
  transformed: transform,
502
- final: valid ? transform.computed : 0
503
+ final: valid ? transform.computed : 0,
503
504
  }
504
505
  }
505
506
  }
@@ -543,9 +544,9 @@ describe('ThemeStore - Heavy Load Tests', () => {
543
544
  const generated = getThemeStore()?.generated[`gen_${i}_${j}`]
544
545
  if (generated?.composer) {
545
546
  const composed = generated.composer(
546
- (x) => x * 2,
547
- (x) => x + 10,
548
- generated.calculator
547
+ (x: any) => x * 2,
548
+ (x: any) => x + 10,
549
+ generated.calculator,
549
550
  )(testValue)
550
551
  if (typeof composed === 'number') executionCount++
551
552
  }
@@ -577,11 +578,11 @@ describe('ThemeStore - Heavy Load Tests', () => {
577
578
 
578
579
  for (let set = 0; set < hugeSets; set++) {
579
580
  // Create massive theme
580
- const hugeTheme = {
581
+ const hugeTheme: Record<string, Record<string, any>> = {
581
582
  colors: {},
582
583
  spacing: {},
583
584
  typography: {},
584
- components: {}
585
+ components: {},
585
586
  }
586
587
 
587
588
  for (let i = 0; i < itemsPerSet; i++) {
@@ -590,16 +591,16 @@ describe('ThemeStore - Heavy Load Tests', () => {
590
591
  hugeTheme.typography[`font_${i}`] = `${i}px`
591
592
  hugeTheme.components[`comp_${i}`] = {
592
593
  styles: { margin: `${i}px` },
593
- variants: Array.from({ length: 10 }, (_, j) => `variant_${j}`)
594
+ variants: Array.from({ length: 10 }, (_, j) => `variant_${j}`),
594
595
  }
595
596
  }
596
597
 
597
598
  store.setTheme(hugeTheme)
598
599
 
599
600
  // Create massive color schemes
600
- const hugeColorSchemes = {}
601
+ const hugeColorSchemes: Record<string, any> = {}
601
602
  for (let i = 0; i < itemsPerSet; i++) {
602
- const scheme = {}
603
+ const scheme: Record<string, any> = {}
603
604
  for (let j = 0; j < 50; j++) {
604
605
  scheme[`color_${j}`] = `#${(i * j).toString(16).padStart(6, '0')}`
605
606
  }
@@ -630,7 +631,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
630
631
  for (let i = 0; i < updates; i++) {
631
632
  themeStore.setTheme({
632
633
  colors: { primary: `#${i.toString(16).padStart(6, '0')}` },
633
- iteration: i
634
+ iteration: i,
634
635
  })
635
636
  themeStore.setColorScheme(`scheme_${i % 100}`)
636
637
 
@@ -681,7 +682,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
681
682
  for (let i = 0; i < updates; i++) {
682
683
  themeStore.setTheme({
683
684
  colors: { primary: `#${i.toString(16).padStart(6, '0')}` },
684
- iteration: i
685
+ iteration: i,
685
686
  })
686
687
  }
687
688
 
@@ -5,7 +5,7 @@ import { map, computed, atom } from 'nanostores'
5
5
  * Theme state interface containing theme and color scheme information.
6
6
  */
7
7
  export type ThemeState = {
8
- theme: AppTheme<Theme> | null
8
+ theme: ITheme | null
9
9
  colorScheme: string | null
10
10
  }
11
11
 
@@ -18,9 +18,9 @@ export class ThemeStore {
18
18
 
19
19
  public readonly colorSchemeStore = atom<string | null>(null)
20
20
 
21
- public readonly themeStore = map<ITheme | null>(null)
22
-
23
- public readonly variantsStore = map<IAppVariants>({})
21
+ public readonly themeStore = atom<ITheme | null>(null)
22
+
23
+ public readonly variantsStore = map<IAppVariants>({} as IAppVariants)
24
24
 
25
25
  /**
26
26
  * Gets the current theme.
@@ -35,7 +35,8 @@ export class ThemeStore {
35
35
  * @returns {AppTheme<Theme>} Current theme cast to AppTheme type
36
36
  */
37
37
  get themeTyped() {
38
- return this.themeStore.get() as unknown as AppTheme<Theme>
38
+ const theme = this.themeStore.get()
39
+ return theme ? theme as unknown as AppTheme<Theme> : null
39
40
  }
40
41
 
41
42
  /**
@@ -159,8 +160,10 @@ export class ThemeStore {
159
160
  export const themeStore = new ThemeStore()
160
161
 
161
162
  /**
162
- * Computed store that combines theme and color scheme for reactive updates.
163
- * @returns {ThemeState} Combined theme state with theme and colorScheme
163
+ * Derived nanostores `computed` that combines `themeStore` and `colorSchemeStore`
164
+ * into a single `ThemeState` atom. Components subscribe to this via `useTheme`
165
+ * so they re-render only when either the theme object or the active color scheme
166
+ * changes — not on every `ThemeStore` method call.
164
167
  */
165
168
  export const themeStoreComputed = computed([
166
169
  themeStore.themeStore,
@@ -22,7 +22,7 @@ export function validateTheme<T extends Theme>(theme: T) {
22
22
 
23
23
  const requiredColors = Object.keys(colors)
24
24
 
25
- const mergedAlternateColors = {}
25
+ const mergedAlternateColors: Record<string, any> = {}
26
26
 
27
27
  if (alternateColors) {
28
28
  for (const [colorSchemeName, colorSchemeColors] of Object.entries(alternateColors)) {