@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.
- package/dist/compute-styles.d.ts +31 -0
- package/dist/compute-styles.js +357 -0
- package/dist/compute-styles.js.map +1 -0
- package/dist/config.d.ts +19 -19
- package/dist/config.js +25 -26
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +6 -5
- package/dist/hooks/useCounterStyle.js +3 -4
- package/dist/hooks/useCounterStyle.js.map +1 -1
- package/dist/hooks/useFontFace.js +3 -4
- package/dist/hooks/useFontFace.js.map +1 -1
- package/dist/hooks/useGlobalStyles.js +4 -5
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.js +3 -4
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.js +3 -4
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.js +3 -4
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/hooks/useStyles.d.ts +4 -9
- package/dist/hooks/useStyles.js +6 -214
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +7 -6
- package/dist/injector/index.d.ts +23 -19
- package/dist/injector/index.js +29 -16
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +32 -3
- package/dist/injector/injector.js +130 -7
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.d.ts +9 -13
- package/dist/injector/sheet-manager.js +30 -66
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +50 -19
- package/dist/ssr/collector.js +3 -10
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/index.d.ts +1 -2
- package/dist/ssr/index.js +1 -2
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -3
- package/dist/ssr/next.js +8 -3
- package/dist/ssr/next.js.map +1 -1
- package/dist/tasty.d.ts +28 -13
- package/dist/tasty.js +72 -60
- package/dist/tasty.js.map +1 -1
- package/dist/utils/process-tokens.d.ts +1 -5
- package/dist/utils/process-tokens.js +1 -8
- package/dist/utils/process-tokens.js.map +1 -1
- package/docs/injector.md +33 -18
- package/docs/methodology.md +50 -1
- package/docs/runtime.md +90 -3
- package/docs/ssr.md +19 -49
- package/package.json +4 -4
- package/dist/hooks/resolve-ssr-collector.js +0 -14
- package/dist/hooks/resolve-ssr-collector.js.map +0 -1
- package/dist/ssr/context.d.ts +0 -8
- package/dist/ssr/context.js +0 -13
- 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
|
-
*
|
|
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
|
-
*
|
|
42
|
-
* These are always
|
|
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
|
|
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(
|
|
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 {
|
|
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
|