@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.
- package/dist/classes/Cacher.d.ts +87 -0
- package/dist/classes/Cacher.d.ts.map +1 -0
- package/dist/classes/StaleControl.d.ts +65 -0
- package/dist/classes/StaleControl.d.ts.map +1 -0
- package/dist/classes/StyleCache.d.ts +63 -0
- package/dist/classes/StyleCache.d.ts.map +1 -0
- package/dist/classes/StylePersistor.d.ts +52 -0
- package/dist/classes/StylePersistor.d.ts.map +1 -0
- package/dist/classes/StyleRegistry.d.ts +108 -0
- package/dist/classes/StyleRegistry.d.ts.map +1 -0
- package/dist/classes/index.d.ts +3 -0
- package/dist/classes/index.d.ts.map +1 -0
- package/dist/constants.d.ts +22 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useCompositionStyles.d.ts +12 -0
- package/dist/hooks/useCompositionStyles.d.ts.map +1 -0
- package/dist/hooks/useNestedStylesByKey.d.ts +11 -0
- package/dist/hooks/useNestedStylesByKey.d.ts.map +1 -0
- package/dist/hooks/useStyleObserver.d.ts +9 -0
- package/dist/hooks/useStyleObserver.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts +19 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/calc.d.ts +27 -0
- package/dist/lib/calc.d.ts.map +1 -0
- package/dist/lib/createStyles.d.ts +30 -0
- package/dist/lib/createStyles.d.ts.map +1 -0
- package/dist/lib/createTheme.d.ts +28 -0
- package/dist/lib/createTheme.d.ts.map +1 -0
- package/dist/lib/cssVariables.d.ts +35 -0
- package/dist/lib/cssVariables.d.ts.map +1 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/theme/generateColorScheme.d.ts +22 -0
- package/dist/theme/generateColorScheme.d.ts.map +1 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/themeStore.d.ts +106 -0
- package/dist/theme/themeStore.d.ts.map +1 -0
- package/dist/theme/validateTheme.d.ts +19 -0
- package/dist/theme/validateTheme.d.ts.map +1 -0
- package/dist/tools/colors.d.ts +70 -0
- package/dist/tools/colors.d.ts.map +1 -0
- package/dist/tools/deepClone.d.ts +7 -0
- package/dist/tools/deepClone.d.ts.map +1 -0
- package/dist/tools/deepmerge.d.ts +13 -0
- package/dist/tools/deepmerge.d.ts.map +1 -0
- package/dist/tools/hashKey.d.ts +8 -0
- package/dist/tools/hashKey.d.ts.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/minifier.d.ts +24 -0
- package/dist/tools/minifier.d.ts.map +1 -0
- package/dist/tools/multiplierProperty.d.ts +4 -0
- package/dist/tools/multiplierProperty.d.ts.map +1 -0
- package/dist/types/cache.d.ts +12 -0
- package/dist/types/cache.d.ts.map +1 -0
- package/dist/types/component.d.ts +58 -0
- package/dist/types/component.d.ts.map +1 -0
- package/dist/types/core.d.ts +77 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/icon.d.ts +15 -0
- package/dist/types/icon.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/spacing.d.ts +28 -0
- package/dist/types/spacing.d.ts.map +1 -0
- package/dist/types/store.d.ts +12 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/style.d.ts +42 -0
- package/dist/types/style.d.ts.map +1 -0
- package/dist/types/theme.d.ts +109 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/utils.d.ts +40 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/variants/borderCreator.d.ts +22 -0
- package/dist/variants/borderCreator.d.ts.map +1 -0
- package/dist/variants/createAppVariants.d.ts +18 -0
- package/dist/variants/createAppVariants.d.ts.map +1 -0
- package/dist/variants/defaultVariants.d.ts +140 -0
- package/dist/variants/defaultVariants.d.ts.map +1 -0
- package/dist/variants/dynamicVariants.d.ts +43 -0
- package/dist/variants/dynamicVariants.d.ts.map +1 -0
- package/dist/variants/index.d.ts +7 -0
- package/dist/variants/index.d.ts.map +1 -0
- package/dist/variants/mediaQuery.d.ts +30 -0
- package/dist/variants/mediaQuery.d.ts.map +1 -0
- package/dist/variants/spacing.d.ts +26 -0
- package/dist/variants/spacing.d.ts.map +1 -0
- package/package.json +19 -5
- package/src/classes/Cacher.ts +9 -9
- package/src/classes/StaleControl.ts +1 -1
- package/src/classes/StyleCache.ts +9 -3
- package/src/classes/StylePersistor.ts +11 -0
- package/src/classes/StyleRegistry.ts +124 -43
- package/src/classes/tests/StyleRegistry.spec.ts +169 -0
- package/src/constants.ts +14 -0
- package/src/hooks/useCompositionStyles.ts +9 -7
- package/src/hooks/useNestedStylesByKey.ts +8 -0
- package/src/hooks/useStyleObserver.ts +6 -5
- package/src/hooks/useTheme.ts +14 -0
- package/src/lib/calc.ts +13 -0
- package/src/lib/createStyles.ts +35 -4
- package/src/lib/createTheme.ts +74 -25
- package/src/lib/cssVariables.ts +74 -0
- package/src/lib/index.ts +2 -1
- package/src/lib/tests/createStyles.spec.ts +80 -23
- package/src/lib/tests/createStylesWithContext.spec.ts +108 -0
- package/src/tests/theme.ts +6 -2
- package/src/theme/generateColorScheme.ts +13 -10
- package/src/theme/tests/themeStore.spec.ts +72 -71
- package/src/theme/themeStore.ts +10 -7
- package/src/theme/validateTheme.ts +1 -1
- package/src/tools/colors.ts +24 -36
- package/src/tools/deepClone.ts +3 -5
- package/src/tools/deepmerge.ts +8 -6
- package/src/tools/hashKey.ts +4 -5
- package/src/tools/minifier.ts +11 -12
- package/src/tools/tests/deepClone.spec.ts +2 -2
- package/src/types/cache.ts +10 -0
- package/src/types/component.ts +41 -5
- package/src/types/core.ts +66 -6
- package/src/types/icon.ts +11 -0
- package/src/types/spacing.ts +21 -0
- package/src/types/store.ts +6 -0
- package/src/types/style.ts +37 -10
- package/src/types/theme.ts +37 -1
- package/src/utils.ts +34 -4
- package/src/variants/borderCreator.ts +14 -5
- package/src/variants/createAppVariants.ts +11 -0
- package/src/variants/defaultVariants.ts +28 -8
- package/src/variants/dynamicVariants.ts +76 -18
- package/src/variants/mediaQuery.ts +18 -0
- package/src/variants/spacing.ts +15 -1
- package/package.json.bak +0 -30
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Developed for potential future need of time-based cache invalidation.
|
|
7
7
|
*/
|
|
8
8
|
export class StaleControl {
|
|
9
|
-
private wiperId:
|
|
9
|
+
private wiperId: ReturnType<typeof setInterval> | null = null
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param staleTime - Expiration time in minutes (default: 60 minutes)
|
|
@@ -5,12 +5,18 @@ import { CacheType } from '../types/cache'
|
|
|
5
5
|
import { StateStorage } from '../types/store'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Aggregates the six typed `Cache` buckets used by the style registry.
|
|
9
|
+
* Three buckets (`styles`, `compositions`, `responsive`) are in-memory only.
|
|
10
|
+
* Three buckets (`variants`, `common`, `components`) are backed by the
|
|
11
|
+
* provided `StateStorage` and survive page reloads until the stale window expires.
|
|
12
|
+
*
|
|
13
|
+
* All keys are derived from a `baseKey` that encodes the active color scheme,
|
|
14
|
+
* theme snapshot, and common variants — so switching color scheme automatically
|
|
15
|
+
* invalidates all cached values without an explicit `wipeCache` call.
|
|
10
16
|
*/
|
|
11
17
|
export class StyleCache {
|
|
12
18
|
/** Base key used for cache key generation */
|
|
13
|
-
baseKey
|
|
19
|
+
baseKey!: string
|
|
14
20
|
|
|
15
21
|
/** Cache for style data */
|
|
16
22
|
styles = new Cache('styles')
|
|
@@ -2,12 +2,23 @@ import { StateStorage } from '../types/store'
|
|
|
2
2
|
import { minifier } from '../tools'
|
|
3
3
|
import { StyleConstants } from '../constants'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Platform-specific storage adapter required by `StylePersistor`.
|
|
7
|
+
* Implementations exist for MMKV (mobile) and localStorage (web).
|
|
8
|
+
* Note the method names (`set`/`get`/`del`) differ from the `StateStorage` interface
|
|
9
|
+
* (`setItem`/`getItem`/`removeItem`) — `StylePersistor` bridges the two.
|
|
10
|
+
*/
|
|
5
11
|
export type StoragePersistor = {
|
|
6
12
|
set: (key: string, value: any) => void
|
|
7
13
|
get: (key: string) => any
|
|
8
14
|
del: (key: string) => void
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Adapts a platform `StoragePersistor` to the `StateStorage` interface expected by
|
|
19
|
+
* `StyleCache`. All values are LZ-compressed on write and decompressed on read,
|
|
20
|
+
* substantially reducing storage footprint for large serialised style caches.
|
|
21
|
+
*/
|
|
11
22
|
export class StylePersistor implements StateStorage {
|
|
12
23
|
/**
|
|
13
24
|
* Creates a new StylePersistor instance with compression capabilities
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable dot-notation */
|
|
2
|
-
import type {
|
|
2
|
+
import type { AnyStyledComponent, ComponentContext, ICSS, ITheme, StyleAggregator, StyleProp, VariantStyleSheet } from '../types'
|
|
3
3
|
import type { StateStorage } from '../types/store'
|
|
4
4
|
import type { MultiplierFunction } from '../variants'
|
|
5
5
|
import { themeStore } from '../theme'
|
|
@@ -7,7 +7,22 @@ import { defaultVariants, dynamicVariants } from '../variants'
|
|
|
7
7
|
import { capitalize, ignoredStyleKeys, isSpacingKey } from '../utils'
|
|
8
8
|
import { StyleCache } from './StyleCache'
|
|
9
9
|
import { minifier, deepClone, deepmerge } from '../tools'
|
|
10
|
-
|
|
10
|
+
import { CONTEXT_FACTORY_SYMBOL } from '../lib/createStyles'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Central style resolution engine for the `@codeleap/styles` system.
|
|
14
|
+
*
|
|
15
|
+
* Responsibilities:
|
|
16
|
+
* - Stores compressed variant stylesheets per component (`stylesheets`).
|
|
17
|
+
* - Maintains the merged common-variant map (`defaultVariants + appVariants + dynamicVariants + spacing`).
|
|
18
|
+
* - Resolves `StyleProp` values into flat `ICSS` objects for each composition element.
|
|
19
|
+
* - Caches every resolution step in the six `StyleCache` buckets for performance.
|
|
20
|
+
* - Provides `createStyle` as an abstract hook for platform-specific style flattening
|
|
21
|
+
* (web passes through; RN uses `StyleSheet.flatten`).
|
|
22
|
+
*
|
|
23
|
+
* Instantiated once per platform registry (not per component). Platform packages
|
|
24
|
+
* subclass or configure this to provide the appropriate `createStyle` implementation.
|
|
25
|
+
*/
|
|
11
26
|
export class CodeleapStyleRegistry {
|
|
12
27
|
stylesheets: Record<string, VariantStyleSheet> = {}
|
|
13
28
|
|
|
@@ -17,6 +32,9 @@ export class CodeleapStyleRegistry {
|
|
|
17
32
|
|
|
18
33
|
components: Record<string, AnyStyledComponent> = {}
|
|
19
34
|
|
|
35
|
+
/** Per-component map of variant names to context-aware factory functions. Populated during `registerVariants`. */
|
|
36
|
+
variantFactories: Record<string, Record<string, (theme: ITheme, context: ComponentContext) => any>> = {}
|
|
37
|
+
|
|
20
38
|
private styleCache: StyleCache
|
|
21
39
|
|
|
22
40
|
constructor(storage: StateStorage) {
|
|
@@ -24,42 +42,47 @@ export class CodeleapStyleRegistry {
|
|
|
24
42
|
|
|
25
43
|
this.registerCommonVariants()
|
|
26
44
|
|
|
27
|
-
const currentColorScheme = themeStore.
|
|
45
|
+
const currentColorScheme = themeStore.themeTyped?.currentColorScheme?.() ?? themeStore.colorScheme ?? 'default'
|
|
28
46
|
|
|
29
47
|
this.styleCache.registerBaseKey([currentColorScheme, themeStore.theme, this.commonVariants])
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a single common-variant string (e.g., `"flex"`, `"padding:2"`, `"xl:flex"`)
|
|
52
|
+
* into a composition-keyed style object. Handles breakpoint-prefixed variants by wrapping
|
|
53
|
+
* the result in a `@media` key. Returns `null` if the variant is not found in `commonVariants`.
|
|
54
|
+
*/
|
|
55
|
+
computeCommonVariantStyle(componentName: string, variant: string, component: string | undefined = undefined) {
|
|
33
56
|
const cache = this.styleCache.keyFor('common', variant)
|
|
34
57
|
|
|
35
58
|
if (!!cache.value) {
|
|
36
59
|
return {
|
|
37
|
-
[component]: this.createStyle(cache.value),
|
|
60
|
+
[component as string]: this.createStyle(cache.value),
|
|
38
61
|
}
|
|
39
62
|
}
|
|
40
63
|
|
|
41
64
|
const theme = themeStore.theme
|
|
42
65
|
|
|
43
|
-
let mediaQuery = null
|
|
66
|
+
let mediaQuery: string | null = null
|
|
44
67
|
|
|
45
|
-
let [variantName, value] = variant?.includes(':') ? variant?.split(':') : [variant, null]
|
|
68
|
+
let [variantName, value] = variant?.includes(':') ? variant?.split(':') : [variant, null as string | null]
|
|
46
69
|
|
|
47
|
-
// @ts-
|
|
70
|
+
// @ts-ignore the not yet augmented/defined
|
|
48
71
|
if (!!theme?.breakpoints[variantName]) {
|
|
49
72
|
const [breakpoint, _variantName, _value] = variant?.split(':')?.length == 2 ? [...variant?.split(':'), null] : variant?.split(':')
|
|
50
73
|
|
|
51
74
|
// @ts-expect-error
|
|
52
75
|
mediaQuery = theme.media.down(breakpoint)
|
|
53
|
-
value = _value
|
|
54
|
-
variantName = _variantName
|
|
76
|
+
value = _value as string | null
|
|
77
|
+
variantName = _variantName as string
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
const variantStyle = this.commonVariants[variantName] ?? this.commonVariants[variant]
|
|
58
81
|
|
|
59
|
-
let style = null
|
|
82
|
+
let style: any = null
|
|
60
83
|
|
|
61
84
|
if (typeof variantStyle == 'function') {
|
|
62
|
-
style = isSpacingKey(variantName) ? variantStyle(value) : variantStyle(theme, value)
|
|
85
|
+
style = isSpacingKey(variantName) ? variantStyle(value as any) : (variantStyle as any)(theme, value)
|
|
63
86
|
} else {
|
|
64
87
|
style = variantStyle
|
|
65
88
|
}
|
|
@@ -73,7 +96,7 @@ export class CodeleapStyleRegistry {
|
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
const commonStyles = {
|
|
76
|
-
[component]: this.createStyle(style),
|
|
99
|
+
[component as string]: this.createStyle(style),
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
this.styleCache.cacheFor('common', cache.key, style)
|
|
@@ -81,15 +104,23 @@ export class CodeleapStyleRegistry {
|
|
|
81
104
|
return commonStyles
|
|
82
105
|
}
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Resolves an array of variant strings for a component into a single merged `ICSS`.
|
|
109
|
+
* Checks component-level variant stylesheet first, then falls back to common variants.
|
|
110
|
+
* Context-aware factories (registered via `CONTEXT_FACTORY_SYMBOL`) are called with
|
|
111
|
+
* the current theme and the provided `context` object.
|
|
112
|
+
*/
|
|
113
|
+
computeVariantStyle(componentName: string, variants: string[], _component: string | undefined = undefined, context?: ComponentContext): ICSS {
|
|
85
114
|
const { rootElement } = this.getRegisteredComponent(componentName)
|
|
86
115
|
|
|
87
116
|
const component = _component ?? rootElement
|
|
88
117
|
|
|
89
|
-
const stylesheet = minifier.decompress(this.stylesheets[componentName])
|
|
118
|
+
const stylesheet = minifier.decompress(this.stylesheets[componentName]) ?? {}
|
|
119
|
+
|
|
120
|
+
|
|
90
121
|
const aggregator = this.aggregators[componentName]
|
|
91
122
|
|
|
92
|
-
const cache = this.styleCache.keyFor('variants', { componentName, component, stylesheet, variants, aggregator })
|
|
123
|
+
const cache = this.styleCache.keyFor('variants', { componentName, component, stylesheet, variants, aggregator, context })
|
|
93
124
|
|
|
94
125
|
if (!!cache.value) {
|
|
95
126
|
return cache.value
|
|
@@ -98,6 +129,12 @@ export class CodeleapStyleRegistry {
|
|
|
98
129
|
const theme = themeStore.theme
|
|
99
130
|
|
|
100
131
|
const variantStyles = variants.map((variant) => {
|
|
132
|
+
const factory = context && theme && this.variantFactories?.[componentName]?.[variant]
|
|
133
|
+
|
|
134
|
+
if (factory) {
|
|
135
|
+
return factory(theme, context)
|
|
136
|
+
}
|
|
137
|
+
|
|
101
138
|
if (!!stylesheet[variant]) {
|
|
102
139
|
|
|
103
140
|
return stylesheet[variant]
|
|
@@ -123,7 +160,7 @@ export class CodeleapStyleRegistry {
|
|
|
123
160
|
let variantStyle = deepmerge({ all: true })(...variantStyles)
|
|
124
161
|
|
|
125
162
|
if (!!aggregator) {
|
|
126
|
-
variantStyle = aggregator(theme, variantStyle)
|
|
163
|
+
variantStyle = aggregator(theme as ITheme, variantStyle)
|
|
127
164
|
}
|
|
128
165
|
|
|
129
166
|
this.styleCache.cacheFor('variants', cache.key, variantStyle)
|
|
@@ -131,8 +168,14 @@ export class CodeleapStyleRegistry {
|
|
|
131
168
|
return variantStyle
|
|
132
169
|
}
|
|
133
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Determines whether a style object contains composition keys for a given component.
|
|
173
|
+
* A key is a composition key if it starts with one of the component's `elements` strings
|
|
174
|
+
* and is not in `ignoredStyleKeys`. Returns both a boolean flag and the extracted
|
|
175
|
+
* composition sub-record so callers can avoid a second pass.
|
|
176
|
+
*/
|
|
134
177
|
isCompositionStyle(component: AnyStyledComponent, style: any) {
|
|
135
|
-
const composition = {}
|
|
178
|
+
const composition: Record<string, any> = {}
|
|
136
179
|
|
|
137
180
|
if (!style) {
|
|
138
181
|
return {
|
|
@@ -143,9 +186,9 @@ export class CodeleapStyleRegistry {
|
|
|
143
186
|
|
|
144
187
|
const styleKeys = Object.keys(style)
|
|
145
188
|
|
|
146
|
-
let elements = []
|
|
189
|
+
let elements: string[] = []
|
|
147
190
|
|
|
148
|
-
for (const element of component?.elements) {
|
|
191
|
+
for (const element of component?.elements ?? []) {
|
|
149
192
|
const componentElements = styleKeys?.filter(k => k?.startsWith(element) && !ignoredStyleKeys?.includes(k))
|
|
150
193
|
|
|
151
194
|
if (componentElements?.length >= 1) {
|
|
@@ -163,6 +206,12 @@ export class CodeleapStyleRegistry {
|
|
|
163
206
|
}
|
|
164
207
|
}
|
|
165
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Checks whether a style object contains a `breakpoints` key, indicating it uses
|
|
211
|
+
* the responsive style API. Returns the key name alongside the boolean so the caller
|
|
212
|
+
* can use it to read and delete the breakpoints entry without a repeated `'breakpoints'`
|
|
213
|
+
* string literal.
|
|
214
|
+
*/
|
|
166
215
|
isResponsiveStyle(style: any) {
|
|
167
216
|
const responsiveStyleKey = 'breakpoints'
|
|
168
217
|
|
|
@@ -214,7 +263,7 @@ export class CodeleapStyleRegistry {
|
|
|
214
263
|
}
|
|
215
264
|
}
|
|
216
265
|
|
|
217
|
-
getResponsiveStyle(componentName: string, responsiveStyleKey: string, style:
|
|
266
|
+
getResponsiveStyle(componentName: string, responsiveStyleKey: string, style: Record<string, any>) {
|
|
218
267
|
const responsiveStyles = style[responsiveStyleKey]
|
|
219
268
|
|
|
220
269
|
if (!responsiveStyles) return {}
|
|
@@ -227,14 +276,14 @@ export class CodeleapStyleRegistry {
|
|
|
227
276
|
return cache.value
|
|
228
277
|
}
|
|
229
278
|
|
|
230
|
-
const styles = {}
|
|
279
|
+
const styles: Record<string, any> = {}
|
|
231
280
|
|
|
232
281
|
for (const responsiveStyle in responsiveStyles) {
|
|
233
282
|
const mediaQuery = this.getMediaQuery(responsiveStyle)
|
|
234
283
|
|
|
235
284
|
const breakpointStyle = responsiveStyles[responsiveStyle]
|
|
236
285
|
|
|
237
|
-
const componentStyles = this.styleFor(componentName, breakpointStyle, false)
|
|
286
|
+
const componentStyles = this.styleFor(componentName, breakpointStyle, false) as Record<string, any>
|
|
238
287
|
|
|
239
288
|
// @ts-ignore
|
|
240
289
|
for (const composition in componentStyles) {
|
|
@@ -272,7 +321,7 @@ export class CodeleapStyleRegistry {
|
|
|
272
321
|
component,
|
|
273
322
|
)
|
|
274
323
|
|
|
275
|
-
styles = deepmerge({ all: true })(styles, computedVariantStyle[component])
|
|
324
|
+
styles = deepmerge({ all: true })(styles, (computedVariantStyle as any)[component])
|
|
276
325
|
}
|
|
277
326
|
} else if (typeof style === 'object') {
|
|
278
327
|
styles = !!predicateObj ? predicateObj(style) : style
|
|
@@ -283,8 +332,7 @@ export class CodeleapStyleRegistry {
|
|
|
283
332
|
|
|
284
333
|
private getMediaQuery(responsiveKey: string) {
|
|
285
334
|
const [breakpoint, query] = responsiveKey?.includes(':') ? responsiveKey?.split(':') : [responsiveKey, 'down']
|
|
286
|
-
|
|
287
|
-
// @ts-expect-error - media not has type
|
|
335
|
+
// @ts-ignore
|
|
288
336
|
const mediaQuery = themeStore.theme?.media?.[query]?.(breakpoint)
|
|
289
337
|
|
|
290
338
|
return mediaQuery
|
|
@@ -345,8 +393,8 @@ export class CodeleapStyleRegistry {
|
|
|
345
393
|
return styles
|
|
346
394
|
}
|
|
347
395
|
|
|
348
|
-
styleFor<T = unknown>(componentName: string, componentStyle: StyleProp<T>, mergeWithDefaultStyle = true): T {
|
|
349
|
-
const cache = this.styleCache.keyFor('components', { componentName, componentStyle, stylesheet: this.stylesheets[componentName] })
|
|
396
|
+
styleFor<T = unknown>(componentName: string, componentStyle: StyleProp<T & string>, mergeWithDefaultStyle = true, context?: ComponentContext): T {
|
|
397
|
+
const cache = this.styleCache.keyFor('components', { componentName, componentStyle, stylesheet: this.stylesheets[componentName], context })
|
|
350
398
|
|
|
351
399
|
if (!!cache.value) {
|
|
352
400
|
return cache.value as T
|
|
@@ -364,7 +412,7 @@ export class CodeleapStyleRegistry {
|
|
|
364
412
|
}
|
|
365
413
|
|
|
366
414
|
if (typeof style === 'string') {
|
|
367
|
-
const computedVariantStyle = this.computeVariantStyle(componentName, [style])
|
|
415
|
+
const computedVariantStyle = this.computeVariantStyle(componentName, [style], undefined, context)
|
|
368
416
|
|
|
369
417
|
return this.mergeStylesWithCache(
|
|
370
418
|
[defaultStyle, computedVariantStyle],
|
|
@@ -418,7 +466,7 @@ export class CodeleapStyleRegistry {
|
|
|
418
466
|
const isObj = typeof s === 'object'
|
|
419
467
|
|
|
420
468
|
if (variants.length > 0 && (idx === filteredStyle.length - 1 || isObj)) {
|
|
421
|
-
const computedVariantStyle = this.computeVariantStyle(componentName, variants)
|
|
469
|
+
const computedVariantStyle = this.computeVariantStyle(componentName, variants, undefined, context)
|
|
422
470
|
|
|
423
471
|
styles.push(computedVariantStyle)
|
|
424
472
|
|
|
@@ -461,10 +509,15 @@ export class CodeleapStyleRegistry {
|
|
|
461
509
|
return {} as T
|
|
462
510
|
}
|
|
463
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Rebuilds `commonVariants` by merging (in ascending priority order):
|
|
514
|
+
* `defaultVariants` → `appVariants` → `dynamicVariants` → `spacingVariants` → `insetVariants`.
|
|
515
|
+
* Called once in the constructor; must be re-called (via `update`) if app variants change.
|
|
516
|
+
*/
|
|
464
517
|
registerCommonVariants() {
|
|
465
|
-
const spacingVariants = themeStore.
|
|
518
|
+
const spacingVariants = themeStore.themeTyped?.spacing
|
|
466
519
|
|
|
467
|
-
const insetVariants = themeStore.
|
|
520
|
+
const insetVariants = themeStore.themeTyped?.inset
|
|
468
521
|
|
|
469
522
|
const appVariants = themeStore.variants
|
|
470
523
|
|
|
@@ -476,18 +529,41 @@ export class CodeleapStyleRegistry {
|
|
|
476
529
|
insetVariants,
|
|
477
530
|
)
|
|
478
531
|
|
|
479
|
-
this.commonVariants = commonVariants
|
|
532
|
+
this.commonVariants = commonVariants as Record<string, ICSS | MultiplierFunction>
|
|
480
533
|
}
|
|
481
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Registers a component's variant stylesheet. No-ops if the component is already
|
|
537
|
+
* registered (variants are immutable after first registration). Extracts any
|
|
538
|
+
* `CONTEXT_FACTORY_SYMBOL`-tagged entries into `variantFactories` for context-aware
|
|
539
|
+
* resolution, then compresses and stores the remaining variants.
|
|
540
|
+
*/
|
|
482
541
|
registerVariants<Composition extends string = any>(componentName: string, variants: VariantStyleSheet, aggregator?: StyleAggregator<Composition>) {
|
|
483
542
|
if (this.stylesheets[componentName]) {
|
|
484
|
-
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const factories: Record<string, (theme: ITheme, context: ComponentContext) => any> = {}
|
|
547
|
+
for (const [variantName, variant] of Object.entries(variants)) {
|
|
548
|
+
const factory = (variant as any)?.[CONTEXT_FACTORY_SYMBOL]
|
|
549
|
+
if (typeof factory === 'function') factories[variantName] = factory
|
|
550
|
+
}
|
|
551
|
+
if (Object.keys(factories).length > 0) {
|
|
552
|
+
this.variantFactories[componentName] = factories
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (aggregator) {
|
|
556
|
+
this.aggregators[componentName] = aggregator as StyleAggregator
|
|
485
557
|
}
|
|
486
|
-
this.aggregators[componentName] = aggregator
|
|
487
558
|
|
|
488
559
|
this.stylesheets[componentName] = minifier.compress(variants)
|
|
489
560
|
}
|
|
490
561
|
|
|
562
|
+
/**
|
|
563
|
+
* Records the minimal component metadata (`styleRegistryName`, `elements`,
|
|
564
|
+
* `rootElement`) needed for composition and style resolution. Only the metadata
|
|
565
|
+
* is stored — the component function itself is not retained.
|
|
566
|
+
*/
|
|
491
567
|
registerComponent(component: AnyStyledComponent) {
|
|
492
568
|
const componentData = {
|
|
493
569
|
styleRegistryName: component?.styleRegistryName,
|
|
@@ -495,7 +571,7 @@ export class CodeleapStyleRegistry {
|
|
|
495
571
|
rootElement: component?.rootElement,
|
|
496
572
|
}
|
|
497
573
|
|
|
498
|
-
this.components[component.styleRegistryName] = componentData as any
|
|
574
|
+
this.components[component.styleRegistryName!] = componentData as any
|
|
499
575
|
}
|
|
500
576
|
|
|
501
577
|
/**
|
|
@@ -506,8 +582,13 @@ export class CodeleapStyleRegistry {
|
|
|
506
582
|
throw new Error('createStyle: Not implemented')
|
|
507
583
|
}
|
|
508
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Must be called after the active color scheme changes so that the `StyleCache`
|
|
587
|
+
* base key is regenerated. Without this call, cached values from the previous
|
|
588
|
+
* scheme would be returned for the new scheme.
|
|
589
|
+
*/
|
|
509
590
|
update() {
|
|
510
|
-
const currentColorScheme = themeStore.
|
|
591
|
+
const currentColorScheme = themeStore.themeTyped?.currentColorScheme?.() ?? themeStore.colorScheme ?? 'default'
|
|
511
592
|
|
|
512
593
|
this.styleCache.registerBaseKey([currentColorScheme, themeStore.theme, this.commonVariants])
|
|
513
594
|
}
|
|
@@ -516,11 +597,11 @@ export class CodeleapStyleRegistry {
|
|
|
516
597
|
return deepClone(style)
|
|
517
598
|
}
|
|
518
599
|
|
|
519
|
-
createStyles<K extends string = string>(styles: Record<K, StyleProp<
|
|
600
|
+
createStyles<K extends string = string>(styles: Record<K, StyleProp<string, ''>> | ((theme: ITheme) => Record<K, StyleProp<string, ''>>)): Record<K, ICSS> {
|
|
520
601
|
const compute = () => {
|
|
521
602
|
const current = themeStore.theme
|
|
522
603
|
|
|
523
|
-
const stylesObj = typeof styles === 'function' ? styles(current) : styles
|
|
604
|
+
const stylesObj = typeof styles === 'function' ? styles(current as ITheme) : styles
|
|
524
605
|
|
|
525
606
|
const cache = this.styleCache.keyFor('styles', stylesObj)
|
|
526
607
|
|
|
@@ -531,7 +612,7 @@ export class CodeleapStyleRegistry {
|
|
|
531
612
|
const createdStyles = {} as Record<K, any>
|
|
532
613
|
|
|
533
614
|
for (const key in stylesObj) {
|
|
534
|
-
const style = this.styleFor('MyComponent', stylesObj[key], false)
|
|
615
|
+
const style = this.styleFor('MyComponent', stylesObj[key], false) as any
|
|
535
616
|
|
|
536
617
|
createdStyles[key] = style?.wrapper ?? style
|
|
537
618
|
}
|
|
@@ -551,12 +632,12 @@ export class CodeleapStyleRegistry {
|
|
|
551
632
|
prefixStyle(prefix: string, style: any) {
|
|
552
633
|
const entries = Object.entries(style).map(e => {
|
|
553
634
|
const [key, value] = e
|
|
554
|
-
|
|
635
|
+
|
|
555
636
|
const elementKey = capitalize(key)
|
|
556
|
-
|
|
637
|
+
|
|
557
638
|
return [`${prefix}${elementKey}`, value]
|
|
558
639
|
})
|
|
559
|
-
|
|
640
|
+
|
|
560
641
|
return Object.fromEntries(entries)
|
|
561
642
|
}
|
|
562
643
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'bun:test'
|
|
2
|
+
import { CodeleapStyleRegistry } from '../StyleRegistry'
|
|
3
|
+
import { createStyles, createStylesWithContext, CONTEXT_FACTORY_SYMBOL } from '../../lib/createStyles'
|
|
4
|
+
import { mockTheme, MockedTheme } from '../../tests/theme'
|
|
5
|
+
import { themeStore } from '../../theme'
|
|
6
|
+
import type { ICSS, ITheme } from '../../types'
|
|
7
|
+
|
|
8
|
+
class MockStateStorage {
|
|
9
|
+
private storage = new Map<string, any>()
|
|
10
|
+
|
|
11
|
+
getItem(key: string): any {
|
|
12
|
+
return this.storage.get(key) || null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setItem(key: string, value: any): void {
|
|
16
|
+
this.storage.set(key, value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
removeItem(key: string): void {
|
|
20
|
+
this.storage.delete(key)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class TestStyleRegistry extends CodeleapStyleRegistry {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(new MockStateStorage() as any)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
createStyle(css: ICSS): ICSS {
|
|
30
|
+
return css
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const TEST_COMPONENT = 'TestComponent'
|
|
35
|
+
|
|
36
|
+
const testComponent = {
|
|
37
|
+
styleRegistryName: TEST_COMPONENT,
|
|
38
|
+
elements: ['wrapper', 'text'],
|
|
39
|
+
rootElement: 'wrapper',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('StyleRegistry — context threading', () => {
|
|
43
|
+
let registry: TestStyleRegistry
|
|
44
|
+
let currentTheme: MockedTheme
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
mockTheme()
|
|
48
|
+
currentTheme = themeStore.theme as unknown as MockedTheme
|
|
49
|
+
registry = new TestStyleRegistry()
|
|
50
|
+
registry.registerComponent(testComponent as any)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('registerVariants — factory extraction', () => {
|
|
54
|
+
it('stores factory for variants created with createStylesWithContext', () => {
|
|
55
|
+
const factory = (_theme: ITheme, context: { isActive: boolean }) => ({
|
|
56
|
+
wrapper: { opacity: context.isActive ? 1 : 0.5 },
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const variants = {
|
|
60
|
+
active: createStylesWithContext(factory as any),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registry.registerVariants(TEST_COMPONENT, variants)
|
|
64
|
+
|
|
65
|
+
expect(registry.variantFactories[TEST_COMPONENT]?.active).toBe(factory as any)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('does not store factory for plain createStyles variants', () => {
|
|
69
|
+
const variants = {
|
|
70
|
+
default: createStyles((_theme: ITheme) => ({
|
|
71
|
+
wrapper: { opacity: 1 },
|
|
72
|
+
})),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
registry.registerVariants(TEST_COMPONENT, variants)
|
|
76
|
+
|
|
77
|
+
expect(registry.variantFactories[TEST_COMPONENT]).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('stores only context-aware factories in mixed registrations', () => {
|
|
81
|
+
const factory = (_theme: ITheme, context: { isActive: boolean }) => ({
|
|
82
|
+
wrapper: { opacity: context.isActive ? 1 : 0.5 },
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const variants = {
|
|
86
|
+
default: createStyles((_theme: ITheme) => ({ wrapper: { opacity: 1 } })),
|
|
87
|
+
active: createStylesWithContext(factory as any),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
registry.registerVariants(TEST_COMPONENT, variants)
|
|
91
|
+
|
|
92
|
+
const factories = registry.variantFactories[TEST_COMPONENT]
|
|
93
|
+
expect(factories?.default).toBeUndefined()
|
|
94
|
+
expect(factories?.active).toBe(factory as any)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('computeVariantStyle — context path', () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
const factory = (_theme: ITheme, context: { isActive: boolean }) => ({
|
|
101
|
+
wrapper: { opacity: context.isActive ? 1 : 0.5 },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const variants = {
|
|
105
|
+
default: createStyles((_theme: ITheme) => ({ wrapper: { color: 'black' } })),
|
|
106
|
+
active: createStylesWithContext(factory as any),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
registry.registerVariants(TEST_COMPONENT, variants)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('uses pre-computed stylesheet when no context is provided', () => {
|
|
113
|
+
const result = registry.computeVariantStyle(TEST_COMPONENT, ['default']) as any
|
|
114
|
+
expect(result?.['wrapper']?.['color']).toBe('black')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('calls factory with context when context is provided', () => {
|
|
118
|
+
const result = registry.computeVariantStyle(TEST_COMPONENT, ['active'], undefined, { isActive: 1 }) as any
|
|
119
|
+
expect(result?.['wrapper']?.['opacity']).toBe(1)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('produces different output for different context values', () => {
|
|
123
|
+
const active = registry.computeVariantStyle(TEST_COMPONENT, ['active'], undefined, { isActive: 1 }) as any
|
|
124
|
+
const inactive = registry.computeVariantStyle(TEST_COMPONENT, ['active'], undefined, { isActive: 0 }) as any
|
|
125
|
+
|
|
126
|
+
expect(active?.['wrapper']?.['opacity']).toBe(1)
|
|
127
|
+
expect(inactive?.['wrapper']?.['opacity']).toBe(0.5)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('caches separately for different context values', () => {
|
|
131
|
+
const resultA = registry.computeVariantStyle(TEST_COMPONENT, ['active'], undefined, { isActive: 1 })
|
|
132
|
+
const resultB = registry.computeVariantStyle(TEST_COMPONENT, ['active'], undefined, { isActive: 0 })
|
|
133
|
+
|
|
134
|
+
expect(resultA).not.toEqual(resultB)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('styleFor — context threading', () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
const factory = (_theme: ITheme, context: { isActive: boolean }) => ({
|
|
141
|
+
wrapper: { opacity: context.isActive ? 1 : 0.5 },
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const variants = {
|
|
145
|
+
default: createStyles((_theme: ITheme) => ({ wrapper: { color: 'black' } })),
|
|
146
|
+
active: createStylesWithContext(factory as any),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
registry.registerVariants(TEST_COMPONENT, variants)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('threads context into computeVariantStyle when style is a string', () => {
|
|
153
|
+
const result = registry.styleFor(TEST_COMPONENT, 'active', true, { isActive: 1 }) as any
|
|
154
|
+
expect(result?.['wrapper']?.['opacity']).toBe(1)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('threads context into computeVariantStyle when style is an array', () => {
|
|
158
|
+
const result = registry.styleFor(TEST_COMPONENT, ['active'], true, { isActive: 0 }) as any
|
|
159
|
+
expect(result?.['wrapper']?.['opacity']).toBe(0.5)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('produces different results for different contexts via styleFor', () => {
|
|
163
|
+
const active = registry.styleFor(TEST_COMPONENT, 'active', true, { isActive: 1 }) as any
|
|
164
|
+
const inactive = registry.styleFor(TEST_COMPONENT, 'active', true, { isActive: 0 }) as any
|
|
165
|
+
|
|
166
|
+
expect(active?.['wrapper']?.['opacity']).not.toBe(inactive?.['wrapper']?.['opacity'])
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
})
|
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
2
|
const isBrowser = ():boolean => typeof document !== 'undefined'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Package-wide feature flags and runtime constants. These are read once at module
|
|
6
|
+
* load time and are not reactive.
|
|
7
|
+
*
|
|
8
|
+
* - `STORES_PERSIST_VERSION` — bumping this value invalidates all persisted caches
|
|
9
|
+
* on the next load (because it is included in every `hashKey` call via `registerBaseKey`).
|
|
10
|
+
* - `STORE_CACHE_ENABLED` — when `false`, the persistent `Cache` instances skip
|
|
11
|
+
* loading from and writing to `StateStorage`.
|
|
12
|
+
* - `CACHE_ENABLED` — when `false`, `StyleCache.cacheFor` returns values without
|
|
13
|
+
* writing to any cache bucket, effectively disabling all in-memory caching.
|
|
14
|
+
* - `IS_BROWSER` — sniffed at load time via `typeof document`; used as a fast guard
|
|
15
|
+
* before any DOM access.
|
|
16
|
+
* - `LOG` — enables verbose `console.log` tracing inside `Cache` and `StylePersistor`.
|
|
17
|
+
*/
|
|
4
18
|
export const StyleConstants = {
|
|
5
19
|
STORES_PERSIST_VERSION: 1,
|
|
6
20
|
STORE_CACHE_ENABLED: true,
|
|
@@ -3,11 +3,13 @@ import { ICSS } from '../types'
|
|
|
3
3
|
import { getNestedStylesByKey } from '../utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Slices a flat component-style record into per-element sub-records, memoised to
|
|
7
|
+
* avoid unnecessary re-renders. For each element name in `composition`, it calls
|
|
8
|
+
* `getNestedStylesByKey(element, componentStyles)` to collect all keys that start
|
|
9
|
+
* with that element name. Accepts either a single element string or an array.
|
|
10
|
+
*
|
|
11
|
+
* Useful when a parent component receives a merged `componentStyles` object and
|
|
12
|
+
* needs to distribute the correct slice to each child element.
|
|
11
13
|
*/
|
|
12
14
|
export function useCompositionStyles<T extends string, C extends string>(
|
|
13
15
|
composition: (T | Array<T>),
|
|
@@ -18,7 +20,7 @@ export function useCompositionStyles<T extends string, C extends string>(
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
return useMemo(() => {
|
|
21
|
-
const compositionStyles = {}
|
|
23
|
+
const compositionStyles: Record<string, ICSS> = {}
|
|
22
24
|
|
|
23
25
|
if (Array.isArray(composition)) {
|
|
24
26
|
for (const element of composition) {
|
|
@@ -28,6 +30,6 @@ export function useCompositionStyles<T extends string, C extends string>(
|
|
|
28
30
|
compositionStyles[composition as string] = getNestedStylesByKey(composition, styles)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
return compositionStyles
|
|
33
|
+
return compositionStyles as Partial<Record<T, ICSS>>
|
|
32
34
|
}, [styles])
|
|
33
35
|
}
|