@codeleap/styles 5.8.2 → 5.8.3

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 (69) hide show
  1. package/package.json +9 -15
  2. package/package.json.bak +8 -14
  3. package/src/classes/Cacher.ts +169 -0
  4. package/src/classes/StaleControl.ts +125 -0
  5. package/src/classes/StyleCache.ts +116 -0
  6. package/src/classes/StylePersistor.ts +62 -0
  7. package/src/{lib → classes}/StyleRegistry.ts +7 -12
  8. package/src/classes/index.ts +2 -0
  9. package/src/classes/tests/Cache.spec.ts +371 -0
  10. package/src/classes/tests/StaleControl.spec.ts +175 -0
  11. package/src/classes/tests/StyleCache.spec.ts +452 -0
  12. package/src/classes/tests/StylePersistor.spec.ts +231 -0
  13. package/src/{lib/constants.ts → constants.ts} +1 -0
  14. package/src/hooks/index.ts +4 -0
  15. package/src/hooks/tests/useCompositionStyles.spec.ts +107 -0
  16. package/src/hooks/tests/useStyleObserver.spec.ts +89 -0
  17. package/src/hooks/useCompositionStyles.ts +33 -0
  18. package/src/hooks/useNestedStylesByKey.ts +13 -0
  19. package/src/hooks/useStyleObserver.ts +19 -0
  20. package/src/hooks/useTheme.ts +16 -0
  21. package/src/index.ts +9 -5
  22. package/src/lib/createStyles.ts +10 -1
  23. package/src/lib/createTheme.ts +22 -13
  24. package/src/lib/index.ts +1 -10
  25. package/src/lib/tests/createStyles.spec.ts +151 -0
  26. package/src/tests/colors/baseColors.ts +166 -0
  27. package/src/tests/colors/darkMode.ts +232 -0
  28. package/src/tests/colors/lightMode.ts +285 -0
  29. package/src/tests/measures.ts +31 -0
  30. package/src/tests/theme.ts +58 -0
  31. package/src/theme/generateColorScheme.ts +53 -0
  32. package/src/theme/index.ts +3 -0
  33. package/src/theme/tests/generateColorScheme.spec.ts +118 -0
  34. package/src/theme/tests/themeStore.spec.ts +698 -0
  35. package/src/theme/tests/validateTheme.spec.ts +173 -0
  36. package/src/{lib → theme}/themeStore.ts +68 -3
  37. package/src/{lib → theme}/validateTheme.ts +13 -0
  38. package/src/tools/colors.ts +83 -39
  39. package/src/tools/deepClone.ts +10 -0
  40. package/src/tools/deepmerge.ts +10 -0
  41. package/src/{lib → tools}/hashKey.ts +7 -0
  42. package/src/tools/index.ts +6 -1
  43. package/src/tools/minifier.ts +38 -0
  44. package/src/tools/tests/colors.spec.ts +233 -0
  45. package/src/tools/tests/deepClone.spec.ts +102 -0
  46. package/src/tools/tests/deepmerge.spec.ts +155 -0
  47. package/src/tools/tests/hashKey.spec.ts +69 -0
  48. package/src/tools/tests/minifier.spec.ts +173 -0
  49. package/src/types/store.ts +2 -2
  50. package/src/types/style.ts +3 -3
  51. package/src/types/theme.ts +4 -4
  52. package/src/{lib/utils.ts → utils.ts} +3 -3
  53. package/src/{lib → variants}/borderCreator.ts +2 -2
  54. package/src/{lib → variants}/createAppVariants.ts +1 -1
  55. package/src/{lib → variants}/dynamicVariants.ts +1 -1
  56. package/src/variants/index.ts +6 -0
  57. package/src/{lib → variants}/spacing.ts +37 -24
  58. package/src/variants/tests/borderCreator.spec.ts +180 -0
  59. package/src/variants/tests/dynamicVariants.spec.ts +194 -0
  60. package/src/variants/tests/spacing.spec.ts +177 -0
  61. package/src/lib/Cacher.ts +0 -112
  62. package/src/lib/StaleControl.ts +0 -73
  63. package/src/lib/StyleCache.ts +0 -81
  64. package/src/lib/StylePersistor.ts +0 -28
  65. package/src/lib/hooks.ts +0 -64
  66. package/src/lib/minifier.ts +0 -20
  67. /package/src/{lib → tools}/multiplierProperty.ts +0 -0
  68. /package/src/{lib → variants}/defaultVariants.ts +0 -0
  69. /package/src/{lib → variants}/mediaQuery.ts +0 -0
@@ -0,0 +1,107 @@
1
+ import { renderHook } from '@testing-library/react'
2
+ import { describe, it, expect } from 'bun:test'
3
+ import { useCompositionStyles } from '../useCompositionStyles'
4
+ import { getNestedStylesByKey } from '../../utils'
5
+
6
+ describe('useCompositionStyles', () => {
7
+ const mockComponentStyles = {
8
+ button: {
9
+ backgroundColor: 'blue',
10
+ color: 'white',
11
+ padding: '10px'
12
+ },
13
+ input: {
14
+ border: '1px solid gray',
15
+ padding: '5px'
16
+ },
17
+ container: {
18
+ display: 'flex',
19
+ gap: '10px'
20
+ }
21
+ }
22
+
23
+ it('should return styles for single element', () => {
24
+ const { result } = renderHook(() =>
25
+ useCompositionStyles('button', mockComponentStyles)
26
+ )
27
+
28
+ expect(result.current).toEqual({
29
+ button: getNestedStylesByKey('button', mockComponentStyles)
30
+ })
31
+ })
32
+
33
+ it('should return styles for array of elements', () => {
34
+ const composition = ['button', 'input']
35
+
36
+ const { result } = renderHook(() =>
37
+ useCompositionStyles(composition, mockComponentStyles)
38
+ )
39
+
40
+ expect(result.current).toEqual({
41
+ button: getNestedStylesByKey('button', mockComponentStyles),
42
+ input: getNestedStylesByKey('input', mockComponentStyles)
43
+ })
44
+ })
45
+
46
+ it('should handle empty array', () => {
47
+ const { result } = renderHook(() =>
48
+ useCompositionStyles([], mockComponentStyles)
49
+ )
50
+
51
+ expect(result.current).toEqual({})
52
+ })
53
+
54
+ it('should memoize results with same props', () => {
55
+ const { result, rerender } = renderHook(
56
+ ({ composition, styles }) => useCompositionStyles(composition, styles),
57
+ {
58
+ initialProps: {
59
+ composition: 'button',
60
+ styles: mockComponentStyles
61
+ }
62
+ }
63
+ )
64
+
65
+ const firstResult = result.current
66
+ rerender({ composition: 'button', styles: mockComponentStyles })
67
+ expect(result.current).toEqual(firstResult)
68
+ })
69
+
70
+ it('should update when composition changes', () => {
71
+ const { result, rerender } = renderHook(
72
+ ({ composition }) => useCompositionStyles(composition, mockComponentStyles),
73
+ {
74
+ initialProps: { composition: 'button' }
75
+ }
76
+ )
77
+
78
+ expect(result.current).toEqual({
79
+ button: getNestedStylesByKey('button', mockComponentStyles)
80
+ })
81
+
82
+ rerender({ composition: 'input' })
83
+ expect(result.current).toEqual({
84
+ input: getNestedStylesByKey('input', mockComponentStyles)
85
+ })
86
+ })
87
+
88
+ it('should handle non-existent keys', () => {
89
+ const { result } = renderHook(() =>
90
+ useCompositionStyles('nonexistent', mockComponentStyles)
91
+ )
92
+
93
+ expect(result.current).toEqual({
94
+ nonexistent: getNestedStylesByKey('nonexistent', mockComponentStyles)
95
+ })
96
+ })
97
+
98
+ it('should preserve original componentStyles object', () => {
99
+ const originalStyles = { ...mockComponentStyles }
100
+
101
+ renderHook(() =>
102
+ useCompositionStyles('button', mockComponentStyles)
103
+ )
104
+
105
+ expect(mockComponentStyles).toEqual(originalStyles)
106
+ })
107
+ })
@@ -0,0 +1,89 @@
1
+ import { renderHook } from '@testing-library/react'
2
+ import { describe, it, expect } from 'bun:test'
3
+ import { useStyleObserver } from '../useStyleObserver'
4
+
5
+ describe('useStyleObserver', () => {
6
+ it('should return string representation for primitive values', () => {
7
+ const { result } = renderHook(() => useStyleObserver('red'))
8
+ expect(result.current).toBe('red')
9
+
10
+ const { result: numberResult } = renderHook(() => useStyleObserver(10))
11
+ expect(numberResult.current).toBe(10)
12
+
13
+ const { result: boolResult } = renderHook(() => useStyleObserver(true))
14
+ expect(boolResult.current).toBe(true)
15
+ })
16
+
17
+ it('should stringify object styles', () => {
18
+ const style = { color: 'red', fontSize: '14px' }
19
+ const { result } = renderHook(() => useStyleObserver(style))
20
+
21
+ expect(result.current).toBe(JSON.stringify(style))
22
+ })
23
+
24
+ it('should stringify array styles and filter falsy values', () => {
25
+ const style = ['btn', 'btn-primary', null, '', undefined, false]
26
+ const { result } = renderHook(() => useStyleObserver(style))
27
+
28
+ expect(result.current).toBe(JSON.stringify(['btn', 'btn-primary']))
29
+ })
30
+
31
+ it('should handle empty array', () => {
32
+ const { result } = renderHook(() => useStyleObserver([]))
33
+ expect(result.current).toBe(JSON.stringify([]))
34
+ })
35
+
36
+ it('should handle array with all falsy values', () => {
37
+ const { result } = renderHook(() => useStyleObserver([null, '', undefined, false]))
38
+ expect(result.current).toBe(JSON.stringify([]))
39
+ })
40
+
41
+ it('should handle null and undefined', () => {
42
+ const { result: nullResult } = renderHook(() => useStyleObserver(null))
43
+ expect(nullResult.current).toBe(JSON.stringify(null))
44
+
45
+ const { result: undefinedResult } = renderHook(() => useStyleObserver(undefined))
46
+ expect(undefinedResult.current).toBe(undefined)
47
+ })
48
+
49
+ it('should memoize results with same input', () => {
50
+ const style = { color: 'blue' }
51
+ const { result, rerender } = renderHook(
52
+ ({ styleInput }) => useStyleObserver(styleInput),
53
+ { initialProps: { styleInput: style } }
54
+ )
55
+
56
+ const firstResult = result.current
57
+ rerender({ styleInput: style })
58
+ expect(result.current).toBe(firstResult)
59
+ })
60
+
61
+ it('should update when style changes', () => {
62
+ const { result, rerender } = renderHook(
63
+ ({ style }) => useStyleObserver(style),
64
+ { initialProps: { style: { color: 'red' } } }
65
+ )
66
+
67
+ expect(result.current).toBe(JSON.stringify({ color: 'red' }))
68
+
69
+ rerender({ style: { color: 'blue' } })
70
+ expect(result.current).toBe(JSON.stringify({ color: 'blue' }))
71
+ })
72
+
73
+ it('should handle nested objects', () => {
74
+ const style = {
75
+ button: { color: 'red' },
76
+ container: { padding: '10px' }
77
+ }
78
+ const { result } = renderHook(() => useStyleObserver(style))
79
+
80
+ expect(result.current).toBe(JSON.stringify(style))
81
+ })
82
+
83
+ it('should handle mixed array with objects and strings', () => {
84
+ const style = ['btn', { active: true }, null, 'primary']
85
+ const { result } = renderHook(() => useStyleObserver(style))
86
+
87
+ expect(result.current).toBe(JSON.stringify(['btn', { active: true }, 'primary']))
88
+ })
89
+ })
@@ -0,0 +1,33 @@
1
+ import { useMemo } from 'react'
2
+ import { ICSS } from '../types'
3
+ import { getNestedStylesByKey } from '../utils'
4
+
5
+ /**
6
+ * Hook that processes composition styles based on component styles.
7
+ *
8
+ * @param {T | Array<T>} composition - Composition element(s)
9
+ * @param {Partial<Record<C, ICSS>>} componentStyles - Component styles object
10
+ * @returns {Partial<Record<T, ICSS>>} Mapped styles for each composition element
11
+ */
12
+ export function useCompositionStyles<T extends string, C extends string>(
13
+ composition: (T | Array<T>),
14
+ componentStyles: Partial<Record<C, ICSS>>
15
+ ): Partial<Record<T, ICSS>> {
16
+ const styles = {
17
+ ...componentStyles
18
+ }
19
+
20
+ return useMemo(() => {
21
+ const compositionStyles = {}
22
+
23
+ if (Array.isArray(composition)) {
24
+ for (const element of composition) {
25
+ compositionStyles[element as string] = getNestedStylesByKey(element, styles)
26
+ }
27
+ } else {
28
+ compositionStyles[composition as string] = getNestedStylesByKey(composition, styles)
29
+ }
30
+
31
+ return compositionStyles
32
+ }, [styles])
33
+ }
@@ -0,0 +1,13 @@
1
+ import { useMemo } from 'react'
2
+ import { ICSS } from '../types'
3
+ import { getNestedStylesByKey } from '../utils'
4
+
5
+ export function useNestedStylesByKey<T extends string>(match: string, componentStyles: Partial<Record<T, ICSS>>) {
6
+ const styles = {
7
+ ...componentStyles
8
+ }
9
+
10
+ return useMemo(() => {
11
+ return getNestedStylesByKey(match, styles)
12
+ }, [styles])
13
+ }
@@ -0,0 +1,19 @@
1
+ import { useMemo } from 'react'
2
+
3
+ /**
4
+ * Hook that observes style changes by creating a memoized string representation.
5
+ *
6
+ * @param {any} style - Style value to observe (array, object, or primitive)
7
+ * @returns {string} Serialized string representation of the style for comparison
8
+ */
9
+ export const useStyleObserver = (style) => {
10
+ return useMemo(() => {
11
+ if (Array.isArray(style)) {
12
+ return JSON.stringify(style?.filter(v => !!v))
13
+ } else if (typeof style === 'object'){
14
+ return JSON.stringify(style)
15
+ } else {
16
+ return style
17
+ }
18
+ }, [style])
19
+ }
@@ -0,0 +1,16 @@
1
+ import { ThemeState, themeStoreComputed } from '../theme'
2
+ import { useStore } from '@nanostores/react'
3
+
4
+ type ThemeSelector<T> = (state: ThemeState) => T
5
+
6
+ export const useTheme = <T = ThemeState>(
7
+ selector?: ThemeSelector<T>
8
+ ): T => {
9
+ const state = useStore(themeStoreComputed)
10
+
11
+ if (!selector) {
12
+ return state as T
13
+ }
14
+
15
+ return selector(state)
16
+ }
package/src/index.ts CHANGED
@@ -1,8 +1,12 @@
1
- import './lib/constants'
2
- import './lib/utils'
1
+ import './constants'
2
+ import './utils'
3
3
 
4
+ export * from './classes'
5
+ export * from './hooks'
4
6
  export * from './lib'
5
- export * from './types'
7
+ export * from './theme'
6
8
  export * from './tools'
7
-
8
- export { default as deepmerge } from '@fastify/deepmerge'
9
+ export * from './types'
10
+ export * from './variants'
11
+ export * from './constants'
12
+ export * from './utils'
@@ -1,10 +1,19 @@
1
1
  import { AnyRecord, ICSS, ITheme } from '../types'
2
- import { themeStore } from './themeStore'
2
+ import { themeStore } from '../theme'
3
3
 
4
4
  type Value = AnyRecord
5
5
 
6
6
  type StylesShape<K extends string, V extends Value> = Partial<Record<K, ICSS & Partial<Omit<V, keyof ICSS>>>>
7
7
 
8
+ /**
9
+ * Creates a reactive styles object that automatically updates when theme changes.
10
+ * Uses a proxy to re-compute styles on each access, ensuring theme changes are reflected.
11
+ *
12
+ * @template K - Style keys (extends string)
13
+ * @template V - Additional value type (extends AnyRecord)
14
+ * @param {StylesShape<K, V> | ((theme: ITheme) => StylesShape<K, V>)} styles - Static styles object or function that receives theme
15
+ * @returns {StylesShape<K, V>} Proxied styles object that recomputes on theme changes
16
+ */
8
17
  export function createStyles<K extends string, V extends Value = {}>(
9
18
  styles: StylesShape<K, V> | ((theme: ITheme) => StylesShape<K, V>),
10
19
  ) {
@@ -1,10 +1,7 @@
1
1
  import { AppTheme, Theme } from '../types'
2
- import { borderCreator } from './borderCreator'
3
- import { createMediaQueries } from './mediaQuery'
4
- import { multiplierProperty } from './multiplierProperty'
5
- import { defaultVariants } from './defaultVariants'
6
- import { spacingFactory } from './spacing'
7
- import { themeStore } from './themeStore'
2
+ import { borderCreator, createMediaQueries, defaultVariants, spacingFactory } from '../variants'
3
+ import { minifier, multiplierProperty } from '../tools'
4
+ import { themeStore } from '../theme'
8
5
 
9
6
  type ThemePersistor = {
10
7
  get: (name: string) => any
@@ -30,9 +27,20 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
30
27
  ...otherThemeValues
31
28
  } = theme
32
29
 
33
- themeStore.setColorScheme(themePersistor.get(colorSchemeKey) ?? 'default')
30
+ const persistor = {
31
+ get: (key: string) => {
32
+ const value = themePersistor.get(key)
33
+ if (!value) return null
34
+ return minifier.decompress(value)
35
+ },
36
+ set: (key: string, value: any) => {
37
+ return themePersistor.set(key, !value ? '' : minifier.compress(value))
38
+ }
39
+ }
40
+
41
+ themeStore.setColorScheme(persistor.get(colorSchemeKey) ?? 'default')
34
42
 
35
- const persistedAlternateColors = themePersistor.get(alternateColorsKey)
43
+ const persistedAlternateColors = persistor.get(alternateColorsKey)
36
44
 
37
45
  const alternateColors = {
38
46
  ...(persistedAlternateColors ?? {}),
@@ -80,31 +88,32 @@ export const createTheme = <T extends Theme>(theme: T, themePersistor: ThemePers
80
88
 
81
89
  themeStore.setColorScheme(colorScheme)
82
90
 
83
- themePersistor.set(colorSchemeKey, colorScheme)
91
+ persistor.set(colorSchemeKey, colorScheme)
84
92
  },
85
93
 
86
94
  injectColorScheme(name, colorMap) {
87
95
  themeStore.injectColorScheme(name, colorMap)
88
96
 
89
- const persistedAlternateColors = themePersistor.get(alternateColorsKey)
97
+ const persistedAlternateColors = persistor.get(alternateColorsKey)
90
98
 
91
99
  const unpersistedAlternateColors = {
92
100
  ...(persistedAlternateColors ?? {}),
93
101
  [name]: colorMap,
94
102
  }
95
103
 
96
- themePersistor.set(alternateColorsKey, unpersistedAlternateColors)
104
+ persistor.set(alternateColorsKey, unpersistedAlternateColors)
97
105
  },
106
+
98
107
  ejectColorScheme(name) {
99
108
  themeStore.ejectColorScheme(name)
100
109
 
101
- const persistedAlternateColors = themePersistor.get(alternateColorsKey)
110
+ const persistedAlternateColors = persistor.get(alternateColorsKey)
102
111
 
103
112
  if (name in persistedAlternateColors) {
104
113
  delete persistedAlternateColors[name]
105
114
  }
106
115
 
107
- themePersistor.set(alternateColorsKey, persistedAlternateColors)
116
+ persistor.set(alternateColorsKey, persistedAlternateColors)
108
117
  },
109
118
 
110
119
  baseSpacing: theme.baseSpacing,
package/src/lib/index.ts CHANGED
@@ -1,11 +1,2 @@
1
- export { validateTheme } from './validateTheme'
2
- export { themeStore } from './themeStore'
3
- export { createTheme } from './createTheme'
4
- export { createAppVariants } from './createAppVariants'
5
1
  export { createStyles } from './createStyles'
6
- export { getNestedStylesByKey, mergeStyles, concatStyles } from './utils'
7
- export { CodeleapStyleRegistry } from './StyleRegistry'
8
- export { StylePersistor } from './StylePersistor'
9
-
10
- export * from './constants'
11
- export * from './hooks'
2
+ export { createTheme } from './createTheme'
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { createStyles } from '../createStyles'
3
+ import { themeStore } from '../../theme'
4
+ import { mockTheme, MockedTheme } from '../../tests/theme'
5
+
6
+ describe('createStyles', () => {
7
+ let currentTheme: MockedTheme = null
8
+
9
+ beforeEach(() => {
10
+ mockTheme()
11
+
12
+ currentTheme = themeStore.theme as unknown as MockedTheme
13
+ })
14
+
15
+ it('should return static styles when provided as object', () => {
16
+ const staticStyles = {
17
+ button: { color: 'red', padding: '10px' },
18
+ input: { border: '1px solid gray' }
19
+ }
20
+
21
+ const styles = createStyles(staticStyles)
22
+
23
+ expect(styles.button).toEqual({ color: 'red', padding: '10px' })
24
+ expect(styles.input).toEqual({ border: '1px solid gray' })
25
+ })
26
+
27
+ it('should handle empty static styles object', () => {
28
+ const styles = createStyles({})
29
+ expect(Object.keys(styles)).toHaveLength(0)
30
+ })
31
+
32
+ it('should compute styles from function when theme is available', () => {
33
+ const functionStyles = (theme: MockedTheme) => ({
34
+ button: {
35
+ color: theme.colors.neutralSo,
36
+ backgroundColor: theme.colors.secondary
37
+ },
38
+ container: {
39
+ padding: theme.spacing.value(2)
40
+ }
41
+ })
42
+
43
+ const styles = createStyles(functionStyles)
44
+
45
+ expect(styles.button).toEqual({
46
+ color: currentTheme.colors.neutralSo,
47
+ backgroundColor: currentTheme.colors.secondary
48
+ })
49
+ expect(styles.container).toEqual({
50
+ padding: 16
51
+ })
52
+ })
53
+
54
+ it('should recompute styles when theme changes (proxy behavior)', () => {
55
+ const functionStyles = (theme: MockedTheme) => ({
56
+ button: { color: theme.colors.buttonRegularPrimaryBgDefault }
57
+ })
58
+
59
+ const styles = createStyles(functionStyles)
60
+
61
+ // Initial theme
62
+ expect(styles.button).toEqual({ color: currentTheme.colors.primarySolid800 })
63
+
64
+ currentTheme.setColorScheme('dark')
65
+
66
+ // Should reflect new theme due to proxy
67
+ expect(styles.button).toEqual({ color: currentTheme.colors.primarySolid500 })
68
+ })
69
+
70
+ it('should handle complex theme-based styles', () => {
71
+ const functionStyles = (theme: MockedTheme) => ({
72
+ button: {
73
+ color: theme.colors.redSolid100,
74
+ border: `1px solid ${theme.colors.neutralSolid1000}`,
75
+ '@media (max-width: 768px)': {
76
+ fontSize: '14px'
77
+ }
78
+ },
79
+ alert: {
80
+ backgroundColor: theme.colors.redSolid600
81
+ }
82
+ })
83
+
84
+ const styles = createStyles(functionStyles)
85
+
86
+ expect(styles.button).toEqual({
87
+ color: currentTheme.colors.redSolid100,
88
+ border: `1px solid ${currentTheme.colors.neutralSolid1000}`,
89
+ '@media (max-width: 768px)': {
90
+ fontSize: '14px'
91
+ }
92
+ })
93
+ expect(styles.alert).toEqual({
94
+ backgroundColor: currentTheme.colors.redSolid600
95
+ })
96
+ })
97
+
98
+ it('should handle accessing non-existent properties', () => {
99
+ const staticStyles = {
100
+ button: { color: 'red' }
101
+ } as any
102
+
103
+ const styles = createStyles(staticStyles)
104
+
105
+ expect(styles.nonExistent).toBeUndefined()
106
+ })
107
+
108
+ it('should work with mixed ICSS and custom properties', () => {
109
+ const functionStyles = (theme: MockedTheme) => ({
110
+ component: {
111
+ // ICSS properties
112
+ color: theme.colors.blueSolid900,
113
+ padding: '10px',
114
+ // Custom properties (merged with ICSS)
115
+ customProp: 'custom-value',
116
+ dataAttribute: 'test'
117
+ }
118
+ })
119
+
120
+ const styles = createStyles(functionStyles)
121
+
122
+ expect(styles.component).toEqual({
123
+ color: currentTheme.colors.blueSolid900,
124
+ padding: '10px',
125
+ customProp: 'custom-value',
126
+ dataAttribute: 'test'
127
+ })
128
+ })
129
+
130
+ it('should maintain proxy behavior across multiple accesses', () => {
131
+ let computeCount = 0
132
+
133
+ const functionStyles = (theme: MockedTheme) => {
134
+ computeCount++
135
+ return {
136
+ button: { color: theme.colors.blueSolid100 }
137
+ }
138
+ }
139
+
140
+ const styles = createStyles(functionStyles)
141
+
142
+ computeCount = 0
143
+
144
+ // Each property access should trigger recomputation
145
+ styles.button
146
+ styles.button
147
+ styles.button
148
+
149
+ expect(computeCount).toBe(3)
150
+ })
151
+ })