@tenphi/tasty 0.0.0-snapshot.e8f14d4 → 0.0.0-snapshot.e958ba8

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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/compute-styles.d.ts +8 -7
  3. package/dist/compute-styles.js +155 -14
  4. package/dist/compute-styles.js.map +1 -1
  5. package/dist/config.d.ts +20 -19
  6. package/dist/config.js +25 -26
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/index.d.ts +3 -3
  9. package/dist/core/index.js +2 -2
  10. package/dist/hooks/useCounterStyle.js +3 -4
  11. package/dist/hooks/useCounterStyle.js.map +1 -1
  12. package/dist/hooks/useFontFace.js +3 -4
  13. package/dist/hooks/useFontFace.js.map +1 -1
  14. package/dist/hooks/useGlobalStyles.js +4 -5
  15. package/dist/hooks/useGlobalStyles.js.map +1 -1
  16. package/dist/hooks/useKeyframes.js +3 -4
  17. package/dist/hooks/useKeyframes.js.map +1 -1
  18. package/dist/hooks/useProperty.js +3 -4
  19. package/dist/hooks/useProperty.js.map +1 -1
  20. package/dist/hooks/useRawCSS.js +3 -4
  21. package/dist/hooks/useRawCSS.js.map +1 -1
  22. package/dist/hooks/useStyles.d.ts +4 -4
  23. package/dist/hooks/useStyles.js +5 -7
  24. package/dist/hooks/useStyles.js.map +1 -1
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +2 -2
  27. package/dist/injector/index.d.ts +18 -2
  28. package/dist/injector/index.js +20 -1
  29. package/dist/injector/index.js.map +1 -1
  30. package/dist/injector/injector.d.ts +30 -3
  31. package/dist/injector/injector.js +107 -7
  32. package/dist/injector/injector.js.map +1 -1
  33. package/dist/injector/sheet-manager.d.ts +9 -13
  34. package/dist/injector/sheet-manager.js +31 -66
  35. package/dist/injector/sheet-manager.js.map +1 -1
  36. package/dist/injector/types.d.ts +43 -19
  37. package/dist/ssr/collector.js +3 -10
  38. package/dist/ssr/collector.js.map +1 -1
  39. package/dist/ssr/index.d.ts +1 -2
  40. package/dist/ssr/index.js +1 -2
  41. package/dist/ssr/index.js.map +1 -1
  42. package/dist/ssr/next.d.ts +1 -3
  43. package/dist/ssr/next.js +1 -2
  44. package/dist/ssr/next.js.map +1 -1
  45. package/dist/tasty.d.ts +1 -1
  46. package/dist/tasty.js +17 -8
  47. package/dist/tasty.js.map +1 -1
  48. package/dist/utils/filter-base-props.d.ts +1 -1
  49. package/dist/utils/filter-base-props.js.map +1 -1
  50. package/docs/dsl.md +20 -5
  51. package/docs/injector.md +32 -18
  52. package/docs/ssr.md +12 -42
  53. package/package.json +4 -4
  54. package/dist/hooks/resolve-ssr-collector.js +0 -14
  55. package/dist/hooks/resolve-ssr-collector.js.map +0 -1
  56. package/dist/ssr/context.d.ts +0 -8
  57. package/dist/ssr/context.js +0 -13
  58. package/dist/ssr/context.js.map +0 -1
package/README.md CHANGED
@@ -347,7 +347,7 @@ Every style property accepts a state mapping object. Keys can be combined with b
347
347
  | Feature query | `@supports(display: grid)` | `@supports (display: grid)` |
348
348
  | Entry animation | `@starting` | `@starting-style` |
349
349
 
350
- Combine with `&` (AND), `|` (OR), `!` (NOT):
350
+ Combine with `&` (AND), `|` (OR), `!` (NOT), `^` (XOR):
351
351
 
352
352
  ```tsx
353
353
  fill: {
@@ -4,6 +4,8 @@ import { ServerStyleCollector } from "./ssr/collector.js";
4
4
  //#region src/compute-styles.d.ts
5
5
  interface ComputeStylesResult {
6
6
  className: string;
7
+ /** CSS text to emit as an inline <style> tag (RSC mode only). */
8
+ css?: string;
7
9
  }
8
10
  interface ComputeStylesOptions {
9
11
  ssrCollector?: ServerStyleCollector | null;
@@ -12,14 +14,13 @@ interface ComputeStylesOptions {
12
14
  * Synchronous, hook-free style computation.
13
15
  *
14
16
  * Resolves recipes, categorizes style keys into chunks, renders CSS rules,
15
- * allocates class names, and injects (client) or collects (SSR) the CSS.
17
+ * allocates class names, and injects / collects / returns the CSS.
16
18
  *
17
- * On the client, CSS is injected synchronously into the DOM. The injector's
18
- * content-based cache makes this idempotent repeated calls with the same
19
- * styles are essentially free (Map lookup + refCount bump).
20
- *
21
- * On the server, an SSR collector is discovered via AsyncLocalStorage
22
- * (or passed explicitly via options) and CSS is collected as strings.
19
+ * Three code paths:
20
+ * 1. SSR collector discovered via ALS or passed explicitly; CSS collected
21
+ * 2. RSC inline no collector and no `document`; CSS returned as `result.css`
22
+ * for the caller to emit as an inline `<style>` tag
23
+ * 3. Client inject CSS injected synchronously into the DOM (idempotent)
23
24
  *
24
25
  * @param styles - Tasty styles object (or undefined for no styles)
25
26
  * @param options - Optional SSR collector override
@@ -1,31 +1,171 @@
1
1
  import { extractLocalProperties, hasLocalProperties } from "./properties/index.js";
2
2
  import { extractLocalFontFace, fontFaceContentHash, formatFontFaceRule, hasLocalFontFace } from "./font-face/index.js";
3
3
  import { extractLocalCounterStyle, formatCounterStyleRule, hasLocalCounterStyle } from "./counter-style/index.js";
4
- import { getConfig, getGlobalKeyframes, hasGlobalKeyframes } from "./config.js";
4
+ import { renderStyles } from "./pipeline/index.js";
5
+ import { getConfig, getEffectiveProperties, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace, getGlobalKeyframes, hasGlobalKeyframes } from "./config.js";
5
6
  import { categorizeStyleKeys } from "./chunks/definitions.js";
6
7
  import { generateChunkCacheKey } from "./chunks/cacheKey.js";
7
8
  import { renderStylesForChunk } from "./chunks/renderChunk.js";
8
- import { counterStyle, fontFace, inject, keyframes, property } from "./injector/index.js";
9
+ import { counterStyle, fontFace, inject, keyframes, property, touch } from "./injector/index.js";
9
10
  import { extractAnimationNamesFromStyles, extractLocalKeyframes, filterUsedKeyframes, hasLocalKeyframes, mergeKeyframes, replaceAnimationNames } from "./keyframes/index.js";
10
11
  import { formatPropertyCSS } from "./ssr/format-property.js";
11
12
  import { collectAutoInferredProperties } from "./ssr/collect-auto-properties.js";
12
- import { getRegisteredSSRCollector } from "./ssr/ssr-collector-ref.js";
13
+ import { formatGlobalRules } from "./ssr/format-global-rules.js";
13
14
  import { formatKeyframesCSS } from "./ssr/format-keyframes.js";
15
+ import { formatRules } from "./ssr/format-rules.js";
16
+ import { getRegisteredSSRCollector } from "./ssr/ssr-collector-ref.js";
14
17
  import { hasKeys } from "./utils/has-keys.js";
15
18
  import { resolveRecipes } from "./utils/resolve-recipes.js";
19
+ import { cache } from "react";
16
20
  //#region src/compute-styles.ts
17
21
  /**
18
22
  * Hook-free, synchronous style computation.
19
23
  *
20
24
  * Extracts the core logic from useStyles() into a plain function that can
21
- * be called during React render without any hooks. On the client, styles
22
- * are injected synchronously (idempotent via the injector cache). On the
23
- * server, styles are collected via the SSR collector.
25
+ * be called during React render without any hooks. Three code paths:
26
+ *
27
+ * 1. SSR collector — styles collected via ServerStyleCollector
28
+ * 2. Client inject — styles injected synchronously into the DOM
29
+ * 3. RSC inline — styles returned as CSS strings for inline <style> emission
24
30
  *
25
31
  * This enables tasty() components to work as React Server Components.
26
32
  */
27
33
  const EMPTY_RESULT = { className: "" };
28
34
  /**
35
+ * Per-request RSC style cache using React.cache.
36
+ * React.cache provides per-request memoization in Server Components,
37
+ * so each request gets its own isolated cache.
38
+ */
39
+ const getRSCCache = cache(() => ({
40
+ cacheKeyToClassName: /* @__PURE__ */ new Map(),
41
+ classCounter: 0,
42
+ emittedKeys: /* @__PURE__ */ new Set(),
43
+ internalsEmitted: false
44
+ }));
45
+ function rscAllocateClassName(rscCache, cacheKey) {
46
+ const existing = rscCache.cacheKeyToClassName.get(cacheKey);
47
+ if (existing) return {
48
+ className: existing,
49
+ isNew: false
50
+ };
51
+ const className = `r${rscCache.classCounter++}`;
52
+ rscCache.cacheKeyToClassName.set(cacheKey, className);
53
+ return {
54
+ className,
55
+ isNew: true
56
+ };
57
+ }
58
+ /**
59
+ * Collect internals CSS for RSC — mirrors ServerStyleCollector.collectInternals().
60
+ * Emitted once per request (tracked via rscCache.internalsEmitted).
61
+ */
62
+ function collectInternalsRSC(rscCache) {
63
+ if (rscCache.internalsEmitted) return "";
64
+ rscCache.internalsEmitted = true;
65
+ const parts = [];
66
+ for (const [token, definition] of Object.entries(getEffectiveProperties())) {
67
+ const css = formatPropertyCSS(token, definition);
68
+ if (css) parts.push(css);
69
+ }
70
+ const tokenStyles = getGlobalConfigTokens();
71
+ if (tokenStyles && Object.keys(tokenStyles).length > 0) {
72
+ const tokenRules = renderStyles(tokenStyles, ":root");
73
+ if (tokenRules.length > 0) {
74
+ const css = formatGlobalRules(tokenRules);
75
+ if (css) parts.push(css);
76
+ }
77
+ }
78
+ const globalFF = getGlobalFontFace();
79
+ if (globalFF) for (const [family, input] of Object.entries(globalFF)) {
80
+ const descriptors = Array.isArray(input) ? input : [input];
81
+ for (const desc of descriptors) parts.push(formatFontFaceRule(family, desc));
82
+ }
83
+ const globalCS = getGlobalCounterStyle();
84
+ if (globalCS) for (const [name, descriptors] of Object.entries(globalCS)) parts.push(formatCounterStyleRule(name, descriptors));
85
+ return parts.join("\n");
86
+ }
87
+ /**
88
+ * Collect per-component ancillary CSS (keyframes, @property, font-face,
89
+ * counter-style) for RSC mode.
90
+ */
91
+ function collectAncillaryRSC(rscCache, styles) {
92
+ const parts = [];
93
+ const usedKf = getUsedKeyframes(styles);
94
+ if (usedKf) for (const [name, steps] of Object.entries(usedKf)) {
95
+ const key = `__kf:${name}`;
96
+ if (!rscCache.emittedKeys.has(key)) {
97
+ rscCache.emittedKeys.add(key);
98
+ parts.push(formatKeyframesCSS(name, steps));
99
+ }
100
+ }
101
+ if (hasLocalProperties(styles)) {
102
+ const localProperties = extractLocalProperties(styles);
103
+ if (localProperties) for (const [token, definition] of Object.entries(localProperties)) {
104
+ const key = `__prop:${token}`;
105
+ if (!rscCache.emittedKeys.has(key)) {
106
+ rscCache.emittedKeys.add(key);
107
+ const css = formatPropertyCSS(token, definition);
108
+ if (css) parts.push(css);
109
+ }
110
+ }
111
+ }
112
+ if (hasLocalFontFace(styles)) {
113
+ const localFontFace = extractLocalFontFace(styles);
114
+ if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
115
+ const descriptors = Array.isArray(input) ? input : [input];
116
+ for (const desc of descriptors) {
117
+ const key = `__ff:${fontFaceContentHash(family, desc)}`;
118
+ if (!rscCache.emittedKeys.has(key)) {
119
+ rscCache.emittedKeys.add(key);
120
+ parts.push(formatFontFaceRule(family, desc));
121
+ }
122
+ }
123
+ }
124
+ }
125
+ if (hasLocalCounterStyle(styles)) {
126
+ const localCounterStyle = extractLocalCounterStyle(styles);
127
+ if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) {
128
+ const key = `__cs:${name}`;
129
+ if (!rscCache.emittedKeys.has(key)) {
130
+ rscCache.emittedKeys.add(key);
131
+ parts.push(formatCounterStyleRule(name, descriptors));
132
+ }
133
+ }
134
+ }
135
+ return parts.join("\n");
136
+ }
137
+ /**
138
+ * Process all chunks in RSC mode: render CSS to strings, allocate classNames,
139
+ * and return combined { className, css }.
140
+ */
141
+ function computeStylesRSC(styles, chunkMap) {
142
+ const rscCache = getRSCCache();
143
+ const cssParts = [];
144
+ const classNames = [];
145
+ const internalsCSS = collectInternalsRSC(rscCache);
146
+ if (internalsCSS) cssParts.push(internalsCSS);
147
+ for (const [chunkName, chunkStyleKeys] of chunkMap) {
148
+ if (chunkStyleKeys.length === 0) continue;
149
+ const { className, isNew } = rscAllocateClassName(rscCache, generateChunkCacheKey(styles, chunkName, chunkStyleKeys));
150
+ classNames.push(className);
151
+ if (isNew) {
152
+ const renderResult = renderStylesForChunk(styles, chunkName, chunkStyleKeys);
153
+ if (renderResult.rules.length > 0) {
154
+ const css = formatRules(renderResult.rules, className);
155
+ if (css) cssParts.push(css);
156
+ }
157
+ }
158
+ }
159
+ const ancillaryCSS = collectAncillaryRSC(rscCache, styles);
160
+ if (ancillaryCSS) cssParts.push(ancillaryCSS);
161
+ if (classNames.length === 0) return EMPTY_RESULT;
162
+ const css = cssParts.join("\n");
163
+ return {
164
+ className: classNames.join(" "),
165
+ css: css || void 0
166
+ };
167
+ }
168
+ /**
29
169
  * Get keyframes that are actually used in styles.
30
170
  * Returns null if no keyframes are used (fast path for zero overhead).
31
171
  */
@@ -171,14 +311,13 @@ function collectAncillarySSR(collector, styles, chunks) {
171
311
  * Synchronous, hook-free style computation.
172
312
  *
173
313
  * Resolves recipes, categorizes style keys into chunks, renders CSS rules,
174
- * allocates class names, and injects (client) or collects (SSR) the CSS.
175
- *
176
- * On the client, CSS is injected synchronously into the DOM. The injector's
177
- * content-based cache makes this idempotent — repeated calls with the same
178
- * styles are essentially free (Map lookup + refCount bump).
314
+ * allocates class names, and injects / collects / returns the CSS.
179
315
  *
180
- * On the server, an SSR collector is discovered via AsyncLocalStorage
181
- * (or passed explicitly via options) and CSS is collected as strings.
316
+ * Three code paths:
317
+ * 1. SSR collector — discovered via ALS or passed explicitly; CSS collected
318
+ * 2. RSC inline — no collector and no `document`; CSS returned as `result.css`
319
+ * for the caller to emit as an inline `<style>` tag
320
+ * 3. Client inject — CSS injected synchronously into the DOM (idempotent)
182
321
  *
183
322
  * @param styles - Tasty styles object (or undefined for no styles)
184
323
  * @param options - Optional SSR collector override
@@ -196,7 +335,8 @@ function computeStyles(styles, options) {
196
335
  if (chunk) chunks.push(chunk);
197
336
  }
198
337
  collectAncillarySSR(collector, resolved, chunks);
199
- } else {
338
+ } else if (typeof document === "undefined") return computeStylesRSC(resolved, chunkMap);
339
+ else {
200
340
  injectAncillarySync(resolved);
201
341
  const usedKf = getUsedKeyframes(resolved);
202
342
  const nameMap = usedKf ? injectKeyframesSync(usedKf) : null;
@@ -205,6 +345,7 @@ function computeStyles(styles, options) {
205
345
  if (chunk) chunks.push(chunk);
206
346
  }
207
347
  if (nameMap) injectChunkRulesSync(chunks, nameMap);
348
+ for (const chunk of chunks) touch(chunk.className);
208
349
  }
209
350
  if (chunks.length === 0) return EMPTY_RESULT;
210
351
  if (chunks.length === 1) return { className: chunks[0].className };
@@ -1 +1 @@
1
- {"version":3,"file":"compute-styles.js","names":[],"sources":["../src/compute-styles.ts"],"sourcesContent":["/**\n * Hook-free, synchronous style computation.\n *\n * Extracts the core logic from useStyles() into a plain function that can\n * be called during React render without any hooks. On the client, styles\n * are injected synchronously (idempotent via the injector cache). On the\n * server, styles are collected via the SSR collector.\n *\n * This enables tasty() components to work as React Server Components.\n */\n\nimport {\n categorizeStyleKeys,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from './chunks';\nimport { getConfig, getGlobalKeyframes, hasGlobalKeyframes } from './config';\nimport {\n counterStyle,\n fontFace,\n inject,\n keyframes,\n property,\n} from './injector';\nimport type { FontFaceDescriptors, KeyframesSteps } from './injector/types';\nimport {\n extractLocalCounterStyle,\n formatCounterStyleRule,\n hasLocalCounterStyle,\n} from './counter-style';\nimport {\n extractLocalFontFace,\n fontFaceContentHash,\n formatFontFaceRule,\n hasLocalFontFace,\n} from './font-face';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n replaceAnimationNames,\n} from './keyframes';\nimport type { RenderResult, StyleResult } from './pipeline';\nimport { extractLocalProperties, hasLocalProperties } from './properties';\nimport { collectAutoInferredProperties } from './ssr/collect-auto-properties';\nimport type { ServerStyleCollector } from './ssr/collector';\nimport { getRegisteredSSRCollector } from './ssr/ssr-collector-ref';\nimport { formatKeyframesCSS } from './ssr/format-keyframes';\nimport { formatPropertyCSS } from './ssr/format-property';\nimport type { Styles } from './styles/types';\nimport { hasKeys } from './utils/has-keys';\nimport { resolveRecipes } from './utils/resolve-recipes';\n\nexport interface ComputeStylesResult {\n className: string;\n}\n\nexport interface ComputeStylesOptions {\n ssrCollector?: ServerStyleCollector | null;\n}\n\ninterface ProcessedChunk {\n name: string;\n styleKeys: string[];\n cacheKey: string;\n renderResult: RenderResult;\n className: string;\n}\n\nconst EMPTY_RESULT: ComputeStylesResult = { className: '' };\n\n/**\n * Get keyframes that are actually used in styles.\n * Returns null if no keyframes are used (fast path for zero overhead).\n */\nfunction getUsedKeyframes(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n const hasLocal = hasLocalKeyframes(styles);\n const hasGlobal = hasGlobalKeyframes();\n if (!hasLocal && !hasGlobal) return null;\n\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return null;\n\n const local = hasLocal ? extractLocalKeyframes(styles) : null;\n const global = hasGlobal ? getGlobalKeyframes() : null;\n const allKeyframes = mergeKeyframes(local, global);\n\n return filterUsedKeyframes(allKeyframes, usedNames);\n}\n\n/**\n * Process a chunk on the SSR path: allocate via collector, render, collect CSS.\n */\nfunction processChunkSSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const { className, isNewAllocation } = collector.allocateClassName(cacheKey);\n\n if (isNewAllocation) {\n const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);\n if (renderResult.rules.length > 0) {\n collector.collectChunk(cacheKey, className, renderResult.rules);\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n }\n return null;\n }\n\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult: { rules: [] },\n className,\n };\n}\n\n/**\n * Process a chunk on the client: render, allocate className, and inject\n * CSS synchronously. The injector's cache makes this idempotent.\n */\nfunction processChunkSync(\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n styleKeys,\n cacheKey,\n );\n if (renderResult.rules.length === 0) return null;\n\n const { className } = inject(renderResult.rules, { cacheKey });\n\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n}\n\n/**\n * Inject keyframes synchronously and return a name replacement map.\n * On the client, keyframes are injected into the DOM.\n */\nfunction injectKeyframesSync(\n usedKeyframes: Record<string, KeyframesSteps>,\n): Map<string, string> | null {\n let nameMap: Map<string, string> | null = null;\n\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const result = keyframes(steps, { name });\n const injectedName = result.toString();\n if (injectedName !== name) {\n if (!nameMap) nameMap = new Map();\n nameMap.set(name, injectedName);\n }\n }\n\n return nameMap;\n}\n\n/**\n * Inject chunk rules synchronously, replacing animation names if needed.\n */\nfunction injectChunkRulesSync(\n chunks: ProcessedChunk[],\n nameMap: Map<string, string> | null,\n): void {\n for (const chunk of chunks) {\n if (chunk.renderResult.rules.length > 0) {\n const rulesToInject: StyleResult[] = nameMap\n ? chunk.renderResult.rules.map((rule) => ({\n ...rule,\n declarations: replaceAnimationNames(rule.declarations, nameMap!),\n }))\n : chunk.renderResult.rules;\n\n inject(rulesToInject, { cacheKey: chunk.cacheKey });\n }\n }\n}\n\n/**\n * Inject all ancillary rules (properties, font-faces, counter-styles) synchronously.\n */\nfunction injectAncillarySync(styles: Styles): void {\n if (hasLocalProperties(styles)) {\n const localProperties = extractLocalProperties(styles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n property(token, definition);\n }\n }\n }\n\n if (hasLocalFontFace(styles)) {\n const localFontFace = extractLocalFontFace(styles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n fontFace(family, desc);\n }\n }\n }\n }\n\n if (hasLocalCounterStyle(styles)) {\n const localCounterStyle = extractLocalCounterStyle(styles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n counterStyle(name, descriptors);\n }\n }\n }\n}\n\n/**\n * Collect all ancillary rules into the SSR collector.\n */\nfunction collectAncillarySSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunks: ProcessedChunk[],\n): void {\n const usedKf = getUsedKeyframes(styles);\n if (usedKf) {\n for (const [name, steps] of Object.entries(usedKf)) {\n const css = formatKeyframesCSS(name, steps);\n collector.collectKeyframes(name, css);\n }\n }\n\n if (hasLocalProperties(styles)) {\n const localProperties = extractLocalProperties(styles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n collector.collectProperty(token, css);\n }\n }\n }\n }\n\n if (hasLocalFontFace(styles)) {\n const localFontFace = extractLocalFontFace(styles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n collector.collectFontFace(hash, css);\n }\n }\n }\n }\n\n if (hasLocalCounterStyle(styles)) {\n const localCounterStyle = extractLocalCounterStyle(styles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n const css = formatCounterStyleRule(name, descriptors);\n collector.collectCounterStyle(name, css);\n }\n }\n }\n\n if (getConfig().autoPropertyTypes !== false) {\n const allRules = chunks.flatMap((c) => c.renderResult.rules);\n if (allRules.length > 0) {\n collectAutoInferredProperties(allRules, collector, styles);\n }\n }\n}\n\n/**\n * Synchronous, hook-free style computation.\n *\n * Resolves recipes, categorizes style keys into chunks, renders CSS rules,\n * allocates class names, and injects (client) or collects (SSR) the CSS.\n *\n * On the client, CSS is injected synchronously into the DOM. The injector's\n * content-based cache makes this idempotent — repeated calls with the same\n * styles are essentially free (Map lookup + refCount bump).\n *\n * On the server, an SSR collector is discovered via AsyncLocalStorage\n * (or passed explicitly via options) and CSS is collected as strings.\n *\n * @param styles - Tasty styles object (or undefined for no styles)\n * @param options - Optional SSR collector override\n */\nexport function computeStyles(\n styles: Styles | undefined,\n options?: ComputeStylesOptions,\n): ComputeStylesResult {\n if (!styles || !hasKeys(styles as Record<string, unknown>)) {\n return EMPTY_RESULT;\n }\n\n const resolved = resolveRecipes(styles);\n const chunkMap = categorizeStyleKeys(resolved as Record<string, unknown>);\n\n const collector =\n options?.ssrCollector !== undefined\n ? options.ssrCollector\n : getRegisteredSSRCollector();\n\n const chunks: ProcessedChunk[] = [];\n\n if (collector) {\n collector.collectInternals();\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSSR(\n collector,\n resolved,\n chunkName,\n chunkStyleKeys,\n );\n if (chunk) chunks.push(chunk);\n }\n\n collectAncillarySSR(collector, resolved, chunks);\n } else {\n injectAncillarySync(resolved);\n\n const usedKf = getUsedKeyframes(resolved);\n const nameMap = usedKf ? injectKeyframesSync(usedKf) : null;\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSync(resolved, chunkName, chunkStyleKeys);\n if (chunk) chunks.push(chunk);\n }\n\n if (nameMap) {\n injectChunkRulesSync(chunks, nameMap);\n }\n }\n\n if (chunks.length === 0) return EMPTY_RESULT;\n if (chunks.length === 1) return { className: chunks[0].className };\n\n return { className: chunks.map((c) => c.className).join(' ') };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"compute-styles.js","names":[],"sources":["../src/compute-styles.ts"],"sourcesContent":["/**\n * Hook-free, synchronous style computation.\n *\n * Extracts the core logic from useStyles() into a plain function that can\n * be called during React render without any hooks. Three code paths:\n *\n * 1. SSR collector — styles collected via ServerStyleCollector\n * 2. Client inject — styles injected synchronously into the DOM\n * 3. RSC inline — styles returned as CSS strings for inline <style> emission\n *\n * This enables tasty() components to work as React Server Components.\n */\n\nimport { cache } from 'react';\n\nimport {\n categorizeStyleKeys,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from './chunks';\nimport {\n getConfig,\n getGlobalConfigTokens,\n getGlobalCounterStyle,\n getEffectiveProperties,\n getGlobalFontFace,\n getGlobalKeyframes,\n hasGlobalKeyframes,\n} from './config';\nimport {\n counterStyle,\n fontFace,\n inject,\n keyframes,\n property,\n touch,\n} from './injector';\nimport type { FontFaceDescriptors, KeyframesSteps } from './injector/types';\nimport {\n extractLocalCounterStyle,\n formatCounterStyleRule,\n hasLocalCounterStyle,\n} from './counter-style';\nimport {\n extractLocalFontFace,\n fontFaceContentHash,\n formatFontFaceRule,\n hasLocalFontFace,\n} from './font-face';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n replaceAnimationNames,\n} from './keyframes';\nimport type { RenderResult, StyleResult } from './pipeline';\nimport { renderStyles } from './pipeline';\nimport { extractLocalProperties, hasLocalProperties } from './properties';\nimport { collectAutoInferredProperties } from './ssr/collect-auto-properties';\nimport type { ServerStyleCollector } from './ssr/collector';\nimport { formatGlobalRules } from './ssr/format-global-rules';\nimport { formatKeyframesCSS } from './ssr/format-keyframes';\nimport { formatPropertyCSS } from './ssr/format-property';\nimport { formatRules } from './ssr/format-rules';\nimport { getRegisteredSSRCollector } from './ssr/ssr-collector-ref';\nimport type { Styles } from './styles/types';\nimport { hasKeys } from './utils/has-keys';\nimport { resolveRecipes } from './utils/resolve-recipes';\n\nexport interface ComputeStylesResult {\n className: string;\n /** CSS text to emit as an inline <style> tag (RSC mode only). */\n css?: string;\n}\n\nexport interface ComputeStylesOptions {\n ssrCollector?: ServerStyleCollector | null;\n}\n\ninterface ProcessedChunk {\n name: string;\n styleKeys: string[];\n cacheKey: string;\n renderResult: RenderResult;\n className: string;\n}\n\nconst EMPTY_RESULT: ComputeStylesResult = { className: '' };\n\n// ---------------------------------------------------------------------------\n// RSC (React Server Components) inline style support\n// ---------------------------------------------------------------------------\n\ninterface RSCStyleCache {\n cacheKeyToClassName: Map<string, string>;\n classCounter: number;\n emittedKeys: Set<string>;\n internalsEmitted: boolean;\n}\n\n/**\n * Per-request RSC style cache using React.cache.\n * React.cache provides per-request memoization in Server Components,\n * so each request gets its own isolated cache.\n */\nconst getRSCCache = cache(\n (): RSCStyleCache => ({\n cacheKeyToClassName: new Map(),\n classCounter: 0,\n emittedKeys: new Set(),\n internalsEmitted: false,\n }),\n);\n\nfunction rscAllocateClassName(\n rscCache: RSCStyleCache,\n cacheKey: string,\n): { className: string; isNew: boolean } {\n const existing = rscCache.cacheKeyToClassName.get(cacheKey);\n if (existing) return { className: existing, isNew: false };\n\n // Use 'r' prefix to avoid collisions with SSR collector's 't' prefix\n const className = `r${rscCache.classCounter++}`;\n rscCache.cacheKeyToClassName.set(cacheKey, className);\n return { className, isNew: true };\n}\n\n/**\n * Collect internals CSS for RSC — mirrors ServerStyleCollector.collectInternals().\n * Emitted once per request (tracked via rscCache.internalsEmitted).\n */\nfunction collectInternalsRSC(rscCache: RSCStyleCache): string {\n if (rscCache.internalsEmitted) return '';\n rscCache.internalsEmitted = true;\n\n const parts: string[] = [];\n\n for (const [token, definition] of Object.entries(getEffectiveProperties())) {\n const css = formatPropertyCSS(token, definition);\n if (css) parts.push(css);\n }\n\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const tokenRules = renderStyles(tokenStyles, ':root') as StyleResult[];\n if (tokenRules.length > 0) {\n const css = formatGlobalRules(tokenRules);\n if (css) parts.push(css);\n }\n }\n\n const globalFF = getGlobalFontFace();\n if (globalFF) {\n for (const [family, input] of Object.entries(globalFF)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n parts.push(formatFontFaceRule(family, desc));\n }\n }\n }\n\n const globalCS = getGlobalCounterStyle();\n if (globalCS) {\n for (const [name, descriptors] of Object.entries(globalCS)) {\n parts.push(formatCounterStyleRule(name, descriptors));\n }\n }\n\n return parts.join('\\n');\n}\n\n/**\n * Collect per-component ancillary CSS (keyframes, @property, font-face,\n * counter-style) for RSC mode.\n */\nfunction collectAncillaryRSC(rscCache: RSCStyleCache, styles: Styles): string {\n const parts: string[] = [];\n\n const usedKf = getUsedKeyframes(styles);\n if (usedKf) {\n for (const [name, steps] of Object.entries(usedKf)) {\n const key = `__kf:${name}`;\n if (!rscCache.emittedKeys.has(key)) {\n rscCache.emittedKeys.add(key);\n parts.push(formatKeyframesCSS(name, steps));\n }\n }\n }\n\n if (hasLocalProperties(styles)) {\n const localProperties = extractLocalProperties(styles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n const key = `__prop:${token}`;\n if (!rscCache.emittedKeys.has(key)) {\n rscCache.emittedKeys.add(key);\n const css = formatPropertyCSS(token, definition);\n if (css) parts.push(css);\n }\n }\n }\n }\n\n if (hasLocalFontFace(styles)) {\n const localFontFace = extractLocalFontFace(styles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const key = `__ff:${hash}`;\n if (!rscCache.emittedKeys.has(key)) {\n rscCache.emittedKeys.add(key);\n parts.push(formatFontFaceRule(family, desc));\n }\n }\n }\n }\n }\n\n if (hasLocalCounterStyle(styles)) {\n const localCounterStyle = extractLocalCounterStyle(styles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n const key = `__cs:${name}`;\n if (!rscCache.emittedKeys.has(key)) {\n rscCache.emittedKeys.add(key);\n parts.push(formatCounterStyleRule(name, descriptors));\n }\n }\n }\n }\n\n return parts.join('\\n');\n}\n\n/**\n * Process all chunks in RSC mode: render CSS to strings, allocate classNames,\n * and return combined { className, css }.\n */\nfunction computeStylesRSC(\n styles: Styles,\n chunkMap: Map<string, string[]>,\n): ComputeStylesResult {\n const rscCache = getRSCCache();\n const cssParts: string[] = [];\n const classNames: string[] = [];\n\n const internalsCSS = collectInternalsRSC(rscCache);\n if (internalsCSS) cssParts.push(internalsCSS);\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n if (chunkStyleKeys.length === 0) continue;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, chunkStyleKeys);\n const { className, isNew } = rscAllocateClassName(rscCache, cacheKey);\n classNames.push(className);\n\n if (isNew) {\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n chunkStyleKeys,\n );\n if (renderResult.rules.length > 0) {\n const css = formatRules(renderResult.rules, className);\n if (css) cssParts.push(css);\n }\n }\n }\n\n const ancillaryCSS = collectAncillaryRSC(rscCache, styles);\n if (ancillaryCSS) cssParts.push(ancillaryCSS);\n\n if (classNames.length === 0) return EMPTY_RESULT;\n\n const css = cssParts.join('\\n');\n\n return {\n className: classNames.join(' '),\n css: css || undefined,\n };\n}\n\n/**\n * Get keyframes that are actually used in styles.\n * Returns null if no keyframes are used (fast path for zero overhead).\n */\nfunction getUsedKeyframes(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n const hasLocal = hasLocalKeyframes(styles);\n const hasGlobal = hasGlobalKeyframes();\n if (!hasLocal && !hasGlobal) return null;\n\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return null;\n\n const local = hasLocal ? extractLocalKeyframes(styles) : null;\n const global = hasGlobal ? getGlobalKeyframes() : null;\n const allKeyframes = mergeKeyframes(local, global);\n\n return filterUsedKeyframes(allKeyframes, usedNames);\n}\n\n/**\n * Process a chunk on the SSR path: allocate via collector, render, collect CSS.\n */\nfunction processChunkSSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const { className, isNewAllocation } = collector.allocateClassName(cacheKey);\n\n if (isNewAllocation) {\n const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);\n if (renderResult.rules.length > 0) {\n collector.collectChunk(cacheKey, className, renderResult.rules);\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n }\n return null;\n }\n\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult: { rules: [] },\n className,\n };\n}\n\n/**\n * Process a chunk on the client: render, allocate className, and inject\n * CSS synchronously. The injector's cache makes this idempotent.\n */\nfunction processChunkSync(\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n styleKeys,\n cacheKey,\n );\n if (renderResult.rules.length === 0) return null;\n\n const { className } = inject(renderResult.rules, { cacheKey });\n\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n}\n\n/**\n * Inject keyframes synchronously and return a name replacement map.\n * On the client, keyframes are injected into the DOM.\n */\nfunction injectKeyframesSync(\n usedKeyframes: Record<string, KeyframesSteps>,\n): Map<string, string> | null {\n let nameMap: Map<string, string> | null = null;\n\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const result = keyframes(steps, { name });\n const injectedName = result.toString();\n if (injectedName !== name) {\n if (!nameMap) nameMap = new Map();\n nameMap.set(name, injectedName);\n }\n }\n\n return nameMap;\n}\n\n/**\n * Inject chunk rules synchronously, replacing animation names if needed.\n */\nfunction injectChunkRulesSync(\n chunks: ProcessedChunk[],\n nameMap: Map<string, string> | null,\n): void {\n for (const chunk of chunks) {\n if (chunk.renderResult.rules.length > 0) {\n const rulesToInject: StyleResult[] = nameMap\n ? chunk.renderResult.rules.map((rule) => ({\n ...rule,\n declarations: replaceAnimationNames(rule.declarations, nameMap!),\n }))\n : chunk.renderResult.rules;\n\n inject(rulesToInject, { cacheKey: chunk.cacheKey });\n }\n }\n}\n\n/**\n * Inject all ancillary rules (properties, font-faces, counter-styles) synchronously.\n */\nfunction injectAncillarySync(styles: Styles): void {\n if (hasLocalProperties(styles)) {\n const localProperties = extractLocalProperties(styles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n property(token, definition);\n }\n }\n }\n\n if (hasLocalFontFace(styles)) {\n const localFontFace = extractLocalFontFace(styles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n fontFace(family, desc);\n }\n }\n }\n }\n\n if (hasLocalCounterStyle(styles)) {\n const localCounterStyle = extractLocalCounterStyle(styles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n counterStyle(name, descriptors);\n }\n }\n }\n}\n\n/**\n * Collect all ancillary rules into the SSR collector.\n */\nfunction collectAncillarySSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunks: ProcessedChunk[],\n): void {\n const usedKf = getUsedKeyframes(styles);\n if (usedKf) {\n for (const [name, steps] of Object.entries(usedKf)) {\n const css = formatKeyframesCSS(name, steps);\n collector.collectKeyframes(name, css);\n }\n }\n\n if (hasLocalProperties(styles)) {\n const localProperties = extractLocalProperties(styles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n collector.collectProperty(token, css);\n }\n }\n }\n }\n\n if (hasLocalFontFace(styles)) {\n const localFontFace = extractLocalFontFace(styles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n collector.collectFontFace(hash, css);\n }\n }\n }\n }\n\n if (hasLocalCounterStyle(styles)) {\n const localCounterStyle = extractLocalCounterStyle(styles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n const css = formatCounterStyleRule(name, descriptors);\n collector.collectCounterStyle(name, css);\n }\n }\n }\n\n if (getConfig().autoPropertyTypes !== false) {\n const allRules = chunks.flatMap((c) => c.renderResult.rules);\n if (allRules.length > 0) {\n collectAutoInferredProperties(allRules, collector, styles);\n }\n }\n}\n\n/**\n * Synchronous, hook-free style computation.\n *\n * Resolves recipes, categorizes style keys into chunks, renders CSS rules,\n * allocates class names, and injects / collects / returns the CSS.\n *\n * Three code paths:\n * 1. SSR collector — discovered via ALS or passed explicitly; CSS collected\n * 2. RSC inline — no collector and no `document`; CSS returned as `result.css`\n * for the caller to emit as an inline `<style>` tag\n * 3. Client inject — CSS injected synchronously into the DOM (idempotent)\n *\n * @param styles - Tasty styles object (or undefined for no styles)\n * @param options - Optional SSR collector override\n */\nexport function computeStyles(\n styles: Styles | undefined,\n options?: ComputeStylesOptions,\n): ComputeStylesResult {\n if (!styles || !hasKeys(styles as Record<string, unknown>)) {\n return EMPTY_RESULT;\n }\n\n const resolved = resolveRecipes(styles);\n const chunkMap = categorizeStyleKeys(resolved as Record<string, unknown>);\n\n const collector =\n options?.ssrCollector !== undefined\n ? options.ssrCollector\n : getRegisteredSSRCollector();\n\n const chunks: ProcessedChunk[] = [];\n\n if (collector) {\n collector.collectInternals();\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSSR(\n collector,\n resolved,\n chunkName,\n chunkStyleKeys,\n );\n if (chunk) chunks.push(chunk);\n }\n\n collectAncillarySSR(collector, resolved, chunks);\n } else if (typeof document === 'undefined') {\n // RSC path: render CSS to strings for inline <style> emission\n return computeStylesRSC(resolved, chunkMap);\n } else {\n injectAncillarySync(resolved);\n\n const usedKf = getUsedKeyframes(resolved);\n const nameMap = usedKf ? injectKeyframesSync(usedKf) : null;\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSync(resolved, chunkName, chunkStyleKeys);\n if (chunk) chunks.push(chunk);\n }\n\n if (nameMap) {\n injectChunkRulesSync(chunks, nameMap);\n }\n\n for (const chunk of chunks) {\n touch(chunk.className);\n }\n }\n\n if (chunks.length === 0) return EMPTY_RESULT;\n if (chunks.length === 1) return { className: chunks[0].className };\n\n return { className: chunks.map((c) => c.className).join(' ') };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,MAAM,eAAoC,EAAE,WAAW,IAAI;;;;;;AAkB3D,MAAM,cAAc,aACI;CACpB,qCAAqB,IAAI,KAAK;CAC9B,cAAc;CACd,6BAAa,IAAI,KAAK;CACtB,kBAAkB;CACnB,EACF;AAED,SAAS,qBACP,UACA,UACuC;CACvC,MAAM,WAAW,SAAS,oBAAoB,IAAI,SAAS;AAC3D,KAAI,SAAU,QAAO;EAAE,WAAW;EAAU,OAAO;EAAO;CAG1D,MAAM,YAAY,IAAI,SAAS;AAC/B,UAAS,oBAAoB,IAAI,UAAU,UAAU;AACrD,QAAO;EAAE;EAAW,OAAO;EAAM;;;;;;AAOnC,SAAS,oBAAoB,UAAiC;AAC5D,KAAI,SAAS,iBAAkB,QAAO;AACtC,UAAS,mBAAmB;CAE5B,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,CAAC,OAAO,eAAe,OAAO,QAAQ,wBAAwB,CAAC,EAAE;EAC1E,MAAM,MAAM,kBAAkB,OAAO,WAAW;AAChD,MAAI,IAAK,OAAM,KAAK,IAAI;;CAG1B,MAAM,cAAc,uBAAuB;AAC3C,KAAI,eAAe,OAAO,KAAK,YAAY,CAAC,SAAS,GAAG;EACtD,MAAM,aAAa,aAAa,aAAa,QAAQ;AACrD,MAAI,WAAW,SAAS,GAAG;GACzB,MAAM,MAAM,kBAAkB,WAAW;AACzC,OAAI,IAAK,OAAM,KAAK,IAAI;;;CAI5B,MAAM,WAAW,mBAAmB;AACpC,KAAI,SACF,MAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,SAAS,EAAE;EACtD,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AACX,OAAK,MAAM,QAAQ,YACjB,OAAM,KAAK,mBAAmB,QAAQ,KAAK,CAAC;;CAKlD,MAAM,WAAW,uBAAuB;AACxC,KAAI,SACF,MAAK,MAAM,CAAC,MAAM,gBAAgB,OAAO,QAAQ,SAAS,CACxD,OAAM,KAAK,uBAAuB,MAAM,YAAY,CAAC;AAIzD,QAAO,MAAM,KAAK,KAAK;;;;;;AAOzB,SAAS,oBAAoB,UAAyB,QAAwB;CAC5E,MAAM,QAAkB,EAAE;CAE1B,MAAM,SAAS,iBAAiB,OAAO;AACvC,KAAI,OACF,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;EAClD,MAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,SAAS,YAAY,IAAI,IAAI,EAAE;AAClC,YAAS,YAAY,IAAI,IAAI;AAC7B,SAAM,KAAK,mBAAmB,MAAM,MAAM,CAAC;;;AAKjD,KAAI,mBAAmB,OAAO,EAAE;EAC9B,MAAM,kBAAkB,uBAAuB,OAAO;AACtD,MAAI,gBACF,MAAK,MAAM,CAAC,OAAO,eAAe,OAAO,QAAQ,gBAAgB,EAAE;GACjE,MAAM,MAAM,UAAU;AACtB,OAAI,CAAC,SAAS,YAAY,IAAI,IAAI,EAAE;AAClC,aAAS,YAAY,IAAI,IAAI;IAC7B,MAAM,MAAM,kBAAkB,OAAO,WAAW;AAChD,QAAI,IAAK,OAAM,KAAK,IAAI;;;;AAMhC,KAAI,iBAAiB,OAAO,EAAE;EAC5B,MAAM,gBAAgB,qBAAqB,OAAO;AAClD,MAAI,cACF,MAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,cAAc,EAAE;GAC3D,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AACX,QAAK,MAAM,QAAQ,aAAa;IAE9B,MAAM,MAAM,QADC,oBAAoB,QAAQ,KAAK;AAE9C,QAAI,CAAC,SAAS,YAAY,IAAI,IAAI,EAAE;AAClC,cAAS,YAAY,IAAI,IAAI;AAC7B,WAAM,KAAK,mBAAmB,QAAQ,KAAK,CAAC;;;;;AAOtD,KAAI,qBAAqB,OAAO,EAAE;EAChC,MAAM,oBAAoB,yBAAyB,OAAO;AAC1D,MAAI,kBACF,MAAK,MAAM,CAAC,MAAM,gBAAgB,OAAO,QAAQ,kBAAkB,EAAE;GACnE,MAAM,MAAM,QAAQ;AACpB,OAAI,CAAC,SAAS,YAAY,IAAI,IAAI,EAAE;AAClC,aAAS,YAAY,IAAI,IAAI;AAC7B,UAAM,KAAK,uBAAuB,MAAM,YAAY,CAAC;;;;AAM7D,QAAO,MAAM,KAAK,KAAK;;;;;;AAOzB,SAAS,iBACP,QACA,UACqB;CACrB,MAAM,WAAW,aAAa;CAC9B,MAAM,WAAqB,EAAE;CAC7B,MAAM,aAAuB,EAAE;CAE/B,MAAM,eAAe,oBAAoB,SAAS;AAClD,KAAI,aAAc,UAAS,KAAK,aAAa;AAE7C,MAAK,MAAM,CAAC,WAAW,mBAAmB,UAAU;AAClD,MAAI,eAAe,WAAW,EAAG;EAGjC,MAAM,EAAE,WAAW,UAAU,qBAAqB,UADjC,sBAAsB,QAAQ,WAAW,eAAe,CACJ;AACrE,aAAW,KAAK,UAAU;AAE1B,MAAI,OAAO;GACT,MAAM,eAAe,qBACnB,QACA,WACA,eACD;AACD,OAAI,aAAa,MAAM,SAAS,GAAG;IACjC,MAAM,MAAM,YAAY,aAAa,OAAO,UAAU;AACtD,QAAI,IAAK,UAAS,KAAK,IAAI;;;;CAKjC,MAAM,eAAe,oBAAoB,UAAU,OAAO;AAC1D,KAAI,aAAc,UAAS,KAAK,aAAa;AAE7C,KAAI,WAAW,WAAW,EAAG,QAAO;CAEpC,MAAM,MAAM,SAAS,KAAK,KAAK;AAE/B,QAAO;EACL,WAAW,WAAW,KAAK,IAAI;EAC/B,KAAK,OAAO,KAAA;EACb;;;;;;AAOH,SAAS,iBACP,QACuC;CACvC,MAAM,WAAW,kBAAkB,OAAO;CAC1C,MAAM,YAAY,oBAAoB;AACtC,KAAI,CAAC,YAAY,CAAC,UAAW,QAAO;CAEpC,MAAM,YAAY,gCAAgC,OAAO;AACzD,KAAI,UAAU,SAAS,EAAG,QAAO;AAMjC,QAAO,oBAFc,eAFP,WAAW,sBAAsB,OAAO,GAAG,MAC1C,YAAY,oBAAoB,GAAG,KACA,EAET,UAAU;;;;;AAMrD,SAAS,gBACP,WACA,QACA,WACA,WACuB;AACvB,KAAI,UAAU,WAAW,EAAG,QAAO;CAEnC,MAAM,WAAW,sBAAsB,QAAQ,WAAW,UAAU;CACpE,MAAM,EAAE,WAAW,oBAAoB,UAAU,kBAAkB,SAAS;AAE5E,KAAI,iBAAiB;EACnB,MAAM,eAAe,qBAAqB,QAAQ,WAAW,UAAU;AACvE,MAAI,aAAa,MAAM,SAAS,GAAG;AACjC,aAAU,aAAa,UAAU,WAAW,aAAa,MAAM;AAC/D,UAAO;IAAE,MAAM;IAAW;IAAW;IAAU;IAAc;IAAW;;AAE1E,SAAO;;AAGT,QAAO;EACL,MAAM;EACN;EACA;EACA,cAAc,EAAE,OAAO,EAAE,EAAE;EAC3B;EACD;;;;;;AAOH,SAAS,iBACP,QACA,WACA,WACuB;AACvB,KAAI,UAAU,WAAW,EAAG,QAAO;CAEnC,MAAM,WAAW,sBAAsB,QAAQ,WAAW,UAAU;CACpE,MAAM,eAAe,qBACnB,QACA,WACA,WACA,SACD;AACD,KAAI,aAAa,MAAM,WAAW,EAAG,QAAO;CAE5C,MAAM,EAAE,cAAc,OAAO,aAAa,OAAO,EAAE,UAAU,CAAC;AAE9D,QAAO;EAAE,MAAM;EAAW;EAAW;EAAU;EAAc;EAAW;;;;;;AAO1E,SAAS,oBACP,eAC4B;CAC5B,IAAI,UAAsC;AAE1C,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,cAAc,EAAE;EAEzD,MAAM,eADS,UAAU,OAAO,EAAE,MAAM,CAAC,CACb,UAAU;AACtC,MAAI,iBAAiB,MAAM;AACzB,OAAI,CAAC,QAAS,2BAAU,IAAI,KAAK;AACjC,WAAQ,IAAI,MAAM,aAAa;;;AAInC,QAAO;;;;;AAMT,SAAS,qBACP,QACA,SACM;AACN,MAAK,MAAM,SAAS,OAClB,KAAI,MAAM,aAAa,MAAM,SAAS,EAQpC,QAPqC,UACjC,MAAM,aAAa,MAAM,KAAK,UAAU;EACtC,GAAG;EACH,cAAc,sBAAsB,KAAK,cAAc,QAAS;EACjE,EAAE,GACH,MAAM,aAAa,OAED,EAAE,UAAU,MAAM,UAAU,CAAC;;;;;AAQzD,SAAS,oBAAoB,QAAsB;AACjD,KAAI,mBAAmB,OAAO,EAAE;EAC9B,MAAM,kBAAkB,uBAAuB,OAAO;AACtD,MAAI,gBACF,MAAK,MAAM,CAAC,OAAO,eAAe,OAAO,QAAQ,gBAAgB,CAC/D,UAAS,OAAO,WAAW;;AAKjC,KAAI,iBAAiB,OAAO,EAAE;EAC5B,MAAM,gBAAgB,qBAAqB,OAAO;AAClD,MAAI,cACF,MAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,cAAc,EAAE;GAC3D,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AACX,QAAK,MAAM,QAAQ,YACjB,UAAS,QAAQ,KAAK;;;AAM9B,KAAI,qBAAqB,OAAO,EAAE;EAChC,MAAM,oBAAoB,yBAAyB,OAAO;AAC1D,MAAI,kBACF,MAAK,MAAM,CAAC,MAAM,gBAAgB,OAAO,QAAQ,kBAAkB,CACjE,cAAa,MAAM,YAAY;;;;;;AASvC,SAAS,oBACP,WACA,QACA,QACM;CACN,MAAM,SAAS,iBAAiB,OAAO;AACvC,KAAI,OACF,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;EAClD,MAAM,MAAM,mBAAmB,MAAM,MAAM;AAC3C,YAAU,iBAAiB,MAAM,IAAI;;AAIzC,KAAI,mBAAmB,OAAO,EAAE;EAC9B,MAAM,kBAAkB,uBAAuB,OAAO;AACtD,MAAI,gBACF,MAAK,MAAM,CAAC,OAAO,eAAe,OAAO,QAAQ,gBAAgB,EAAE;GACjE,MAAM,MAAM,kBAAkB,OAAO,WAAW;AAChD,OAAI,IACF,WAAU,gBAAgB,OAAO,IAAI;;;AAM7C,KAAI,iBAAiB,OAAO,EAAE;EAC5B,MAAM,gBAAgB,qBAAqB,OAAO;AAClD,MAAI,cACF,MAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,cAAc,EAAE;GAC3D,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AACX,QAAK,MAAM,QAAQ,aAAa;IAC9B,MAAM,OAAO,oBAAoB,QAAQ,KAAK;IAC9C,MAAM,MAAM,mBAAmB,QAAQ,KAAK;AAC5C,cAAU,gBAAgB,MAAM,IAAI;;;;AAM5C,KAAI,qBAAqB,OAAO,EAAE;EAChC,MAAM,oBAAoB,yBAAyB,OAAO;AAC1D,MAAI,kBACF,MAAK,MAAM,CAAC,MAAM,gBAAgB,OAAO,QAAQ,kBAAkB,EAAE;GACnE,MAAM,MAAM,uBAAuB,MAAM,YAAY;AACrD,aAAU,oBAAoB,MAAM,IAAI;;;AAK9C,KAAI,WAAW,CAAC,sBAAsB,OAAO;EAC3C,MAAM,WAAW,OAAO,SAAS,MAAM,EAAE,aAAa,MAAM;AAC5D,MAAI,SAAS,SAAS,EACpB,+BAA8B,UAAU,WAAW,OAAO;;;;;;;;;;;;;;;;;;AAoBhE,SAAgB,cACd,QACA,SACqB;AACrB,KAAI,CAAC,UAAU,CAAC,QAAQ,OAAkC,CACxD,QAAO;CAGT,MAAM,WAAW,eAAe,OAAO;CACvC,MAAM,WAAW,oBAAoB,SAAoC;CAEzE,MAAM,YACJ,SAAS,iBAAiB,KAAA,IACtB,QAAQ,eACR,2BAA2B;CAEjC,MAAM,SAA2B,EAAE;AAEnC,KAAI,WAAW;AACb,YAAU,kBAAkB;AAE5B,OAAK,MAAM,CAAC,WAAW,mBAAmB,UAAU;GAClD,MAAM,QAAQ,gBACZ,WACA,UACA,WACA,eACD;AACD,OAAI,MAAO,QAAO,KAAK,MAAM;;AAG/B,sBAAoB,WAAW,UAAU,OAAO;YACvC,OAAO,aAAa,YAE7B,QAAO,iBAAiB,UAAU,SAAS;MACtC;AACL,sBAAoB,SAAS;EAE7B,MAAM,SAAS,iBAAiB,SAAS;EACzC,MAAM,UAAU,SAAS,oBAAoB,OAAO,GAAG;AAEvD,OAAK,MAAM,CAAC,WAAW,mBAAmB,UAAU;GAClD,MAAM,QAAQ,iBAAiB,UAAU,WAAW,eAAe;AACnE,OAAI,MAAO,QAAO,KAAK,MAAM;;AAG/B,MAAI,QACF,sBAAqB,QAAQ,QAAQ;AAGvC,OAAK,MAAM,SAAS,OAClB,OAAM,MAAM,UAAU;;AAI1B,KAAI,OAAO,WAAW,EAAG,QAAO;AAChC,KAAI,OAAO,WAAW,EAAG,QAAO,EAAE,WAAW,OAAO,GAAG,WAAW;AAElE,QAAO,EAAE,WAAW,OAAO,KAAK,MAAM,EAAE,UAAU,CAAC,KAAK,IAAI,EAAE"}
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CounterStyleDescriptors, FontFaceInput, KeyframesSteps, PropertyDefinition } from "./injector/types.js";
1
+ import { CounterStyleDescriptors, FontFaceInput, GCConfig, KeyframesSteps, PropertyDefinition } from "./injector/types.js";
2
2
  import { StyleDetails, UnitHandler } from "./parser/types.js";
3
3
  import { StyleHandlerDefinition } from "./utils/styles.js";
4
4
  import { ConfigTokens, RecipeStyles } from "./styles/types.js";
@@ -15,27 +15,10 @@ interface TastyConfig {
15
15
  nonce?: string;
16
16
  /** Maximum rules per stylesheet (default: 8192) */
17
17
  maxRulesPerSheet?: number;
18
- /** Threshold for bulk cleanup of unused styles (default: 500) */
19
- unusedStylesThreshold?: number;
20
- /** Delay before bulk cleanup in ms, ignored if idleCleanup is true (default: 5000) */
21
- bulkCleanupDelay?: number;
22
- /** Use requestIdleCallback for cleanup when available (default: true) */
23
- idleCleanup?: boolean;
24
18
  /** Force text injection mode, auto-detected in test environments (default: auto) */
25
19
  forceTextInjection?: boolean;
26
20
  /** Enable development mode features: performance metrics and debug info (default: auto) */
27
21
  devMode?: boolean;
28
- /**
29
- * Ratio of unused styles to delete per bulk cleanup run (0..1).
30
- * Defaults to 0.5 (oldest half) to reduce risk of removing styles
31
- * that may be restored shortly after being marked unused.
32
- */
33
- bulkCleanupBatchRatio?: number;
34
- /**
35
- * Minimum age (in ms) a style must remain unused before eligible for deletion.
36
- * Helps avoid races during rapid mount/unmount cycles. Default: 10000ms.
37
- */
38
- unusedStylesMinAgeMs?: number;
39
22
  /**
40
23
  * Global predefined states for advanced state mapping.
41
24
  * These are state aliases that can be used in any component.
@@ -78,6 +61,18 @@ interface TastyConfig {
78
61
  * @default true
79
62
  */
80
63
  autoPropertyTypes?: boolean;
64
+ /**
65
+ * Garbage collection configuration for unused styles.
66
+ * GC is triggered by touch count: every `touchInterval` touches, the
67
+ * oldest unused styles are evicted when their count exceeds `capacity`.
68
+ * @example
69
+ * ```ts
70
+ * configure({
71
+ * gc: { touchInterval: 1000, capacity: 1000 },
72
+ * });
73
+ * ```
74
+ */
75
+ gc?: GCConfig;
81
76
  /**
82
77
  * Plugins that extend tasty with custom functions, units, or states.
83
78
  * Plugins are processed in order, with later plugins overriding earlier ones.
@@ -109,7 +104,11 @@ interface TastyConfig {
109
104
  /**
110
105
  * Global CSS @property definitions for custom properties.
111
106
  * Keys use tasty token syntax ($name for properties, #name for colors).
112
- * Properties are only injected when the component using them is rendered.
107
+ *
108
+ * Tasty ships with `DEFAULT_PROPERTIES` (e.g. `$gap`, `$radius`, `#white`,
109
+ * `#black`, `#clear`, `#border`, etc.) that are always included.
110
+ * Properties you specify here are merged on top, so you can override any
111
+ * default by using the same key.
113
112
  *
114
113
  * For color tokens (#name), `syntax: '<color>'` is auto-set and
115
114
  * `initialValue` defaults to `'transparent'` if not specified.
@@ -121,6 +120,8 @@ interface TastyConfig {
121
120
  * '$rotation': { syntax: '<angle>', initialValue: '0deg' },
122
121
  * '$scale': { syntax: '<number>', inherits: false, initialValue: 1 },
123
122
  * '#accent': { initialValue: 'purple' }, // syntax: '<color>' auto-set
123
+ * // Override a default property:
124
+ * '$gap': { syntax: '<length>', inherits: true, initialValue: '8px' },
124
125
  * },
125
126
  * });
126
127
  *
package/dist/config.js CHANGED
@@ -38,14 +38,14 @@ let globalProperties = null;
38
38
  let globalRecipes = null;
39
39
  let globalConfigTokens = null;
40
40
  /**
41
- * Internal properties required by tasty core features.
42
- * These are always injected when styles are first generated.
41
+ * Default properties shipped with tasty.
42
+ * These are always included unless explicitly overridden via `configure({ properties })`.
43
43
  * Keys use tasty token syntax (#name for colors, $name for other properties).
44
44
  *
45
45
  * For properties with CSS @property-compatible types (length, time, number, color),
46
46
  * an `initialValue` is provided so the property works even without a project-level token.
47
47
  */
48
- const INTERNAL_PROPERTIES = {
48
+ const DEFAULT_PROPERTIES = {
49
49
  "#tasty-second-fill": {
50
50
  inherits: false,
51
51
  initialValue: "transparent"
@@ -62,6 +62,14 @@ const INTERNAL_PROPERTIES = {
62
62
  inherits: true,
63
63
  initialValue: "rgb(0 0 0)"
64
64
  },
65
+ "#clear": {
66
+ inherits: true,
67
+ initialValue: "transparent"
68
+ },
69
+ "#border": {
70
+ inherits: true,
71
+ initialValue: "rgb(0 0 0)"
72
+ },
65
73
  $gap: {
66
74
  syntax: "<length>",
67
75
  inherits: true,
@@ -130,13 +138,8 @@ function isTestEnvironment() {
130
138
  function createDefaultConfig(isTest) {
131
139
  return {
132
140
  maxRulesPerSheet: 8192,
133
- unusedStylesThreshold: 500,
134
- bulkCleanupDelay: 5e3,
135
- idleCleanup: true,
136
141
  forceTextInjection: isTest ?? false,
137
- devMode: isDevEnv(),
138
- bulkCleanupBatchRatio: .5,
139
- unusedStylesMinAgeMs: 1e4
142
+ devMode: isDevEnv()
140
143
  };
141
144
  }
142
145
  /**
@@ -148,8 +151,7 @@ function markStylesGenerated() {
148
151
  if (stylesGenerated) return;
149
152
  stylesGenerated = true;
150
153
  const injector = getGlobalInjector();
151
- for (const [token, definition] of Object.entries(INTERNAL_PROPERTIES)) injector.property(token, definition);
152
- if (globalProperties && Object.keys(globalProperties).length > 0) for (const [token, definition] of Object.entries(globalProperties)) injector.property(token, definition);
154
+ for (const [token, definition] of Object.entries(getEffectiveProperties())) injector.property(token, definition);
153
155
  if (globalFontFace && Object.keys(globalFontFace).length > 0) for (const [family, input] of Object.entries(globalFontFace)) {
154
156
  const descriptors = Array.isArray(input) ? input : [input];
155
157
  for (const desc of descriptors) injector.fontFace(family, desc);
@@ -194,20 +196,6 @@ function setGlobalKeyframes(keyframes) {
194
196
  _hasGlobalKeyframes = Object.keys(keyframes).length > 0;
195
197
  }
196
198
  /**
197
- * Check if any global properties are configured.
198
- * Fast path: returns false if no properties were ever set.
199
- */
200
- function hasGlobalProperties() {
201
- return globalProperties !== null && Object.keys(globalProperties).length > 0;
202
- }
203
- /**
204
- * Get global properties configuration.
205
- * Returns null if no properties configured (fast path for zero-overhead).
206
- */
207
- function getGlobalProperties() {
208
- return globalProperties;
209
- }
210
- /**
211
199
  * Set global properties (called from configure).
212
200
  * Internal use only.
213
201
  */
@@ -219,6 +207,17 @@ function setGlobalProperties(properties) {
219
207
  globalProperties = properties;
220
208
  }
221
209
  /**
210
+ * Get the effective properties: DEFAULT_PROPERTIES merged with user-configured
211
+ * properties. User properties override defaults with matching keys.
212
+ */
213
+ function getEffectiveProperties() {
214
+ if (!globalProperties) return DEFAULT_PROPERTIES;
215
+ return {
216
+ ...DEFAULT_PROPERTIES,
217
+ ...globalProperties
218
+ };
219
+ }
220
+ /**
222
221
  * Get global font-face configuration.
223
222
  * Returns null if no font faces configured.
224
223
  */
@@ -498,6 +497,6 @@ function resetConfig() {
498
497
  delete storage[GLOBAL_INJECTOR_KEY];
499
498
  }
500
499
  //#endregion
501
- export { INTERNAL_PROPERTIES, configure, getConfig, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace, getGlobalInjector, getGlobalKeyframes, getGlobalProperties, getGlobalRecipes, hasGlobalKeyframes, hasGlobalProperties, hasGlobalRecipes, hasStylesGenerated, isConfigLocked, isTestEnvironment, markStylesGenerated, resetConfig };
500
+ export { configure, getConfig, getEffectiveProperties, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace, getGlobalInjector, getGlobalKeyframes, getGlobalRecipes, hasGlobalKeyframes, hasGlobalRecipes, hasStylesGenerated, isConfigLocked, isTestEnvironment, markStylesGenerated, resetConfig };
502
501
 
503
502
  //# sourceMappingURL=config.js.map