@tenphi/tasty 1.2.0 → 1.3.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/compute-styles.d.ts +31 -0
- package/dist/compute-styles.js +356 -0
- package/dist/compute-styles.js.map +1 -0
- package/dist/config.d.ts +7 -1
- package/dist/config.js +24 -20
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.js +6 -5
- package/dist/hooks/useCounterStyle.js +1 -1
- package/dist/hooks/useFontFace.js +1 -1
- package/dist/hooks/useGlobalStyles.js +3 -3
- package/dist/hooks/useKeyframes.js +2 -2
- package/dist/hooks/useProperty.js +1 -1
- package/dist/hooks/useRawCSS.js +1 -1
- package/dist/hooks/useStyles.d.ts +3 -8
- package/dist/hooks/useStyles.js +6 -212
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +7 -6
- package/dist/injector/index.d.ts +1 -18
- package/dist/injector/index.js +3 -16
- package/dist/injector/index.js.map +1 -1
- package/dist/ssr/collector.js +3 -10
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/next.js +7 -1
- package/dist/ssr/next.js.map +1 -1
- package/dist/tasty.d.ts +28 -13
- package/dist/tasty.js +69 -60
- package/dist/tasty.js.map +1 -1
- package/dist/utils/process-tokens.d.ts +1 -5
- package/dist/utils/process-tokens.js +1 -8
- package/dist/utils/process-tokens.js.map +1 -1
- package/docs/methodology.md +50 -1
- package/docs/runtime.md +90 -3
- package/docs/ssr.md +10 -10
- package/package.json +2 -2
|
@@ -12,10 +12,6 @@ import { Tokens } from "../types.js";
|
|
|
12
12
|
* @returns CSSProperties object or undefined if no tokens to process
|
|
13
13
|
*/
|
|
14
14
|
declare function processTokens(tokens: Tokens | undefined): CSSProperties | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Stringify tokens for memoization key.
|
|
17
|
-
*/
|
|
18
|
-
declare function stringifyTokens(tokens: Tokens | undefined): string;
|
|
19
15
|
//#endregion
|
|
20
|
-
export { processTokens
|
|
16
|
+
export { processTokens };
|
|
21
17
|
//# sourceMappingURL=process-tokens.d.ts.map
|
|
@@ -77,14 +77,7 @@ function processTokens(tokens) {
|
|
|
77
77
|
}
|
|
78
78
|
return result;
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Stringify tokens for memoization key.
|
|
82
|
-
*/
|
|
83
|
-
function stringifyTokens(tokens) {
|
|
84
|
-
if (!tokens) return "";
|
|
85
|
-
return JSON.stringify(tokens);
|
|
86
|
-
}
|
|
87
80
|
//#endregion
|
|
88
|
-
export { processTokens
|
|
81
|
+
export { processTokens };
|
|
89
82
|
|
|
90
83
|
//# sourceMappingURL=process-tokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-tokens.js","names":[],"sources":["../../src/utils/process-tokens.ts"],"sourcesContent":["import type { Tokens, TokenValue } from '../types';\n\nimport type { CSSProperties } from './css-types';\n\nimport { getColorSpaceComponents, getColorSpaceSuffix } from './color-space';\nimport { normalizeColorTokenValue, parseStyle } from './styles';\n\nexport { hslToRgbValues } from './color-math';\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n/**\n * Extract color components in the configured color space.\n * Returns a CSS variable reference for token colors, or decomposed\n * components as a space-separated string.\n */\nfunction extractColorSpaceValue(\n colorValue: string,\n parsedOutput: string,\n): string {\n const suffix = getColorSpaceSuffix();\n\n // If the parsed output references a color variable, use the companion variant\n const varMatch = parsedOutput.match(/var\\(--([a-z0-9-]+)-color\\)/);\n if (varMatch) {\n return `var(--${varMatch[1]}-color-${suffix})`;\n }\n\n // Try the original color value first, then parsed output\n const components = getColorSpaceComponents(colorValue);\n if (components !== colorValue) return components;\n\n const componentsFromParsed = getColorSpaceComponents(parsedOutput);\n if (componentsFromParsed !== parsedOutput) return componentsFromParsed;\n\n // Fallback: return the parsed output\n return parsedOutput;\n}\n\n/**\n * Check if a value is a valid token value (string, number, or boolean - not object).\n * Returns false for `false` values (they mean \"skip this token\").\n */\nfunction isValidTokenValue(\n value: unknown,\n): value is Exclude<TokenValue, undefined | null | false> {\n if (value === undefined || value === null || value === false) {\n return false;\n }\n\n if (typeof value === 'object') {\n if (devMode) {\n console.warn(\n 'Tasty: Object values are not allowed in tokens prop. ' +\n 'Tokens do not support state-based styling. Use a primitive value instead.',\n );\n }\n return false;\n }\n\n return (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n );\n}\n\n/**\n * Process a single token value through the tasty parser.\n * Numbers are converted to strings; 0 stays as \"0\".\n */\nfunction processTokenValue(value: string | number): string {\n if (typeof value === 'number') {\n // 0 should remain as \"0\", not converted to any unit\n if (value === 0) {\n return '0';\n }\n return parseStyle(String(value)).output;\n }\n return parseStyle(value).output;\n}\n\n/**\n * Process tokens object into inline style properties.\n * - $name -> --name with parsed value\n * - #name -> --name-color AND --name-color-{colorSpace} with parsed values\n *\n * @param tokens - The tokens object to process\n * @returns CSSProperties object or undefined if no tokens to process\n */\nexport function processTokens(\n tokens: Tokens | undefined,\n): CSSProperties | undefined {\n if (!tokens) {\n return undefined;\n }\n\n const keys = Object.keys(tokens);\n if (keys.length === 0) {\n return undefined;\n }\n\n let result: Record<string, string> | undefined;\n\n for (const key of keys) {\n const value = tokens[key as keyof Tokens];\n\n // Skip undefined/null values\n if (!isValidTokenValue(value)) {\n continue;\n }\n\n if (key.startsWith('$')) {\n // Custom property token: $name -> --name\n const propName = `--${key.slice(1)}`;\n // Boolean true for custom properties converts to empty string (valid CSS value)\n const effectiveValue = value === true ? '' : value;\n const processedValue = processTokenValue(effectiveValue);\n\n if (!result) result = {};\n result[propName] = processedValue;\n } else if (key.startsWith('#')) {\n const colorName = key.slice(1);\n const suffix = getColorSpaceSuffix();\n\n // Normalize color token value (true → 'transparent', false is already filtered by isValidTokenValue)\n const effectiveValue = normalizeColorTokenValue(value);\n // Skip if normalized to null (shouldn't happen since false is filtered by isValidTokenValue)\n if (effectiveValue === null) continue;\n\n const originalValue =\n typeof effectiveValue === 'number'\n ? String(effectiveValue)\n : effectiveValue;\n const lowerValue = originalValue.toLowerCase();\n const processedValue = processTokenValue(effectiveValue);\n\n if (!result) result = {};\n result[`--${colorName}-color`] = processedValue;\n\n // Skip component generation for #current values (currentcolor is dynamic, cannot decompose)\n if (/^#current(?:\\.|$)/i.test(lowerValue)) {\n continue;\n }\n\n result[`--${colorName}-color-${suffix}`] = extractColorSpaceValue(\n originalValue,\n processedValue,\n );\n }\n }\n\n return result as CSSProperties | undefined;\n}\n
|
|
1
|
+
{"version":3,"file":"process-tokens.js","names":[],"sources":["../../src/utils/process-tokens.ts"],"sourcesContent":["import type { Tokens, TokenValue } from '../types';\n\nimport type { CSSProperties } from './css-types';\n\nimport { getColorSpaceComponents, getColorSpaceSuffix } from './color-space';\nimport { normalizeColorTokenValue, parseStyle } from './styles';\n\nexport { hslToRgbValues } from './color-math';\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n/**\n * Extract color components in the configured color space.\n * Returns a CSS variable reference for token colors, or decomposed\n * components as a space-separated string.\n */\nfunction extractColorSpaceValue(\n colorValue: string,\n parsedOutput: string,\n): string {\n const suffix = getColorSpaceSuffix();\n\n // If the parsed output references a color variable, use the companion variant\n const varMatch = parsedOutput.match(/var\\(--([a-z0-9-]+)-color\\)/);\n if (varMatch) {\n return `var(--${varMatch[1]}-color-${suffix})`;\n }\n\n // Try the original color value first, then parsed output\n const components = getColorSpaceComponents(colorValue);\n if (components !== colorValue) return components;\n\n const componentsFromParsed = getColorSpaceComponents(parsedOutput);\n if (componentsFromParsed !== parsedOutput) return componentsFromParsed;\n\n // Fallback: return the parsed output\n return parsedOutput;\n}\n\n/**\n * Check if a value is a valid token value (string, number, or boolean - not object).\n * Returns false for `false` values (they mean \"skip this token\").\n */\nfunction isValidTokenValue(\n value: unknown,\n): value is Exclude<TokenValue, undefined | null | false> {\n if (value === undefined || value === null || value === false) {\n return false;\n }\n\n if (typeof value === 'object') {\n if (devMode) {\n console.warn(\n 'Tasty: Object values are not allowed in tokens prop. ' +\n 'Tokens do not support state-based styling. Use a primitive value instead.',\n );\n }\n return false;\n }\n\n return (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n );\n}\n\n/**\n * Process a single token value through the tasty parser.\n * Numbers are converted to strings; 0 stays as \"0\".\n */\nfunction processTokenValue(value: string | number): string {\n if (typeof value === 'number') {\n // 0 should remain as \"0\", not converted to any unit\n if (value === 0) {\n return '0';\n }\n return parseStyle(String(value)).output;\n }\n return parseStyle(value).output;\n}\n\n/**\n * Process tokens object into inline style properties.\n * - $name -> --name with parsed value\n * - #name -> --name-color AND --name-color-{colorSpace} with parsed values\n *\n * @param tokens - The tokens object to process\n * @returns CSSProperties object or undefined if no tokens to process\n */\nexport function processTokens(\n tokens: Tokens | undefined,\n): CSSProperties | undefined {\n if (!tokens) {\n return undefined;\n }\n\n const keys = Object.keys(tokens);\n if (keys.length === 0) {\n return undefined;\n }\n\n let result: Record<string, string> | undefined;\n\n for (const key of keys) {\n const value = tokens[key as keyof Tokens];\n\n // Skip undefined/null values\n if (!isValidTokenValue(value)) {\n continue;\n }\n\n if (key.startsWith('$')) {\n // Custom property token: $name -> --name\n const propName = `--${key.slice(1)}`;\n // Boolean true for custom properties converts to empty string (valid CSS value)\n const effectiveValue = value === true ? '' : value;\n const processedValue = processTokenValue(effectiveValue);\n\n if (!result) result = {};\n result[propName] = processedValue;\n } else if (key.startsWith('#')) {\n const colorName = key.slice(1);\n const suffix = getColorSpaceSuffix();\n\n // Normalize color token value (true → 'transparent', false is already filtered by isValidTokenValue)\n const effectiveValue = normalizeColorTokenValue(value);\n // Skip if normalized to null (shouldn't happen since false is filtered by isValidTokenValue)\n if (effectiveValue === null) continue;\n\n const originalValue =\n typeof effectiveValue === 'number'\n ? String(effectiveValue)\n : effectiveValue;\n const lowerValue = originalValue.toLowerCase();\n const processedValue = processTokenValue(effectiveValue);\n\n if (!result) result = {};\n result[`--${colorName}-color`] = processedValue;\n\n // Skip component generation for #current values (currentcolor is dynamic, cannot decompose)\n if (/^#current(?:\\.|$)/i.test(lowerValue)) {\n continue;\n }\n\n result[`--${colorName}-color-${suffix}`] = extractColorSpaceValue(\n originalValue,\n processedValue,\n );\n }\n }\n\n return result as CSSProperties | undefined;\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAS,uBACP,YACA,cACQ;CACR,MAAM,SAAS,qBAAqB;CAGpC,MAAM,WAAW,aAAa,MAAM,8BAA8B;AAClE,KAAI,SACF,QAAO,SAAS,SAAS,GAAG,SAAS,OAAO;CAI9C,MAAM,aAAa,wBAAwB,WAAW;AACtD,KAAI,eAAe,WAAY,QAAO;CAEtC,MAAM,uBAAuB,wBAAwB,aAAa;AAClE,KAAI,yBAAyB,aAAc,QAAO;AAGlD,QAAO;;;;;;AAOT,SAAS,kBACP,OACwD;AACxD,KAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,MACrD,QAAO;AAGT,KAAI,OAAO,UAAU,UAAU;AAE3B,UAAQ,KACN,iIAED;AAEH,SAAO;;AAGT,QACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;;;;;;AAQrB,SAAS,kBAAkB,OAAgC;AACzD,KAAI,OAAO,UAAU,UAAU;AAE7B,MAAI,UAAU,EACZ,QAAO;AAET,SAAO,WAAW,OAAO,MAAM,CAAC,CAAC;;AAEnC,QAAO,WAAW,MAAM,CAAC;;;;;;;;;;AAW3B,SAAgB,cACd,QAC2B;AAC3B,KAAI,CAAC,OACH;CAGF,MAAM,OAAO,OAAO,KAAK,OAAO;AAChC,KAAI,KAAK,WAAW,EAClB;CAGF,IAAI;AAEJ,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,OAAO;AAGrB,MAAI,CAAC,kBAAkB,MAAM,CAC3B;AAGF,MAAI,IAAI,WAAW,IAAI,EAAE;GAEvB,MAAM,WAAW,KAAK,IAAI,MAAM,EAAE;GAGlC,MAAM,iBAAiB,kBADA,UAAU,OAAO,KAAK,MACW;AAExD,OAAI,CAAC,OAAQ,UAAS,EAAE;AACxB,UAAO,YAAY;aACV,IAAI,WAAW,IAAI,EAAE;GAC9B,MAAM,YAAY,IAAI,MAAM,EAAE;GAC9B,MAAM,SAAS,qBAAqB;GAGpC,MAAM,iBAAiB,yBAAyB,MAAM;AAEtD,OAAI,mBAAmB,KAAM;GAE7B,MAAM,gBACJ,OAAO,mBAAmB,WACtB,OAAO,eAAe,GACtB;GACN,MAAM,aAAa,cAAc,aAAa;GAC9C,MAAM,iBAAiB,kBAAkB,eAAe;AAExD,OAAI,CAAC,OAAQ,UAAS,EAAE;AACxB,UAAO,KAAK,UAAU,WAAW;AAGjC,OAAI,qBAAqB,KAAK,WAAW,CACvC;AAGF,UAAO,KAAK,UAAU,SAAS,YAAY,uBACzC,eACA,eACD;;;AAIL,QAAO"}
|
package/docs/methodology.md
CHANGED
|
@@ -256,6 +256,54 @@ The `tokens` prop sets `style="--progress: 75%"` on the DOM element. The `$progr
|
|
|
256
256
|
|
|
257
257
|
Design tokens (via `configure({ tokens })`) are injected as CSS custom properties on `:root`. Replace tokens (via `configure({ replaceTokens })`) are resolved at parse time and baked into the generated CSS. The `tokens` prop on components is resolved at render time via inline CSS custom properties. Use design tokens for design-system constants, replace tokens for value aliases, and the `tokens` prop for truly dynamic per-instance values.
|
|
258
258
|
|
|
259
|
+
### tokenProps
|
|
260
|
+
|
|
261
|
+
`tokenProps` expose token keys as top-level component props — the token equivalent of `styleProps` and `modProps`. Use them when a component has a fixed set of known dynamic token values.
|
|
262
|
+
|
|
263
|
+
#### Array form
|
|
264
|
+
|
|
265
|
+
Prop names are plain camelCase identifiers. Names ending in `Color` map to `#` color tokens; everything else maps to `$` custom property tokens:
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
const ProgressBar = tasty({
|
|
269
|
+
tokenProps: ['progress', 'accentColor'] as const,
|
|
270
|
+
styles: { width: '$progress', fill: '#accent' },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Clean prop API — no tokens object needed
|
|
274
|
+
<ProgressBar progress="75%" accentColor="#purple" />
|
|
275
|
+
|
|
276
|
+
// Conversion:
|
|
277
|
+
// 'progress' → $progress → --progress
|
|
278
|
+
// 'accentColor' → #accent → --accent-color + --accent-color-oklch
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Object form
|
|
282
|
+
|
|
283
|
+
Keys are prop names; values are `$`/`#`-prefixed token keys. No suffix convention needed — the prefix in the value is explicit:
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
const Card = tasty({
|
|
287
|
+
tokenProps: {
|
|
288
|
+
size: '$card-size',
|
|
289
|
+
color: '#card-accent',
|
|
290
|
+
},
|
|
291
|
+
styles: { padding: '$card-size', fill: '#card-accent' },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
<Card size="4x" color="#purple" />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Merge order
|
|
298
|
+
|
|
299
|
+
When all three token sources are present, values merge with increasing priority:
|
|
300
|
+
|
|
301
|
+
1. `tokens` option in `tasty({...})` — default tokens (lowest)
|
|
302
|
+
2. `tokens` prop on the component instance — runtime overrides
|
|
303
|
+
3. `tokenProps`-derived values — highest priority (explicit named props win)
|
|
304
|
+
|
|
305
|
+
The `tokens` prop remains available for ad-hoc or dynamic tokens alongside `tokenProps`.
|
|
306
|
+
|
|
259
307
|
---
|
|
260
308
|
|
|
261
309
|
## styles prop vs style prop
|
|
@@ -458,7 +506,8 @@ See [Configuration](configuration.md) for the full `configure()` API.
|
|
|
458
506
|
- **Use `elements` prop** to declare typed sub-components for compound components
|
|
459
507
|
- **Use `styleProps`** to define what product engineers can customize
|
|
460
508
|
- **Use `modProps`** to expose known modifier states as clean component props
|
|
461
|
-
- **Use `
|
|
509
|
+
- **Use `tokenProps`** to expose known token keys as clean component props
|
|
510
|
+
- **Use `tokens` prop** for ad-hoc or dynamic per-instance token values (progress, user color)
|
|
462
511
|
- **Use modifiers** (`mods` or `modProps`) for state-driven style changes instead of runtime `styles` prop changes
|
|
463
512
|
|
|
464
513
|
### Avoid
|
package/docs/runtime.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Runtime API
|
|
2
2
|
|
|
3
|
-
The React-specific `tasty()` component factory, component props, and hooks. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
|
|
3
|
+
The React-specific `tasty()` component factory, component props, and hooks. `tasty()` components are hook-free and compatible with React Server Components — no `'use client'` directive needed. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -154,6 +154,75 @@ For architecture guidance on when to use modifiers vs `styleProps`, see [Methodo
|
|
|
154
154
|
|
|
155
155
|
---
|
|
156
156
|
|
|
157
|
+
## Token Props
|
|
158
|
+
|
|
159
|
+
Use `tokenProps` to expose token keys as direct component props instead of requiring the `tokens` object:
|
|
160
|
+
|
|
161
|
+
```jsx
|
|
162
|
+
// Before: tokens object
|
|
163
|
+
<ProgressBar tokens={{ $progress: '75%', '#accent': '#purple' }} />
|
|
164
|
+
|
|
165
|
+
// After: token props
|
|
166
|
+
<ProgressBar progress="75%" accentColor="#purple" />
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Array form
|
|
170
|
+
|
|
171
|
+
List prop names. Names ending in `Color` map to `#` color tokens; everything else maps to `$` custom property tokens:
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
const ProgressBar = tasty({
|
|
175
|
+
tokenProps: ['progress', 'accentColor'] as const,
|
|
176
|
+
styles: { width: '$progress', fill: '#accent' },
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
<ProgressBar progress="75%" accentColor="#purple" />
|
|
180
|
+
// 'progress' → $progress → --progress
|
|
181
|
+
// 'accentColor' → #accent → --accent-color + --accent-color-oklch
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Object form
|
|
185
|
+
|
|
186
|
+
Map prop names to explicit `$`/`#`-prefixed token keys:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
const Card = tasty({
|
|
190
|
+
tokenProps: {
|
|
191
|
+
size: '$card-size',
|
|
192
|
+
color: '#card-accent',
|
|
193
|
+
},
|
|
194
|
+
styles: { padding: '$card-size', fill: '#card-accent' },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
<Card size="4x" color="#purple" />
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Merge with `tokens`
|
|
201
|
+
|
|
202
|
+
Token props and the `tokens` prop can be used together. Token props take precedence over `tokens`, which takes precedence over default `tokens` in `tasty({...})`:
|
|
203
|
+
|
|
204
|
+
```jsx
|
|
205
|
+
const Bar = tasty({
|
|
206
|
+
tokenProps: ['progress'] as const,
|
|
207
|
+
tokens: { $progress: '0%' }, // default
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
<Bar tokens={{ $progress: '50%' }} progress="90%" />
|
|
211
|
+
// progress="90%" wins (from token prop)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### When to use `tokenProps` vs `tokens`
|
|
215
|
+
|
|
216
|
+
| Use case | Recommendation |
|
|
217
|
+
|---|---|
|
|
218
|
+
| Component has a fixed set of known token keys | `tokenProps` — cleaner API, better TypeScript autocomplete |
|
|
219
|
+
| Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>` |
|
|
220
|
+
| Both fixed and dynamic | Combine: `tokenProps` for known keys, `tokens` for ad-hoc |
|
|
221
|
+
|
|
222
|
+
For architecture guidance, see [Methodology — tokenProps](methodology.md#tokenprops).
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
157
226
|
## Variants
|
|
158
227
|
|
|
159
228
|
Define named style variations. Only CSS for variants actually used at runtime is injected:
|
|
@@ -264,11 +333,29 @@ For the mental model behind sub-elements — how they share root state context a
|
|
|
264
333
|
|
|
265
334
|
---
|
|
266
335
|
|
|
336
|
+
## computeStyles
|
|
337
|
+
|
|
338
|
+
Hook-free, synchronous style computation. Can be used anywhere — including React Server Components, plain functions, and non-React code:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
import { computeStyles } from '@tenphi/tasty';
|
|
342
|
+
|
|
343
|
+
const { className } = computeStyles({
|
|
344
|
+
padding: '2x',
|
|
345
|
+
fill: '#surface',
|
|
346
|
+
radius: '1r',
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
On the client, CSS is injected synchronously into the DOM (idempotent via the injector cache). On the server, CSS is collected via the SSR collector if one is available. This is the same function that `tasty()` components use internally.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
267
354
|
## Hooks
|
|
268
355
|
|
|
269
356
|
### useStyles
|
|
270
357
|
|
|
271
|
-
Generate a className from a style object:
|
|
358
|
+
Generate a className from a style object. Thin wrapper around `computeStyles()` that adds React context-based SSR collector discovery for backward compatibility:
|
|
272
359
|
|
|
273
360
|
```tsx
|
|
274
361
|
import { useStyles } from '@tenphi/tasty';
|
|
@@ -469,7 +556,7 @@ function useCounterStyle(
|
|
|
469
556
|
### Troubleshooting
|
|
470
557
|
|
|
471
558
|
- Styles are not updating: make sure `configure()` runs before first render, and verify the generated class name or global rule with [Debug Utilities](debug.md).
|
|
472
|
-
- SSR output looks wrong: check the [SSR guide](ssr.md)
|
|
559
|
+
- SSR output looks wrong: check the [SSR guide](ssr.md) for collector setup. `computeStyles()` discovers the SSR collector via `AsyncLocalStorage` or the global getter registered by `TastyRegistry`.
|
|
473
560
|
- Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
|
|
474
561
|
|
|
475
562
|
---
|
package/docs/ssr.md
CHANGED
|
@@ -18,16 +18,16 @@ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies
|
|
|
18
18
|
|
|
19
19
|
## How It Works
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
`tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` detects a `ServerStyleCollector` (via `AsyncLocalStorage` or an explicit option) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles, serializes them as `<style>` tags and a cache state script in the HTML. On the client, `hydrateTastyCache()` pre-populates the injector cache so that `computeStyles()` skips the rendering pipeline entirely during hydration.
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
Server Client
|
|
25
25
|
────── ──────
|
|
26
26
|
tasty() renders hydrateTastyCache() pre-populates cache
|
|
27
|
-
└─
|
|
27
|
+
└─ computeStyles() └─ cacheKey → className map ready
|
|
28
28
|
└─ collector.collect()
|
|
29
29
|
tasty() renders
|
|
30
|
-
After render: └─
|
|
30
|
+
After render: └─ computeStyles()
|
|
31
31
|
<style data-tasty-ssr> └─ cache hit → skip pipeline
|
|
32
32
|
<script data-tasty-cache> └─ no CSS re-injection
|
|
33
33
|
```
|
|
@@ -83,14 +83,14 @@ That's it. All `tasty()` components inside the tree automatically get SSR suppor
|
|
|
83
83
|
### How it works
|
|
84
84
|
|
|
85
85
|
- `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load.
|
|
86
|
-
- During SSR, `
|
|
86
|
+
- During SSR, `computeStyles()` finds the collector (registered globally by `TastyRegistry`) and collects CSS rules into it.
|
|
87
87
|
- `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible -- styles are injected alongside each Suspense boundary as it resolves.
|
|
88
88
|
- A companion `<script>` tag transfers the `cacheKey → className` mapping to the client.
|
|
89
|
-
- When the module loads on the client, `hydrateTastyCache()` runs automatically and pre-populates the injector cache. During hydration, `
|
|
89
|
+
- When the module loads on the client, `hydrateTastyCache()` runs automatically and pre-populates the injector cache. During hydration, `computeStyles()` hits the cache and skips the entire pipeline.
|
|
90
90
|
|
|
91
91
|
### Using tasty() in Server Components
|
|
92
92
|
|
|
93
|
-
`tasty()` components
|
|
93
|
+
`tasty()` components are hook-free and do not require `'use client'`. They can be used directly in React Server Components. Dynamic `styleProps` like `<Grid flow="column">` work normally in server components. During SSR, the `TastyRegistry` registers its collector globally so that `computeStyles()` can discover it without React context.
|
|
94
94
|
|
|
95
95
|
### Options
|
|
96
96
|
|
|
@@ -161,10 +161,10 @@ import Card from '../components/Card.tsx';
|
|
|
161
161
|
|
|
162
162
|
### How it works
|
|
163
163
|
|
|
164
|
-
Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware uses `AsyncLocalStorage` to make the collector available to all `
|
|
164
|
+
Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware uses `AsyncLocalStorage` to make the collector available to all `computeStyles()` calls within the request.
|
|
165
165
|
|
|
166
166
|
- **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>`. No JavaScript is shipped for these components.
|
|
167
|
-
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, importing `@tenphi/tasty/ssr/astro` auto-hydrates the cache from `<script data-tasty-cache>`. The island's `
|
|
167
|
+
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, importing `@tenphi/tasty/ssr/astro` auto-hydrates the cache from `<script data-tasty-cache>`. The island's `computeStyles()` calls hit the cache during hydration.
|
|
168
168
|
|
|
169
169
|
### Client-side hydration for islands
|
|
170
170
|
|
|
@@ -325,7 +325,7 @@ Server-safe style collector. One instance per request.
|
|
|
325
325
|
|
|
326
326
|
### `TastySSRContext`
|
|
327
327
|
|
|
328
|
-
React context (`createContext<ServerStyleCollector | null>(null)`). Used by `useStyles()` to find the collector during SSR.
|
|
328
|
+
React context (`createContext<ServerStyleCollector | null>(null)`). Used by the `useStyles()` hook to find the collector during SSR. Not needed when using `computeStyles()` directly (which discovers the collector via `AsyncLocalStorage` or the global getter registered by `TastyRegistry`).
|
|
329
329
|
|
|
330
330
|
### `TastyRegistry`
|
|
331
331
|
|
|
@@ -350,7 +350,7 @@ Pre-populate the client injector cache. When called without arguments, reads fro
|
|
|
350
350
|
|
|
351
351
|
### `runWithCollector(collector, fn)`
|
|
352
352
|
|
|
353
|
-
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `useStyles()` calls within `fn` (and async continuations) will find this collector.
|
|
353
|
+
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `computeStyles()` and `useStyles()` calls within `fn` (and async continuations) will find this collector.
|
|
354
354
|
|
|
355
355
|
---
|
|
356
356
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"name": "main (import *)",
|
|
151
151
|
"path": "dist/index.js",
|
|
152
152
|
"import": "*",
|
|
153
|
-
"limit": "
|
|
153
|
+
"limit": "48 kB"
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
"name": "core (import *)",
|