@tenphi/tasty 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/compute-styles.d.ts +31 -0
  2. package/dist/compute-styles.js +357 -0
  3. package/dist/compute-styles.js.map +1 -0
  4. package/dist/config.d.ts +19 -19
  5. package/dist/config.js +25 -26
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/index.d.ts +5 -4
  8. package/dist/core/index.js +6 -5
  9. package/dist/hooks/useCounterStyle.js +3 -4
  10. package/dist/hooks/useCounterStyle.js.map +1 -1
  11. package/dist/hooks/useFontFace.js +3 -4
  12. package/dist/hooks/useFontFace.js.map +1 -1
  13. package/dist/hooks/useGlobalStyles.js +4 -5
  14. package/dist/hooks/useGlobalStyles.js.map +1 -1
  15. package/dist/hooks/useKeyframes.js +3 -4
  16. package/dist/hooks/useKeyframes.js.map +1 -1
  17. package/dist/hooks/useProperty.js +3 -4
  18. package/dist/hooks/useProperty.js.map +1 -1
  19. package/dist/hooks/useRawCSS.js +3 -4
  20. package/dist/hooks/useRawCSS.js.map +1 -1
  21. package/dist/hooks/useStyles.d.ts +4 -9
  22. package/dist/hooks/useStyles.js +6 -214
  23. package/dist/hooks/useStyles.js.map +1 -1
  24. package/dist/index.d.ts +6 -5
  25. package/dist/index.js +7 -6
  26. package/dist/injector/index.d.ts +23 -19
  27. package/dist/injector/index.js +29 -16
  28. package/dist/injector/index.js.map +1 -1
  29. package/dist/injector/injector.d.ts +32 -3
  30. package/dist/injector/injector.js +130 -7
  31. package/dist/injector/injector.js.map +1 -1
  32. package/dist/injector/sheet-manager.d.ts +9 -13
  33. package/dist/injector/sheet-manager.js +30 -66
  34. package/dist/injector/sheet-manager.js.map +1 -1
  35. package/dist/injector/types.d.ts +50 -19
  36. package/dist/ssr/collector.js +3 -10
  37. package/dist/ssr/collector.js.map +1 -1
  38. package/dist/ssr/index.d.ts +1 -2
  39. package/dist/ssr/index.js +1 -2
  40. package/dist/ssr/index.js.map +1 -1
  41. package/dist/ssr/next.d.ts +1 -3
  42. package/dist/ssr/next.js +8 -3
  43. package/dist/ssr/next.js.map +1 -1
  44. package/dist/tasty.d.ts +28 -13
  45. package/dist/tasty.js +72 -60
  46. package/dist/tasty.js.map +1 -1
  47. package/dist/utils/process-tokens.d.ts +1 -5
  48. package/dist/utils/process-tokens.js +1 -8
  49. package/dist/utils/process-tokens.js.map +1 -1
  50. package/docs/injector.md +33 -18
  51. package/docs/methodology.md +50 -1
  52. package/docs/runtime.md +90 -3
  53. package/docs/ssr.md +19 -49
  54. package/package.json +4 -4
  55. package/dist/hooks/resolve-ssr-collector.js +0 -14
  56. package/dist/hooks/resolve-ssr-collector.js.map +0 -1
  57. package/dist/ssr/context.d.ts +0 -8
  58. package/dist/ssr/context.js +0 -13
  59. package/dist/ssr/context.js.map +0 -1
@@ -0,0 +1,31 @@
1
+ import { Styles } from "./styles/types.js";
2
+ import { ServerStyleCollector } from "./ssr/collector.js";
3
+
4
+ //#region src/compute-styles.d.ts
5
+ interface ComputeStylesResult {
6
+ className: string;
7
+ /** CSS text to emit as an inline <style> tag (RSC mode only). */
8
+ css?: string;
9
+ }
10
+ interface ComputeStylesOptions {
11
+ ssrCollector?: ServerStyleCollector | null;
12
+ }
13
+ /**
14
+ * Synchronous, hook-free style computation.
15
+ *
16
+ * Resolves recipes, categorizes style keys into chunks, renders CSS rules,
17
+ * allocates class names, and injects / collects / returns the CSS.
18
+ *
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)
24
+ *
25
+ * @param styles - Tasty styles object (or undefined for no styles)
26
+ * @param options - Optional SSR collector override
27
+ */
28
+ declare function computeStyles(styles: Styles | undefined, options?: ComputeStylesOptions): ComputeStylesResult;
29
+ //#endregion
30
+ export { ComputeStylesOptions, ComputeStylesResult, computeStyles };
31
+ //# sourceMappingURL=compute-styles.d.ts.map
@@ -0,0 +1,357 @@
1
+ import { extractLocalProperties, hasLocalProperties } from "./properties/index.js";
2
+ import { extractLocalFontFace, fontFaceContentHash, formatFontFaceRule, hasLocalFontFace } from "./font-face/index.js";
3
+ import { extractLocalCounterStyle, formatCounterStyleRule, hasLocalCounterStyle } from "./counter-style/index.js";
4
+ import { renderStyles } from "./pipeline/index.js";
5
+ import { getConfig, getEffectiveProperties, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace, getGlobalKeyframes, hasGlobalKeyframes } from "./config.js";
6
+ import { categorizeStyleKeys } from "./chunks/definitions.js";
7
+ import { generateChunkCacheKey } from "./chunks/cacheKey.js";
8
+ import { renderStylesForChunk } from "./chunks/renderChunk.js";
9
+ import { counterStyle, fontFace, inject, keyframes, property, touch } from "./injector/index.js";
10
+ import { extractAnimationNamesFromStyles, extractLocalKeyframes, filterUsedKeyframes, hasLocalKeyframes, mergeKeyframes, replaceAnimationNames } from "./keyframes/index.js";
11
+ import { formatPropertyCSS } from "./ssr/format-property.js";
12
+ import { collectAutoInferredProperties } from "./ssr/collect-auto-properties.js";
13
+ import { formatGlobalRules } from "./ssr/format-global-rules.js";
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";
17
+ import { hasKeys } from "./utils/has-keys.js";
18
+ import { resolveRecipes } from "./utils/resolve-recipes.js";
19
+ import { cache } from "react";
20
+ //#region src/compute-styles.ts
21
+ /**
22
+ * Hook-free, synchronous style computation.
23
+ *
24
+ * Extracts the core logic from useStyles() into a plain function that can
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
30
+ *
31
+ * This enables tasty() components to work as React Server Components.
32
+ */
33
+ const EMPTY_RESULT = { className: "" };
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
+ /**
169
+ * Get keyframes that are actually used in styles.
170
+ * Returns null if no keyframes are used (fast path for zero overhead).
171
+ */
172
+ function getUsedKeyframes(styles) {
173
+ const hasLocal = hasLocalKeyframes(styles);
174
+ const hasGlobal = hasGlobalKeyframes();
175
+ if (!hasLocal && !hasGlobal) return null;
176
+ const usedNames = extractAnimationNamesFromStyles(styles);
177
+ if (usedNames.size === 0) return null;
178
+ return filterUsedKeyframes(mergeKeyframes(hasLocal ? extractLocalKeyframes(styles) : null, hasGlobal ? getGlobalKeyframes() : null), usedNames);
179
+ }
180
+ /**
181
+ * Process a chunk on the SSR path: allocate via collector, render, collect CSS.
182
+ */
183
+ function processChunkSSR(collector, styles, chunkName, styleKeys) {
184
+ if (styleKeys.length === 0) return null;
185
+ const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);
186
+ const { className, isNewAllocation } = collector.allocateClassName(cacheKey);
187
+ if (isNewAllocation) {
188
+ const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);
189
+ if (renderResult.rules.length > 0) {
190
+ collector.collectChunk(cacheKey, className, renderResult.rules);
191
+ return {
192
+ name: chunkName,
193
+ styleKeys,
194
+ cacheKey,
195
+ renderResult,
196
+ className
197
+ };
198
+ }
199
+ return null;
200
+ }
201
+ return {
202
+ name: chunkName,
203
+ styleKeys,
204
+ cacheKey,
205
+ renderResult: { rules: [] },
206
+ className
207
+ };
208
+ }
209
+ /**
210
+ * Process a chunk on the client: render, allocate className, and inject
211
+ * CSS synchronously. The injector's cache makes this idempotent.
212
+ */
213
+ function processChunkSync(styles, chunkName, styleKeys) {
214
+ if (styleKeys.length === 0) return null;
215
+ const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);
216
+ const renderResult = renderStylesForChunk(styles, chunkName, styleKeys, cacheKey);
217
+ if (renderResult.rules.length === 0) return null;
218
+ const { className } = inject(renderResult.rules, { cacheKey });
219
+ return {
220
+ name: chunkName,
221
+ styleKeys,
222
+ cacheKey,
223
+ renderResult,
224
+ className
225
+ };
226
+ }
227
+ /**
228
+ * Inject keyframes synchronously and return a name replacement map.
229
+ * On the client, keyframes are injected into the DOM.
230
+ */
231
+ function injectKeyframesSync(usedKeyframes) {
232
+ let nameMap = null;
233
+ for (const [name, steps] of Object.entries(usedKeyframes)) {
234
+ const injectedName = keyframes(steps, { name }).toString();
235
+ if (injectedName !== name) {
236
+ if (!nameMap) nameMap = /* @__PURE__ */ new Map();
237
+ nameMap.set(name, injectedName);
238
+ }
239
+ }
240
+ return nameMap;
241
+ }
242
+ /**
243
+ * Inject chunk rules synchronously, replacing animation names if needed.
244
+ */
245
+ function injectChunkRulesSync(chunks, nameMap) {
246
+ for (const chunk of chunks) if (chunk.renderResult.rules.length > 0) inject(nameMap ? chunk.renderResult.rules.map((rule) => ({
247
+ ...rule,
248
+ declarations: replaceAnimationNames(rule.declarations, nameMap)
249
+ })) : chunk.renderResult.rules, { cacheKey: chunk.cacheKey });
250
+ }
251
+ /**
252
+ * Inject all ancillary rules (properties, font-faces, counter-styles) synchronously.
253
+ */
254
+ function injectAncillarySync(styles) {
255
+ if (hasLocalProperties(styles)) {
256
+ const localProperties = extractLocalProperties(styles);
257
+ if (localProperties) for (const [token, definition] of Object.entries(localProperties)) property(token, definition);
258
+ }
259
+ if (hasLocalFontFace(styles)) {
260
+ const localFontFace = extractLocalFontFace(styles);
261
+ if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
262
+ const descriptors = Array.isArray(input) ? input : [input];
263
+ for (const desc of descriptors) fontFace(family, desc);
264
+ }
265
+ }
266
+ if (hasLocalCounterStyle(styles)) {
267
+ const localCounterStyle = extractLocalCounterStyle(styles);
268
+ if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) counterStyle(name, descriptors);
269
+ }
270
+ }
271
+ /**
272
+ * Collect all ancillary rules into the SSR collector.
273
+ */
274
+ function collectAncillarySSR(collector, styles, chunks) {
275
+ const usedKf = getUsedKeyframes(styles);
276
+ if (usedKf) for (const [name, steps] of Object.entries(usedKf)) {
277
+ const css = formatKeyframesCSS(name, steps);
278
+ collector.collectKeyframes(name, css);
279
+ }
280
+ if (hasLocalProperties(styles)) {
281
+ const localProperties = extractLocalProperties(styles);
282
+ if (localProperties) for (const [token, definition] of Object.entries(localProperties)) {
283
+ const css = formatPropertyCSS(token, definition);
284
+ if (css) collector.collectProperty(token, css);
285
+ }
286
+ }
287
+ if (hasLocalFontFace(styles)) {
288
+ const localFontFace = extractLocalFontFace(styles);
289
+ if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
290
+ const descriptors = Array.isArray(input) ? input : [input];
291
+ for (const desc of descriptors) {
292
+ const hash = fontFaceContentHash(family, desc);
293
+ const css = formatFontFaceRule(family, desc);
294
+ collector.collectFontFace(hash, css);
295
+ }
296
+ }
297
+ }
298
+ if (hasLocalCounterStyle(styles)) {
299
+ const localCounterStyle = extractLocalCounterStyle(styles);
300
+ if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) {
301
+ const css = formatCounterStyleRule(name, descriptors);
302
+ collector.collectCounterStyle(name, css);
303
+ }
304
+ }
305
+ if (getConfig().autoPropertyTypes !== false) {
306
+ const allRules = chunks.flatMap((c) => c.renderResult.rules);
307
+ if (allRules.length > 0) collectAutoInferredProperties(allRules, collector, styles);
308
+ }
309
+ }
310
+ /**
311
+ * Synchronous, hook-free style computation.
312
+ *
313
+ * Resolves recipes, categorizes style keys into chunks, renders CSS rules,
314
+ * allocates class names, and injects / collects / returns the CSS.
315
+ *
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)
321
+ *
322
+ * @param styles - Tasty styles object (or undefined for no styles)
323
+ * @param options - Optional SSR collector override
324
+ */
325
+ function computeStyles(styles, options) {
326
+ if (!styles || !hasKeys(styles)) return EMPTY_RESULT;
327
+ const resolved = resolveRecipes(styles);
328
+ const chunkMap = categorizeStyleKeys(resolved);
329
+ const collector = options?.ssrCollector !== void 0 ? options.ssrCollector : getRegisteredSSRCollector();
330
+ const chunks = [];
331
+ if (collector) {
332
+ collector.collectInternals();
333
+ for (const [chunkName, chunkStyleKeys] of chunkMap) {
334
+ const chunk = processChunkSSR(collector, resolved, chunkName, chunkStyleKeys);
335
+ if (chunk) chunks.push(chunk);
336
+ }
337
+ collectAncillarySSR(collector, resolved, chunks);
338
+ } else if (typeof document === "undefined") return computeStylesRSC(resolved, chunkMap);
339
+ else {
340
+ injectAncillarySync(resolved);
341
+ const usedKf = getUsedKeyframes(resolved);
342
+ const nameMap = usedKf ? injectKeyframesSync(usedKf) : null;
343
+ for (const [chunkName, chunkStyleKeys] of chunkMap) {
344
+ const chunk = processChunkSync(resolved, chunkName, chunkStyleKeys);
345
+ if (chunk) chunks.push(chunk);
346
+ }
347
+ if (nameMap) injectChunkRulesSync(chunks, nameMap);
348
+ for (const chunk of chunks) touch(chunk.className);
349
+ }
350
+ if (chunks.length === 0) return EMPTY_RESULT;
351
+ if (chunks.length === 1) return { className: chunks[0].className };
352
+ return { className: chunks.map((c) => c.className).join(" ") };
353
+ }
354
+ //#endregion
355
+ export { computeStyles };
356
+
357
+ //# sourceMappingURL=compute-styles.js.map
@@ -0,0 +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. 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":"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"}
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,17 @@ interface TastyConfig {
78
61
  * @default true
79
62
  */
80
63
  autoPropertyTypes?: boolean;
64
+ /**
65
+ * Garbage collection configuration for unused styles.
66
+ * Controls popularity-aware style eviction with DOM safety guard.
67
+ * @example
68
+ * ```ts
69
+ * configure({
70
+ * gc: { auto: true, baseMaxAge: 60000, cooldown: 30000 },
71
+ * });
72
+ * ```
73
+ */
74
+ gc?: GCConfig;
81
75
  /**
82
76
  * Plugins that extend tasty with custom functions, units, or states.
83
77
  * Plugins are processed in order, with later plugins overriding earlier ones.
@@ -109,7 +103,11 @@ interface TastyConfig {
109
103
  /**
110
104
  * Global CSS @property definitions for custom properties.
111
105
  * Keys use tasty token syntax ($name for properties, #name for colors).
112
- * Properties are only injected when the component using them is rendered.
106
+ *
107
+ * Tasty ships with `DEFAULT_PROPERTIES` (e.g. `$gap`, `$radius`, `#white`,
108
+ * `#black`, `#clear`, `#border`, etc.) that are always included.
109
+ * Properties you specify here are merged on top, so you can override any
110
+ * default by using the same key.
113
111
  *
114
112
  * For color tokens (#name), `syntax: '<color>'` is auto-set and
115
113
  * `initialValue` defaults to `'transparent'` if not specified.
@@ -121,6 +119,8 @@ interface TastyConfig {
121
119
  * '$rotation': { syntax: '<angle>', initialValue: '0deg' },
122
120
  * '$scale': { syntax: '<number>', inherits: false, initialValue: 1 },
123
121
  * '#accent': { initialValue: 'purple' }, // syntax: '<color>' auto-set
122
+ * // Override a default property:
123
+ * '$gap': { syntax: '<length>', inherits: true, initialValue: '8px' },
124
124
  * },
125
125
  * });
126
126
  *
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