@codeleap/styles 6.3.0 → 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 +38 -37
  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)
@@ -75,13 +75,13 @@ 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')}`,
@@ -135,9 +135,10 @@ describe('ThemeStore - Heavy Load Tests', () => {
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) {
@@ -157,10 +158,10 @@ describe('ThemeStore - Heavy Load Tests', () => {
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}`,
@@ -229,7 +230,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
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,7 +247,7 @@ 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: {},
@@ -259,10 +260,10 @@ 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),
@@ -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)
@@ -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
  }
@@ -449,7 +450,7 @@ 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
456
  dynamicComputed: {},
@@ -459,17 +460,17 @@ describe('ThemeStore - Heavy Load Tests', () => {
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
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
 
@@ -490,7 +491,7 @@ describe('ThemeStore - Heavy Load Tests', () => {
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)
@@ -543,8 +544,8 @@ 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,
547
+ (x: any) => x * 2,
548
+ (x: any) => x + 10,
548
549
  generated.calculator,
549
550
  )(testValue)
550
551
  if (typeof composed === 'number') executionCount++
@@ -577,7 +578,7 @@ 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: {},
@@ -597,9 +598,9 @@ describe('ThemeStore - Heavy Load Tests', () => {
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
  }
@@ -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)) {
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Converts a hex color to HSL format.
3
- *
4
- * @param {string} hex - Hex color string (e.g., '#ff5733')
5
- * @returns {object} HSL object with h (0-360), s (0-100), l (0-100)
2
+ * Expects a 7-character hex string (`#rrggbb`). Shorter forms (3-char, no hash) are
3
+ * not handled and will produce NaN channels. Returns h in [0, 360], s and l in [0, 100],
4
+ * all rounded to integers via `Math.round`.
6
5
  */
7
6
  export function hexToHSL(hex: string) {
8
7
  const r = parseInt(hex.slice(1, 3), 16) / 255
@@ -31,12 +30,10 @@ export function hexToHSL(hex: string) {
31
30
  }
32
31
 
33
32
  /**
34
- * Converts HSL values to hex color format.
35
- *
36
- * @param {number} h - Hue (0-360)
37
- * @param {number} s - Saturation (0-100)
38
- * @param {number} l - Lightness (0-100)
39
- * @returns {string} Hex color string
33
+ * Uses the HSL-to-RGB CSS Color 4 formula (single-pass, no separate hue helper).
34
+ * Inputs h in [0, 360], s and l in [0, 100]; channels are clamped implicitly by
35
+ * `Math.min`/`Math.max`. Each channel is individually rounded before hex encoding,
36
+ * so the round-trip `hexToHSL → hslToHex` may differ by ±1 in the last hex digit.
40
37
  */
41
38
  export function hslToHex(h: number, s: number, l: number): string {
42
39
  s /= 100
@@ -50,10 +47,8 @@ export function hslToHex(h: number, s: number, l: number): string {
50
47
  }
51
48
 
52
49
  /**
53
- * Converts a hex color to RGB format.
54
- *
55
- * @param {string} hex - Hex color string (e.g., '#ff5733')
56
- * @returns {object} RGB object with r, g, b values (0-255)
50
+ * Parses only the 6-digit `#rrggbb` form via `parseInt(..., 16)`.
51
+ * No alpha channel is extracted. Channels are returned as integers in [0, 255].
57
52
  */
58
53
  export function hexToRGB(hex: string) {
59
54
  const r = parseInt(hex.slice(1, 3), 16)
@@ -63,12 +58,9 @@ export function hexToRGB(hex: string) {
63
58
  }
64
59
 
65
60
  /**
66
- * Converts HSL values to RGB format.
67
- *
68
- * @param {number} h - Hue (0-360)
69
- * @param {number} s - Saturation (0-100)
70
- * @param {number} l - Lightness (0-100)
71
- * @returns {object} RGB object with r, g, b values (0-255)
61
+ * Shares the same formula as `hslToHex` but returns separate integer r/g/b channels
62
+ * instead of a hex string. Useful when you need numeric channels for `rgba(...)` output.
63
+ * Input ranges: h [0, 360], s [0, 100], l [0, 100].
72
64
  */
73
65
  export function hslToRGB(h: number, s: number, l: number) {
74
66
  s /= 100
@@ -86,10 +78,9 @@ export function hslToRGB(h: number, s: number, l: number) {
86
78
  }
87
79
 
88
80
  /**
89
- * Converts RGB values to HSL format.
90
- *
91
- * @param {object} rgb - RGB object with r, g, b values (0-255)
92
- * @returns {object} HSL object with h (0-360), s (0-100), l (0-100)
81
+ * Inverse of `hslToRGB`. Normalises each channel from [0, 255] to [0, 1] before
82
+ * computing. When max === min (achromatic), hue is fixed at 0. All output values are
83
+ * rounded, so the round-trip `hslToRGB rgbToHSL` is not guaranteed to be lossless.
93
84
  */
94
85
  export function rgbToHSL(rgb: { r: number; g: number; b: number }): { h: number; s: number; l: number } {
95
86
  const r = rgb.r / 255
@@ -129,11 +120,11 @@ export function rgbToHSL(rgb: { r: number; g: number; b: number }): { h: number;
129
120
  }
130
121
 
131
122
  /**
132
- * Calculates the relative luminance of an RGB color.
133
- * Uses the WCAG formula for accessibility calculations.
134
- *
135
- * @param {object} rgb - RGB object with r, g, b values (0-255)
136
- * @returns {number} Luminance value (0-1)
123
+ * Implements WCAG 2.x relative luminance (https://www.w3.org/TR/WCAG20/#relativeluminancedef).
124
+ * Channels are linearised with the sRGB transfer function: values ≤ 0.03928 use
125
+ * the linear segment (C / 12.92); values above use the gamma curve ((C + 0.055) / 1.055)^2.4.
126
+ * The luminance coefficients are 0.2126 R + 0.7152 G + 0.0722 B, reflecting the
127
+ * eye's greater sensitivity to green. Returns a value in [0, 1].
137
128
  */
138
129
  export function getLuminance({ r, g, b }: { r: number; g: number; b: number }): number {
139
130
  const [R, G, B] = [r, g, b].map(c => {
@@ -147,13 +138,10 @@ export function getLuminance({ r, g, b }: { r: number; g: number; b: number }):
147
138
  }
148
139
 
149
140
  /**
150
- * Determines the best text color (dark or light) for a given background.
151
- * Uses luminance calculation for optimal contrast.
152
- *
153
- * @param {string} backgroundHex - Background hex color
154
- * @param {string} darkColor - Dark text color option
155
- * @param {string} lightColor - Light text color option
156
- * @returns {string} Recommended text color
141
+ * Uses a luminance threshold of **0.5** (not the WCAG 4.5:1 contrast ratio) to pick
142
+ * between two text colours. Backgrounds with luminance > 0.5 are treated as light and
143
+ * receive `darkColor`; all others receive `lightColor`. This is a perceptual shortcut —
144
+ * use explicit contrast-ratio checks for accessibility-critical situations.
157
145
  */
158
146
  export function getTextColor(backgroundHex: string, darkColor = 'black', lightColor = 'white'): string {
159
147
  const rgb = hexToRGB(backgroundHex)
@@ -1,10 +1,8 @@
1
1
  import rfdc from 'rfdc'
2
2
 
3
3
  /**
4
- * Creates a deep clone of any JavaScript object or value.
5
- * Uses rfdc (Really Fast Deep Clone) for optimal performance.
6
- *
7
- * @param {T} obj - The object or value to clone
8
- * @returns {T} A deep copy of the input
4
+ * rfdc clone function configured with default options (no circular-reference support,
5
+ * proto: false). Does not handle `Date`, `RegExp`, or `Buffer` fields — they are copied
6
+ * by reference. Use only on plain style/theme objects.
9
7
  */
10
8
  export const deepClone = rfdc()
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Deep merge constructor from Fastify's optimized implementation.
3
- * Returns a merge function that recursively merges objects with high performance.
4
- * By default, arrays are concatenated and objects are deeply merged.
5
- *
2
+ * Re-exports `@fastify/deepmerge`'s factory function. Call it once with options
3
+ * (e.g., `deepmerge({ all: true })`) to obtain a variadic merge function.
4
+ * Passing `{ all: true }` merges arrays by index rather than concatenating them
5
+ * this is the mode used throughout the style registry for variant merging.
6
+ * The returned merger is not idempotent: source properties always overwrite target.
7
+ *
6
8
  * @example
7
- * const merger = deepmerge()
8
- * const result = merger(target, source)
9
+ * const merge = deepmerge({ all: true })
10
+ * const result = merge(base, override1, override2)
9
11
  */
10
12
  export { default as deepmerge } from '@fastify/deepmerge'
@@ -4,11 +4,10 @@ const styleKey = '@styles-version'
4
4
  const version = require('../../package.json')?.version
5
5
 
6
6
  /**
7
- * Generates a SHA-256 hash from an array with automatic version injection.
8
- * Appends package version to the array for cache invalidation purposes.
9
- *
10
- * @param {Array<any>} value - Array to be hashed
11
- * @returns {string} SHA-256 hash string
7
+ * Mutates the input array by appending `{ '@styles-version': <pkg.version> }` before
8
+ * hashing meaning callers must not rely on the array being unchanged after the call.
9
+ * The version injection ensures any cache key computed against an older package version
10
+ * is automatically invalid after a library upgrade.
12
11
  */
13
12
  export const hashKey = (value: Array<any>): string => {
14
13
  value.push({ [styleKey]: version })
@@ -1,11 +1,11 @@
1
1
  import { compressToBase64, decompressFromBase64 } from 'lz-string'
2
2
 
3
3
  /**
4
- * Compresses any value to a Base64 string using LZ compression.
5
- * Returns the original value if falsy.
6
- *
7
- * @param {any} value - Value to compress
8
- * @returns {string|any} Compressed Base64 string or original falsy value
4
+ * Serialises `value` with `JSON.stringify` then LZ-compresses to a Base64 string.
5
+ * Returns the value unchanged (without throwing) when it is falsy, so callers
6
+ * do not need to guard against `null`/`undefined`/`''` inputs.
7
+ * Non-serialisable values (functions, `undefined` inside objects, circular refs)
8
+ * will be silently dropped by `JSON.stringify`.
9
9
  */
10
10
  export function compress(value: any): any {
11
11
  if (!value) return value
@@ -14,11 +14,10 @@ export function compress(value: any): any {
14
14
  }
15
15
 
16
16
  /**
17
- * Decompresses a Base64 string back to its original value.
18
- * Returns the original value if falsy.
19
- *
20
- * @param {any} value - Compressed Base64 string to decompress
21
- * @returns {any} Original decompressed value or original falsy value
17
+ * Reverses `compress`: LZ-decompresses the Base64 string then parses the JSON.
18
+ * Returns the value unchanged when falsy. Will throw if `value` is a non-empty
19
+ * string that is not valid LZ-Base64, or if the decompressed payload is not
20
+ * valid JSON callers should guard accordingly.
22
21
  */
23
22
  export function decompress(value: any): any {
24
23
  if (!value) return value
@@ -29,8 +28,8 @@ export function decompress(value: any): any {
29
28
  }
30
29
 
31
30
  /**
32
- * Utility object containing compress and decompress functions.
33
- * Useful for data compression/decompression operations.
31
+ * Convenience namespace so callsites can import a single symbol and call
32
+ * `minifier.compress` / `minifier.decompress` without named imports.
34
33
  */
35
34
  export const minifier = {
36
35
  compress,
@@ -44,8 +44,8 @@ describe('deepClone', () => {
44
44
  expect(cloned).not.toBe(original)
45
45
  expect(cloned[2]).not.toBe(original[2])
46
46
 
47
- cloned[2][0] = 5
48
- expect(original[2][0]).toBe(3)
47
+ ;(cloned[2] as any)[0] = 5
48
+ expect((original[2] as any)[0]).toBe(3)
49
49
  })
50
50
 
51
51
  test('should handle complex nested structures', () => {