@tenphi/tasty 0.0.0-snapshot.27b6708 → 0.0.0-snapshot.28eb83c
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/README.md +38 -54
- package/dist/{babel-BUQGeOXA.d.ts → babel-D6lOuSwk.d.ts} +2 -2
- package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
- package/dist/chunks/build-config-BBThSdlo.js +45 -0
- package/dist/chunks/build-config-BBThSdlo.js.map +1 -0
- package/dist/{collector-DTahQUiV.js → chunks/collector-DcViW2ZQ.js} +28 -14
- package/dist/chunks/collector-DcViW2ZQ.js.map +1 -0
- package/dist/chunks/config-engine-DQVQK4NU.js +403 -0
- package/dist/chunks/config-engine-DQVQK4NU.js.map +1 -0
- package/dist/chunks/css-definitions-BZ9vm0ci.js +1284 -0
- package/dist/chunks/css-definitions-BZ9vm0ci.js.map +1 -0
- package/dist/chunks/css-resources-Cyl_axbI.js +149 -0
- package/dist/chunks/css-resources-Cyl_axbI.js.map +1 -0
- package/dist/chunks/debug-Vyauml7U.js +583 -0
- package/dist/chunks/debug-Vyauml7U.js.map +1 -0
- package/dist/chunks/dsl-CXvoaLnj.js +2097 -0
- package/dist/chunks/dsl-CXvoaLnj.js.map +1 -0
- package/dist/chunks/hydration-CLdVKcH2.js +59 -0
- package/dist/chunks/hydration-CLdVKcH2.js.map +1 -0
- package/dist/chunks/react-runtime-so_X_yMo.js +1526 -0
- package/dist/chunks/react-runtime-so_X_yMo.js.map +1 -0
- package/dist/chunks/runtime-engine-DQ5laRou.js +3056 -0
- package/dist/chunks/runtime-engine-DQ5laRou.js.map +1 -0
- package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-OIg0N_dp.js} +42 -3
- package/dist/chunks/shared-utils-OIg0N_dp.js.map +1 -0
- package/dist/{config-B5kHzuNz.js → chunks/style-engine-CBuOt1e4.js} +2387 -7442
- package/dist/chunks/style-engine-CBuOt1e4.js.map +1 -0
- package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-BFh7gKbK.js} +3 -3
- package/dist/chunks/zero-engine-BFh7gKbK.js.map +1 -0
- package/dist/{collector-DUaHCcTS.d.ts → collector-CAmndLXe.d.ts} +23 -3
- package/dist/{config-YsxGv4tq.d.ts → config-BWYrv_Aa.d.ts} +141 -37
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/{index-Cd45t5NM.d.ts → index-BzAC3p06.d.ts} +118 -23
- package/dist/{index-PqN-DIpn.d.ts → index-D4kRLj2o.d.ts} +128 -68
- package/dist/index.d.ts +5 -5
- package/dist/index.js +10 -922
- package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BFktNP8J.d.ts} +2 -2
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract-static.js +9 -0
- package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
- package/dist/ssr/astro-middleware-extract.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract.js +9 -0
- package/dist/ssr/astro-middleware-extract.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +3 -1
- package/dist/ssr/astro-middleware.d.ts +3 -1
- package/dist/ssr/astro.d.ts +45 -3
- package/dist/ssr/astro.js +157 -14
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +7 -7
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +66 -0
- package/dist/ssr/next-config.js +135 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +9 -2
- package/dist/ssr/next.js +26 -10
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +25 -24
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/docs/README.md +5 -3
- package/docs/adoption.md +3 -3
- package/docs/ai-agents.md +83 -78
- package/docs/comparison.md +12 -12
- package/docs/configuration.md +47 -47
- package/docs/debug.md +19 -7
- package/docs/dsl.md +3 -3
- package/docs/getting-started.md +10 -10
- package/docs/injector.md +62 -25
- package/docs/methodology.md +3 -3
- package/docs/migration-v3.md +49 -49
- package/docs/plugins.md +37 -33
- package/docs/react-api.md +2 -2
- package/docs/runtime-benchmarks.md +351 -0
- package/docs/ssr.md +219 -61
- package/docs/styles.md +43 -11
- package/docs/tasty-static.md +136 -103
- package/package.json +80 -11
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/collector-DTahQUiV.js.map +0 -1
- package/dist/config-B5kHzuNz.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-Dr4u1NVD.js +0 -1566
- package/dist/core-Dr4u1NVD.js.map +0 -1
- package/dist/css-writer-B-J87ncv.js.map +0 -1
- package/dist/format-global-rules-DklyaXv-.js +0 -22
- package/dist/format-global-rules-DklyaXv-.js.map +0 -1
- package/dist/format-rules-DKOA-6qu.js +0 -130
- package/dist/format-rules-DKOA-6qu.js.map +0 -1
- package/dist/hydrate-OeMX99We.js +0 -37
- package/dist/hydrate-OeMX99We.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-CV8azJf3.js +0 -493
- package/dist/keyframes-CV8azJf3.js.map +0 -1
- package/dist/merge-styles-oklji0KB.js.map +0 -1
- package/dist/resolve-recipes-DTG81rzl.js +0 -144
- package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
- /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
import { M as normalizeDslName, d as normalizeColorTokenValue, p as parseStyle } from "./dsl-CXvoaLnj.js";
|
|
2
|
+
import { n as isSelector, t as mergeStyles } from "./shared-utils-OIg0N_dp.js";
|
|
3
|
+
import { C as hasLocalFontFace, D as makeClassName, G as registerFunctionPolyfill, H as formatFunctionRule, I as hasLocalProperties, K as registerLocalFunctionPolyfills, N as hashString, O as makeCounterStyleName, P as extractLocalProperties, R as parsePropertyToken, S as formatFontFaceRule, U as hasLocalFunctions, W as parseFunctionName, _ as formatCounterStyleRule, a as formatPropertyCSS, b as fontFaceContentHash, c as filterUsedKeyframes, d as referencesAnimation, f as replaceAnimationNames, h as extractLocalCounterStyle, i as formatKeyframesCSS, k as makeKeyframeName, l as hasLocalKeyframes, n as formatGlobalRules, o as extractAnimationNamesFromStyles, p as resolveKeyframesNames, r as formatRules, s as extractLocalKeyframes, u as mergeKeyframes, v as hasLocalCounterStyle, w as PropertyTypeResolver, y as extractLocalFontFace, z as extractLocalFunctions } from "./css-definitions-BZ9vm0ci.js";
|
|
4
|
+
import { S as camelToKebab, U as isFunctionsPolyfillEnabled, b as renderStyles, i as baseStylePropsRegistry, n as generateChunkCacheKey, r as categorizeStyleKeys, s as BASE_STYLES, t as renderStylesForChunk } from "./style-engine-CBuOt1e4.js";
|
|
5
|
+
import { D as touch, E as property, H as hasGlobalKeyframes, L as getGlobalInjector, Q as openBatchWindow, R as getGlobalKeyframes, T as ownKeyframes, V as getNamePrefix, Y as closeBatchWindow, a as getRegisteredSSRCollector, b as injectGlobal, et as propHandlerRegistry, f as fontFace, i as resolveRecipes, j as getConfig, l as counterStyle, n as createClientState, p as func, r as cacheWrapper, t as depsEqual, v as holdKeyframes, w as keyframes, x as injectRawCSS, y as inject } from "./runtime-engine-DQ5laRou.js";
|
|
6
|
+
import { Fragment, cache, createContext, createElement, forwardRef, useContext, useInsertionEffect } from "react";
|
|
7
|
+
//#region src/rsc-cache.ts
|
|
8
|
+
/**
|
|
9
|
+
* Shared RSC (React Server Components) inline style cache.
|
|
10
|
+
*
|
|
11
|
+
* Uses React.cache for per-request memoization in Server Components.
|
|
12
|
+
* Both computeStyles() and standalone style functions (useGlobalStyles,
|
|
13
|
+
* useRawCSS, useKeyframes, useProperty, useFontFace, useCounterStyle)
|
|
14
|
+
* share this cache so that CSS accumulated by standalone functions is
|
|
15
|
+
* flushed into inline <style> tags by the next tasty() component.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Per-request RSC style cache using React.cache.
|
|
19
|
+
* React.cache provides per-request memoization in Server Components,
|
|
20
|
+
* so each request gets its own isolated cache.
|
|
21
|
+
*/
|
|
22
|
+
const getRSCCache = cache(() => ({
|
|
23
|
+
cacheKeyToClassName: /* @__PURE__ */ new Map(),
|
|
24
|
+
emittedKeys: /* @__PURE__ */ new Set(),
|
|
25
|
+
internalsEmitted: false,
|
|
26
|
+
pendingCSS: [],
|
|
27
|
+
keyToIndex: /* @__PURE__ */ new Map(),
|
|
28
|
+
generatedNames: /* @__PURE__ */ new Map()
|
|
29
|
+
}));
|
|
30
|
+
function rscAllocateClassName(rscCache, cacheKey) {
|
|
31
|
+
const existing = rscCache.cacheKeyToClassName.get(cacheKey);
|
|
32
|
+
if (existing) return {
|
|
33
|
+
className: existing,
|
|
34
|
+
isNew: false
|
|
35
|
+
};
|
|
36
|
+
const className = makeClassName(getNamePrefix(), hashString(cacheKey));
|
|
37
|
+
rscCache.cacheKeyToClassName.set(cacheKey, className);
|
|
38
|
+
return {
|
|
39
|
+
className,
|
|
40
|
+
isNew: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Flush any pending CSS accumulated by standalone functions.
|
|
45
|
+
* Returns the CSS string and clears the buffer.
|
|
46
|
+
*/
|
|
47
|
+
function flushPendingCSS(rscCache) {
|
|
48
|
+
if (rscCache.pendingCSS.length === 0) return "";
|
|
49
|
+
const css = rscCache.pendingCSS.join("\n");
|
|
50
|
+
rscCache.pendingCSS.length = 0;
|
|
51
|
+
rscCache.keyToIndex.clear();
|
|
52
|
+
return css;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Push CSS into the RSC pending buffer with dedup via emittedKeys.
|
|
56
|
+
* Returns true if the CSS was added, false if it was already emitted.
|
|
57
|
+
*
|
|
58
|
+
* Pass `replace` for slot-keyed entries (an explicit `id`), where the last write
|
|
59
|
+
* must win to match the client's update-tracking behavior. If the slot has
|
|
60
|
+
* already been flushed into a `<style>` tag the new CSS is appended instead,
|
|
61
|
+
* which still wins by cascade order.
|
|
62
|
+
*/
|
|
63
|
+
function pushRSCCSS(rscCache, key, css, replace) {
|
|
64
|
+
if (replace) {
|
|
65
|
+
const pendingIndex = rscCache.keyToIndex.get(key);
|
|
66
|
+
if (pendingIndex !== void 0) {
|
|
67
|
+
rscCache.pendingCSS[pendingIndex] = css;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
} else if (rscCache.emittedKeys.has(key)) return false;
|
|
71
|
+
rscCache.emittedKeys.add(key);
|
|
72
|
+
rscCache.keyToIndex.set(key, rscCache.pendingCSS.length);
|
|
73
|
+
rscCache.pendingCSS.push(css);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Determine the current style injection target.
|
|
78
|
+
* Centralizes the three-way detection (SSR collector / RSC cache / client DOM)
|
|
79
|
+
* used by all style functions.
|
|
80
|
+
*/
|
|
81
|
+
function getStyleTarget() {
|
|
82
|
+
const collector = getRegisteredSSRCollector();
|
|
83
|
+
if (collector) return {
|
|
84
|
+
mode: "ssr",
|
|
85
|
+
collector
|
|
86
|
+
};
|
|
87
|
+
if (typeof document === "undefined") return {
|
|
88
|
+
mode: "rsc",
|
|
89
|
+
cache: getRSCCache()
|
|
90
|
+
};
|
|
91
|
+
return { mode: "client" };
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/ssr/collect-auto-properties.ts
|
|
95
|
+
/**
|
|
96
|
+
* Scan rendered rules for auto-inferable custom properties and emit
|
|
97
|
+
* @property CSS via the provided callback.
|
|
98
|
+
*/
|
|
99
|
+
function scanAndEmitAutoProperties(rules, styles, emit) {
|
|
100
|
+
const registered = /* @__PURE__ */ new Set();
|
|
101
|
+
if (styles) {
|
|
102
|
+
const localProps = styles["@property"];
|
|
103
|
+
if (localProps && typeof localProps === "object") for (const token of Object.keys(localProps)) {
|
|
104
|
+
const parsed = parsePropertyToken(token);
|
|
105
|
+
if (parsed.isValid) registered.add(parsed.cssName);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const resolver = new PropertyTypeResolver();
|
|
109
|
+
for (const rule of rules) {
|
|
110
|
+
if (!rule.declarations) continue;
|
|
111
|
+
resolver.scanDeclarations(rule.declarations, (name) => registered.has(name), (name, syntax, initialValue) => {
|
|
112
|
+
registered.add(name);
|
|
113
|
+
const css = formatPropertyCSS(name, {
|
|
114
|
+
syntax,
|
|
115
|
+
inherits: true,
|
|
116
|
+
initialValue
|
|
117
|
+
});
|
|
118
|
+
if (css) emit(name, css);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Scan rendered rules for custom property declarations and collect
|
|
124
|
+
* auto-inferred @property rules via the SSR collector.
|
|
125
|
+
*
|
|
126
|
+
* @param rules - Rendered style rules containing CSS declarations
|
|
127
|
+
* @param collector - SSR collector to emit @property CSS into
|
|
128
|
+
* @param styles - Original styles object (used to skip explicit @property)
|
|
129
|
+
*/
|
|
130
|
+
function collectAutoInferredProperties(rules, collector, styles) {
|
|
131
|
+
scanAndEmitAutoProperties(rules, styles, (name, css) => {
|
|
132
|
+
collector.collectProperty(`__auto:${name}`, css);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* RSC variant: scan rendered rules and push auto-inferred @property CSS
|
|
137
|
+
* into the RSC pending buffer.
|
|
138
|
+
*/
|
|
139
|
+
function collectAutoInferredPropertiesRSC(rules, rscCache, styles) {
|
|
140
|
+
scanAndEmitAutoProperties(rules, styles, (name, css) => {
|
|
141
|
+
pushRSCCSS(rscCache, `__auto:${name}`, css);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/utils/has-keys.ts
|
|
146
|
+
/**
|
|
147
|
+
* Check if an object has any own enumerable keys.
|
|
148
|
+
* Avoids the array allocation of Object.keys(obj).length > 0.
|
|
149
|
+
*/
|
|
150
|
+
function hasKeys(obj) {
|
|
151
|
+
for (const _ in obj) return true;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/compute-styles.ts
|
|
156
|
+
/**
|
|
157
|
+
* Hook-free, synchronous style computation.
|
|
158
|
+
*
|
|
159
|
+
* Extracts the core logic from useStyles() into a plain function that can
|
|
160
|
+
* be called during React render without any hooks. Three code paths:
|
|
161
|
+
*
|
|
162
|
+
* 1. SSR collector — styles collected via ServerStyleCollector
|
|
163
|
+
* 2. Client inject — styles injected synchronously into the DOM
|
|
164
|
+
* 3. RSC inline — styles returned as CSS strings for inline <style> emission
|
|
165
|
+
*
|
|
166
|
+
* This enables tasty() components to work as React Server Components.
|
|
167
|
+
*/
|
|
168
|
+
const EMPTY_RESULT = { className: "" };
|
|
169
|
+
/**
|
|
170
|
+
* Mark internals as emitted for this RSC request.
|
|
171
|
+
*
|
|
172
|
+
* Internals (tokens, @property, @font-face, @counter-style) are emitted
|
|
173
|
+
* exclusively by the SSR collector via ServerStyleCollector.collectInternals().
|
|
174
|
+
* The SSR path is reliable because TastyRegistry is always present as a
|
|
175
|
+
* client component in the root layout, guaranteeing SSR runs for every page.
|
|
176
|
+
*
|
|
177
|
+
* Previously this function also emitted internals and coordinated with SSR
|
|
178
|
+
* via a globalThis flag, but that flag leaked across requests in the same
|
|
179
|
+
* Node.js process, causing pages without RSC-rendered tasty components
|
|
180
|
+
* (e.g. the playground route) to lose all token CSS.
|
|
181
|
+
*/
|
|
182
|
+
function collectInternalsRSC(rscCache) {
|
|
183
|
+
if (rscCache.internalsEmitted) return "";
|
|
184
|
+
rscCache.internalsEmitted = true;
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Collect per-component ancillary CSS (keyframes, @property, font-face,
|
|
189
|
+
* counter-style) for RSC mode.
|
|
190
|
+
*/
|
|
191
|
+
function collectAncillaryRSC(rscCache, styles) {
|
|
192
|
+
const parts = [];
|
|
193
|
+
const usedKf = getUsedKeyframes(styles);
|
|
194
|
+
const rscKeyframeNames = usedKf ? resolveKeyframesNames(usedKf) : null;
|
|
195
|
+
if (usedKf && rscKeyframeNames) for (const [authored, steps] of Object.entries(usedKf)) {
|
|
196
|
+
const name = rscKeyframeNames.get(authored);
|
|
197
|
+
const key = `__kf:${name}`;
|
|
198
|
+
if (!rscCache.emittedKeys.has(key)) {
|
|
199
|
+
rscCache.emittedKeys.add(key);
|
|
200
|
+
parts.push(formatKeyframesCSS(name, steps));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (hasLocalProperties(styles)) {
|
|
204
|
+
const localProperties = extractLocalProperties(styles);
|
|
205
|
+
if (localProperties) for (const [token, definition] of Object.entries(localProperties)) {
|
|
206
|
+
const key = `__prop:${token}`;
|
|
207
|
+
if (!rscCache.emittedKeys.has(key)) {
|
|
208
|
+
rscCache.emittedKeys.add(key);
|
|
209
|
+
const css = formatPropertyCSS(token, definition);
|
|
210
|
+
if (css) parts.push(css);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (hasLocalFontFace(styles)) {
|
|
215
|
+
const localFontFace = extractLocalFontFace(styles);
|
|
216
|
+
if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
|
|
217
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
218
|
+
for (const desc of descriptors) {
|
|
219
|
+
const key = `__ff:${fontFaceContentHash(family, desc)}`;
|
|
220
|
+
if (!rscCache.emittedKeys.has(key)) {
|
|
221
|
+
rscCache.emittedKeys.add(key);
|
|
222
|
+
parts.push(formatFontFaceRule(family, desc));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (hasLocalCounterStyle(styles)) {
|
|
228
|
+
const localCounterStyle = extractLocalCounterStyle(styles);
|
|
229
|
+
if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) {
|
|
230
|
+
const key = `__cs:${name}:${JSON.stringify(descriptors)}`;
|
|
231
|
+
if (!rscCache.emittedKeys.has(key)) {
|
|
232
|
+
rscCache.emittedKeys.add(key);
|
|
233
|
+
parts.push(formatCounterStyleRule(name, descriptors));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!isFunctionsPolyfillEnabled() && hasLocalFunctions(styles)) {
|
|
238
|
+
const localFunctions = extractLocalFunctions(styles);
|
|
239
|
+
if (localFunctions) for (const [name, definition] of Object.entries(localFunctions)) {
|
|
240
|
+
const key = `__func:${parseFunctionName(name)}`;
|
|
241
|
+
if (!rscCache.emittedKeys.has(key)) {
|
|
242
|
+
rscCache.emittedKeys.add(key);
|
|
243
|
+
parts.push(formatFunctionRule(name, definition));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return parts.join("\n");
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Process all chunks in RSC mode: render CSS to strings, allocate classNames,
|
|
251
|
+
* and return combined { className, css }.
|
|
252
|
+
*/
|
|
253
|
+
function computeStylesRSC(styles, chunkMap, stableStyles) {
|
|
254
|
+
const rscCache = getRSCCache();
|
|
255
|
+
const cssParts = [];
|
|
256
|
+
const classNames = [];
|
|
257
|
+
const rscUsedKf = getUsedKeyframes(styles);
|
|
258
|
+
const rscKeyframeNames = rscUsedKf ? resolveKeyframesNames(rscUsedKf) : null;
|
|
259
|
+
const pendingCSS = flushPendingCSS(rscCache);
|
|
260
|
+
if (pendingCSS) cssParts.push(pendingCSS);
|
|
261
|
+
const internalsCSS = collectInternalsRSC(rscCache);
|
|
262
|
+
if (internalsCSS) cssParts.push(internalsCSS);
|
|
263
|
+
for (const [chunkName, chunkStyleKeys] of chunkMap) {
|
|
264
|
+
if (chunkStyleKeys.length === 0) continue;
|
|
265
|
+
const baseKey = generateChunkCacheKey(styles, chunkName, chunkStyleKeys, stableStyles);
|
|
266
|
+
const renderResult = renderStylesForChunk(styles, chunkName, chunkStyleKeys);
|
|
267
|
+
if (renderResult.rules.length === 0) continue;
|
|
268
|
+
const { rules, cacheKey } = applyKeyframeNames(renderResult.rules, baseKey, rscKeyframeNames);
|
|
269
|
+
const { className, isNew } = rscAllocateClassName(rscCache, cacheKey);
|
|
270
|
+
classNames.push(className);
|
|
271
|
+
if (isNew) {
|
|
272
|
+
const css = formatRules(rules, className);
|
|
273
|
+
if (css) cssParts.push(css);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const ancillaryCSS = collectAncillaryRSC(rscCache, styles);
|
|
277
|
+
if (ancillaryCSS) cssParts.push(ancillaryCSS);
|
|
278
|
+
if (classNames.length === 0) return EMPTY_RESULT;
|
|
279
|
+
const css = cssParts.join("\n");
|
|
280
|
+
return {
|
|
281
|
+
className: classNames.join(" "),
|
|
282
|
+
css: css || void 0
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get keyframes that are actually used in styles.
|
|
287
|
+
* Returns null if no keyframes are used (fast path for zero overhead).
|
|
288
|
+
*/
|
|
289
|
+
function getUsedKeyframes(styles) {
|
|
290
|
+
const hasLocal = hasLocalKeyframes(styles);
|
|
291
|
+
const hasGlobal = hasGlobalKeyframes();
|
|
292
|
+
if (!hasLocal && !hasGlobal) return null;
|
|
293
|
+
const usedNames = extractAnimationNamesFromStyles(styles);
|
|
294
|
+
if (usedNames.size === 0) return null;
|
|
295
|
+
return filterUsedKeyframes(mergeKeyframes(hasLocal ? extractLocalKeyframes(styles) : null, hasGlobal ? getGlobalKeyframes() : null), usedNames);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Process a chunk on the SSR path: allocate via collector, render, collect CSS.
|
|
299
|
+
*/
|
|
300
|
+
function processChunkSSR(collector, styles, chunkName, styleKeys, stableStyles, keyframeNames) {
|
|
301
|
+
if (styleKeys.length === 0) return null;
|
|
302
|
+
const baseKey = generateChunkCacheKey(styles, chunkName, styleKeys, stableStyles);
|
|
303
|
+
const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);
|
|
304
|
+
if (renderResult.rules.length === 0) return null;
|
|
305
|
+
const { animations, rules, cacheKey } = applyKeyframeNames(renderResult.rules, baseKey, keyframeNames);
|
|
306
|
+
const { className, isNewAllocation } = collector.allocateClassName(cacheKey);
|
|
307
|
+
if (isNewAllocation) {
|
|
308
|
+
collector.collectChunk(cacheKey, className, rules);
|
|
309
|
+
return {
|
|
310
|
+
name: chunkName,
|
|
311
|
+
styleKeys,
|
|
312
|
+
cacheKey,
|
|
313
|
+
renderResult: {
|
|
314
|
+
...renderResult,
|
|
315
|
+
rules
|
|
316
|
+
},
|
|
317
|
+
className,
|
|
318
|
+
animations
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
name: chunkName,
|
|
323
|
+
styleKeys,
|
|
324
|
+
cacheKey,
|
|
325
|
+
renderResult: { rules: [] },
|
|
326
|
+
className
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Point a chunk's rules at the resolved keyframe names, and fold those names
|
|
331
|
+
* into its cache key.
|
|
332
|
+
*
|
|
333
|
+
* Both halves matter, and both have to happen before the rules are written
|
|
334
|
+
* anywhere: the declarations have to name the animation that will exist, and
|
|
335
|
+
* the key has to distinguish two components that authored the same shorthand
|
|
336
|
+
* over different definitions. Shared by the client and the server so they
|
|
337
|
+
* cannot disagree about either.
|
|
338
|
+
*/
|
|
339
|
+
function applyKeyframeNames(ruleset, baseKey, keyframeNames) {
|
|
340
|
+
if (!keyframeNames || keyframeNames.size === 0) return {
|
|
341
|
+
animations: [],
|
|
342
|
+
rules: ruleset,
|
|
343
|
+
cacheKey: baseKey
|
|
344
|
+
};
|
|
345
|
+
const animations = [...keyframeNames.keys()].filter((authored) => ruleset.some((rule) => referencesAnimation(rule.declarations, authored)));
|
|
346
|
+
if (animations.length === 0) return {
|
|
347
|
+
animations,
|
|
348
|
+
rules: ruleset,
|
|
349
|
+
cacheKey: baseKey
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
animations,
|
|
353
|
+
rules: ruleset.map((rule) => ({
|
|
354
|
+
...rule,
|
|
355
|
+
declarations: replaceAnimationNames(rule.declarations, keyframeNames)
|
|
356
|
+
})),
|
|
357
|
+
cacheKey: `${baseKey}\u0000kf:${animations.map((authored) => `${authored}=${keyframeNames.get(authored)}`).sort().join(",")}`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Process a chunk on the client: render, allocate className, and inject
|
|
362
|
+
* CSS synchronously. The injector's cache makes this idempotent.
|
|
363
|
+
*/
|
|
364
|
+
function processChunkSync(styles, chunkName, styleKeys, stableStyles, root, keyframeNames) {
|
|
365
|
+
if (styleKeys.length === 0) return null;
|
|
366
|
+
const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys, stableStyles);
|
|
367
|
+
const renderResult = renderStylesForChunk(styles, chunkName, styleKeys, cacheKey);
|
|
368
|
+
if (renderResult.rules.length === 0) return null;
|
|
369
|
+
const { animations, rules, cacheKey: injectKey } = applyKeyframeNames(renderResult.rules, cacheKey, keyframeNames);
|
|
370
|
+
const { className } = inject(rules, {
|
|
371
|
+
cacheKey: injectKey,
|
|
372
|
+
root,
|
|
373
|
+
pin: false
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
name: chunkName,
|
|
377
|
+
styleKeys,
|
|
378
|
+
cacheKey: injectKey,
|
|
379
|
+
renderResult: {
|
|
380
|
+
...renderResult,
|
|
381
|
+
rules
|
|
382
|
+
},
|
|
383
|
+
className,
|
|
384
|
+
animations
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Inject all ancillary rules (properties, font-faces, counter-styles) synchronously.
|
|
389
|
+
*/
|
|
390
|
+
function injectAncillarySync(styles, root) {
|
|
391
|
+
if (hasLocalProperties(styles)) {
|
|
392
|
+
const localProperties = extractLocalProperties(styles);
|
|
393
|
+
if (localProperties) for (const [token, definition] of Object.entries(localProperties)) property(token, {
|
|
394
|
+
...definition,
|
|
395
|
+
root
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (hasLocalFontFace(styles)) {
|
|
399
|
+
const localFontFace = extractLocalFontFace(styles);
|
|
400
|
+
if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
|
|
401
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
402
|
+
for (const desc of descriptors) fontFace(family, desc, { root });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (hasLocalCounterStyle(styles)) {
|
|
406
|
+
const localCounterStyle = extractLocalCounterStyle(styles);
|
|
407
|
+
if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) counterStyle(name, descriptors, { root });
|
|
408
|
+
}
|
|
409
|
+
if (!isFunctionsPolyfillEnabled() && hasLocalFunctions(styles)) {
|
|
410
|
+
const localFunctions = extractLocalFunctions(styles);
|
|
411
|
+
if (localFunctions) for (const [name, definition] of Object.entries(localFunctions)) func(name, definition, { root });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Collect all ancillary rules into the SSR collector.
|
|
416
|
+
*/
|
|
417
|
+
function collectAncillarySSR(collector, styles, chunks) {
|
|
418
|
+
const usedKf = getUsedKeyframes(styles);
|
|
419
|
+
if (usedKf) {
|
|
420
|
+
const names = resolveKeyframesNames(usedKf);
|
|
421
|
+
for (const [authored, steps] of Object.entries(usedKf)) {
|
|
422
|
+
const name = names.get(authored);
|
|
423
|
+
collector.collectKeyframes(name, formatKeyframesCSS(name, steps));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (hasLocalProperties(styles)) {
|
|
427
|
+
const localProperties = extractLocalProperties(styles);
|
|
428
|
+
if (localProperties) for (const [token, definition] of Object.entries(localProperties)) {
|
|
429
|
+
const css = formatPropertyCSS(token, definition);
|
|
430
|
+
if (css) collector.collectProperty(token, css);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (hasLocalFontFace(styles)) {
|
|
434
|
+
const localFontFace = extractLocalFontFace(styles);
|
|
435
|
+
if (localFontFace) for (const [family, input] of Object.entries(localFontFace)) {
|
|
436
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
437
|
+
for (const desc of descriptors) {
|
|
438
|
+
const hash = fontFaceContentHash(family, desc);
|
|
439
|
+
const css = formatFontFaceRule(family, desc);
|
|
440
|
+
collector.collectFontFace(hash, css);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (hasLocalCounterStyle(styles)) {
|
|
445
|
+
const localCounterStyle = extractLocalCounterStyle(styles);
|
|
446
|
+
if (localCounterStyle) for (const [name, descriptors] of Object.entries(localCounterStyle)) {
|
|
447
|
+
const css = formatCounterStyleRule(name, descriptors);
|
|
448
|
+
collector.collectCounterStyle(name, css);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (!isFunctionsPolyfillEnabled() && hasLocalFunctions(styles)) {
|
|
452
|
+
const localFunctions = extractLocalFunctions(styles);
|
|
453
|
+
if (localFunctions) for (const [name, definition] of Object.entries(localFunctions)) {
|
|
454
|
+
const css = formatFunctionRule(name, definition);
|
|
455
|
+
collector.collectFunction(parseFunctionName(name), css);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (getConfig().autoPropertyTypes !== false) {
|
|
459
|
+
const allRules = chunks.flatMap((c) => c.renderResult.rules);
|
|
460
|
+
if (allRules.length > 0) collectAutoInferredProperties(allRules, collector, styles);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Synchronous, hook-free style computation.
|
|
465
|
+
*
|
|
466
|
+
* Resolves recipes, categorizes style keys into chunks, renders CSS rules,
|
|
467
|
+
* allocates class names, and injects / collects / returns the CSS.
|
|
468
|
+
*
|
|
469
|
+
* Three code paths:
|
|
470
|
+
* 1. SSR collector — discovered via ALS or passed explicitly; CSS collected
|
|
471
|
+
* 2. RSC inline — no collector and no `document`; CSS returned as `result.css`
|
|
472
|
+
* for the caller to emit as an inline `<style>` tag
|
|
473
|
+
* 3. Client inject — CSS injected synchronously into the DOM (idempotent)
|
|
474
|
+
*
|
|
475
|
+
* @param styles - Tasty styles object (or undefined for no styles)
|
|
476
|
+
* @param options - Optional SSR collector override
|
|
477
|
+
*/
|
|
478
|
+
function computeStyles(styles, options) {
|
|
479
|
+
if (!styles || !hasKeys(styles)) return EMPTY_RESULT;
|
|
480
|
+
const resolved = resolveRecipes(styles);
|
|
481
|
+
const stableStyles = options?.stableStyles === true && resolved === styles;
|
|
482
|
+
if (isFunctionsPolyfillEnabled() && hasLocalFunctions(resolved)) registerLocalFunctionPolyfills(resolved);
|
|
483
|
+
const chunkMap = categorizeStyleKeys(resolved);
|
|
484
|
+
const collector = options?.ssrCollector !== void 0 ? options.ssrCollector : getRegisteredSSRCollector();
|
|
485
|
+
const chunks = [];
|
|
486
|
+
if (collector) {
|
|
487
|
+
collector.collectInternals();
|
|
488
|
+
const ssrKf = getUsedKeyframes(resolved);
|
|
489
|
+
const ssrKeyframeNames = ssrKf ? resolveKeyframesNames(ssrKf) : null;
|
|
490
|
+
for (const [chunkName, chunkStyleKeys] of chunkMap) {
|
|
491
|
+
const chunk = processChunkSSR(collector, resolved, chunkName, chunkStyleKeys, stableStyles, ssrKeyframeNames);
|
|
492
|
+
if (chunk) chunks.push(chunk);
|
|
493
|
+
}
|
|
494
|
+
collectAncillarySSR(collector, resolved, chunks);
|
|
495
|
+
} else if (typeof document === "undefined") return computeStylesRSC(resolved, chunkMap, stableStyles);
|
|
496
|
+
else {
|
|
497
|
+
const root = options?.root;
|
|
498
|
+
injectAncillarySync(resolved, root);
|
|
499
|
+
const usedKf = getUsedKeyframes(resolved);
|
|
500
|
+
const keyframeNames = usedKf ? resolveKeyframesNames(usedKf) : null;
|
|
501
|
+
const keyframeKeys = usedKf && keyframeNames ? holdKeyframes(usedKf, keyframeNames, { root }) : null;
|
|
502
|
+
for (const [chunkName, chunkStyleKeys] of chunkMap) {
|
|
503
|
+
const chunk = processChunkSync(resolved, chunkName, chunkStyleKeys, stableStyles, root, keyframeNames);
|
|
504
|
+
if (chunk) chunks.push(chunk);
|
|
505
|
+
}
|
|
506
|
+
if (keyframeKeys) for (const chunk of chunks) for (const authored of chunk.animations ?? []) {
|
|
507
|
+
const key = keyframeKeys.get(authored);
|
|
508
|
+
if (key) ownKeyframes(key, chunk.className, { root });
|
|
509
|
+
}
|
|
510
|
+
for (const chunk of chunks) touch(chunk.className, { root });
|
|
511
|
+
}
|
|
512
|
+
if (chunks.length === 0) return EMPTY_RESULT;
|
|
513
|
+
if (chunks.length === 1) return { className: chunks[0].className };
|
|
514
|
+
return { className: chunks.map((c) => c.className).join(" ") };
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/utils/filter-base-props.ts
|
|
518
|
+
const DOMPropNames = new Set(["id"]);
|
|
519
|
+
const BasePropNames = new Set([
|
|
520
|
+
"role",
|
|
521
|
+
"as",
|
|
522
|
+
"element",
|
|
523
|
+
"css",
|
|
524
|
+
"qa",
|
|
525
|
+
"mods",
|
|
526
|
+
"qaVal",
|
|
527
|
+
"hidden",
|
|
528
|
+
"isHidden",
|
|
529
|
+
"disabled",
|
|
530
|
+
"isDisabled",
|
|
531
|
+
"children",
|
|
532
|
+
"style",
|
|
533
|
+
"className",
|
|
534
|
+
"href",
|
|
535
|
+
"target",
|
|
536
|
+
"tabIndex"
|
|
537
|
+
]);
|
|
538
|
+
const ignoreEventPropsNames = new Set([
|
|
539
|
+
"onPress",
|
|
540
|
+
"onHoverStart",
|
|
541
|
+
"onHoverEnd",
|
|
542
|
+
"onPressStart",
|
|
543
|
+
"onPressEnd"
|
|
544
|
+
]);
|
|
545
|
+
const propRe = /^((data-).*)$/;
|
|
546
|
+
const eventRe = /^on[A-Z].+$/;
|
|
547
|
+
/**
|
|
548
|
+
* Filters out all props that aren't valid DOM props or defined via override prop obj.
|
|
549
|
+
* @param props - The component props to be filtered.
|
|
550
|
+
* @param opts - Props to override.
|
|
551
|
+
*/
|
|
552
|
+
function filterBaseProps(props, opts = {}) {
|
|
553
|
+
const { propNames, eventProps } = opts;
|
|
554
|
+
const filteredProps = {};
|
|
555
|
+
for (const prop in props) if (Object.prototype.hasOwnProperty.call(props, prop) && (DOMPropNames.has(prop) || BasePropNames.has(prop) || prop.startsWith("aria-") || eventProps && eventRe.test(prop) && !ignoreEventPropsNames.has(prop) || propNames?.has(prop) || propRe.test(prop))) filteredProps[prop] = props[prop];
|
|
556
|
+
return filteredProps;
|
|
557
|
+
}
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/utils/mod-attrs.ts
|
|
560
|
+
function modAttrs(map) {
|
|
561
|
+
return map ? Object.keys(map).reduce((attrs, key) => {
|
|
562
|
+
const value = map[key];
|
|
563
|
+
if (value == null || value === false) return attrs;
|
|
564
|
+
const attrName = `data-${camelToKebab(key)}`;
|
|
565
|
+
if (value === true) attrs[attrName] = "";
|
|
566
|
+
else if (typeof value === "string") attrs[attrName] = value;
|
|
567
|
+
else if (typeof value === "number") attrs[attrName] = String(value);
|
|
568
|
+
else console.warn(`[Tasty] Invalid mod value for "${key}". Expected boolean, string, or number, got ${typeof value}`);
|
|
569
|
+
return attrs;
|
|
570
|
+
}, {}) : null;
|
|
571
|
+
}
|
|
572
|
+
const _modAttrs = cacheWrapper(modAttrs);
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/utils/process-tokens.ts
|
|
575
|
+
/**
|
|
576
|
+
* Check if a value is a valid token value (string, number, or boolean - not object).
|
|
577
|
+
* Returns false for `false` values (they mean "skip this token").
|
|
578
|
+
*/
|
|
579
|
+
function isValidTokenValue(value) {
|
|
580
|
+
if (value === void 0 || value === null || value === false) return false;
|
|
581
|
+
if (typeof value === "object") {
|
|
582
|
+
console.warn("[Tasty] Object values are not allowed in tokens prop. Tokens do not support state-based styling. Use a primitive value instead.");
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Process a single token value through the tasty parser.
|
|
589
|
+
* Numbers are converted to strings; 0 stays as "0".
|
|
590
|
+
*/
|
|
591
|
+
function processTokenValue(value) {
|
|
592
|
+
if (typeof value === "number") {
|
|
593
|
+
if (value === 0) return "0";
|
|
594
|
+
return parseStyle(String(value)).output;
|
|
595
|
+
}
|
|
596
|
+
return parseStyle(value).output;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Resolve one `$name` / `#name` entry to the custom property it declares and
|
|
600
|
+
* the value to declare, or `null` when the entry declares nothing.
|
|
601
|
+
*
|
|
602
|
+
* The two sigils differ only in the property name they build and in how `true`
|
|
603
|
+
* is read: for a custom property it is the empty value, for a color it is
|
|
604
|
+
* `transparent`.
|
|
605
|
+
*/
|
|
606
|
+
function resolveTokenEntry(key, value) {
|
|
607
|
+
const name = `--${normalizeDslName(key.slice(1))}`;
|
|
608
|
+
if (key[0] === "$") return [name, processTokenValue(value === true ? "" : value)];
|
|
609
|
+
const colorValue = normalizeColorTokenValue(value);
|
|
610
|
+
if (colorValue === null) return null;
|
|
611
|
+
return [`${name}-color`, processTokenValue(colorValue)];
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Process tokens object into inline style properties.
|
|
615
|
+
* - `$name` -> `--name` with the parsed value
|
|
616
|
+
* - `#name` -> `--name-color` with the parsed value
|
|
617
|
+
*
|
|
618
|
+
* @param tokens - The tokens object to process
|
|
619
|
+
* @returns CSSProperties object or undefined if no tokens to process
|
|
620
|
+
*/
|
|
621
|
+
function processTokens(tokens) {
|
|
622
|
+
if (!tokens) return;
|
|
623
|
+
const keys = Object.keys(tokens);
|
|
624
|
+
if (keys.length === 0) return;
|
|
625
|
+
let result;
|
|
626
|
+
for (const key of keys) {
|
|
627
|
+
const value = tokens[key];
|
|
628
|
+
if (!isValidTokenValue(value)) continue;
|
|
629
|
+
if (key[0] !== "$" && key[0] !== "#") continue;
|
|
630
|
+
const entry = resolveTokenEntry(key, value);
|
|
631
|
+
if (!entry) continue;
|
|
632
|
+
if (!result) result = {};
|
|
633
|
+
result[entry[0]] = entry[1];
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region src/utils/get-display-name.ts
|
|
639
|
+
const DEFAULT_NAME = "Anonymous";
|
|
640
|
+
function getDisplayName(Component, fallbackName = DEFAULT_NAME) {
|
|
641
|
+
if (typeof Component === "function") return Component.displayName ?? Component.name ?? fallbackName;
|
|
642
|
+
return fallbackName;
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/utils/is-valid-element-type.ts
|
|
646
|
+
/**
|
|
647
|
+
* Lightweight replacement for `react-is`'s isValidElementType.
|
|
648
|
+
* Detects string tags, function/class components, and React exotic types
|
|
649
|
+
* (forwardRef, memo, lazy, etc.) via their internal $$typeof symbol.
|
|
650
|
+
*/
|
|
651
|
+
function isValidElementType(value) {
|
|
652
|
+
if (typeof value === "string" || typeof value === "function") return true;
|
|
653
|
+
if (typeof value === "object" && value !== null) return typeof value.$$typeof === "symbol";
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region src/tasty.tsx
|
|
658
|
+
/**
|
|
659
|
+
* Precalculated entries for performance optimization
|
|
660
|
+
*/
|
|
661
|
+
const IS_PROPERTIES_ENTRIES = Object.entries({
|
|
662
|
+
isDisabled: "disabled",
|
|
663
|
+
isHidden: "hidden",
|
|
664
|
+
isChecked: "checked"
|
|
665
|
+
});
|
|
666
|
+
/**
|
|
667
|
+
* Helper function to handle is* properties consistently
|
|
668
|
+
* Transforms is* props to HTML attributes and adds corresponding data-* attributes
|
|
669
|
+
*/
|
|
670
|
+
function handleIsProperties(props) {
|
|
671
|
+
for (const [isProperty, targetAttribute] of IS_PROPERTIES_ENTRIES) {
|
|
672
|
+
if (isProperty in props) {
|
|
673
|
+
props[targetAttribute] = props[isProperty];
|
|
674
|
+
delete props[isProperty];
|
|
675
|
+
}
|
|
676
|
+
const dataAttribute = `data-${targetAttribute}`;
|
|
677
|
+
if (!(dataAttribute in props) && props[targetAttribute]) props[dataAttribute] = "";
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Creates a sub-element component for compound component patterns.
|
|
682
|
+
* Sub-elements are lightweight components with data-element attribute for CSS targeting.
|
|
683
|
+
*/
|
|
684
|
+
function createSubElement(elementName, definition) {
|
|
685
|
+
const config = typeof definition === "string" ? { as: definition } : definition;
|
|
686
|
+
const tag = config.as ?? "div";
|
|
687
|
+
const defaultQa = config.qa;
|
|
688
|
+
const defaultQaVal = config.qaVal;
|
|
689
|
+
const SubElement = forwardRef((props, ref) => {
|
|
690
|
+
const { qa, qaVal, mods, tokens, isDisabled, isHidden, isChecked, className, style, ...htmlProps } = props;
|
|
691
|
+
let modDataAttrs;
|
|
692
|
+
if (mods) modDataAttrs = _modAttrs(mods);
|
|
693
|
+
const tokenStyle = tokens ? processTokens(tokens) : void 0;
|
|
694
|
+
let mergedStyle;
|
|
695
|
+
if (tokenStyle || style) mergedStyle = tokenStyle && style ? {
|
|
696
|
+
...tokenStyle,
|
|
697
|
+
...style
|
|
698
|
+
} : tokenStyle ?? style;
|
|
699
|
+
const elementProps = {
|
|
700
|
+
"data-element": elementName,
|
|
701
|
+
"data-qa": qa ?? defaultQa,
|
|
702
|
+
"data-qaval": qaVal ?? defaultQaVal,
|
|
703
|
+
...modDataAttrs || {},
|
|
704
|
+
...htmlProps,
|
|
705
|
+
className,
|
|
706
|
+
style: mergedStyle,
|
|
707
|
+
isDisabled,
|
|
708
|
+
isHidden,
|
|
709
|
+
isChecked,
|
|
710
|
+
ref
|
|
711
|
+
};
|
|
712
|
+
handleIsProperties(elementProps);
|
|
713
|
+
if (elementProps["data-qa"] === void 0) delete elementProps["data-qa"];
|
|
714
|
+
if (elementProps["data-qaval"] === void 0) delete elementProps["data-qaval"];
|
|
715
|
+
return createElement(tag, elementProps);
|
|
716
|
+
});
|
|
717
|
+
SubElement.displayName = `SubElement(${elementName})`;
|
|
718
|
+
return SubElement;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Pre-compute the mapping from prop name to token key at component-creation time.
|
|
722
|
+
* Array form: `'progress'` -> `'$progress'`, `'accentColor'` -> `'#accent'`.
|
|
723
|
+
* Object form: entries used as-is.
|
|
724
|
+
*/
|
|
725
|
+
function buildTokenPropsMapping(def) {
|
|
726
|
+
if (Array.isArray(def)) return def.map((propName) => {
|
|
727
|
+
if (propName.endsWith("Color") && propName.length > 5) return [propName, `#${propName.slice(0, -5)}`];
|
|
728
|
+
return [propName, `$${propName}`];
|
|
729
|
+
});
|
|
730
|
+
return Object.entries(def);
|
|
731
|
+
}
|
|
732
|
+
function tasty(Component, options) {
|
|
733
|
+
if (isValidElementType(Component)) return tastyWrap(Component, options);
|
|
734
|
+
return tastyElement(Component);
|
|
735
|
+
}
|
|
736
|
+
function tastyWrap(Component, options) {
|
|
737
|
+
const { as: extendTag, element: extendElement, styleProps: _styleProps, modProps: _modProps, tokenProps: _tokenProps, variants: _variants, elements: _elements, ...defaultProps } = options ?? {};
|
|
738
|
+
const propsWithStyles = ["styles"].concat(Object.keys(defaultProps).filter((prop) => prop.endsWith("Styles")));
|
|
739
|
+
const _WrappedComponent = forwardRef((props, ref) => {
|
|
740
|
+
const { as, element, ...restProps } = props;
|
|
741
|
+
const mergedStylesMap = propsWithStyles.reduce((map, prop) => {
|
|
742
|
+
const restValue = restProps[prop];
|
|
743
|
+
const defaultValue = defaultProps[prop];
|
|
744
|
+
if (restValue != null && defaultValue != null) map[prop] = mergeStyles(defaultValue, restValue);
|
|
745
|
+
else map[prop] = restValue ?? defaultValue;
|
|
746
|
+
return map;
|
|
747
|
+
}, {});
|
|
748
|
+
return createElement(Component, {
|
|
749
|
+
...defaultProps,
|
|
750
|
+
...restProps,
|
|
751
|
+
...mergedStylesMap,
|
|
752
|
+
as: as ?? extendTag,
|
|
753
|
+
element: element || extendElement,
|
|
754
|
+
ref
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
_WrappedComponent.displayName = `TastyWrappedComponent(${getDisplayName(Component, defaultProps.qa ?? extendTag ?? "Anonymous")})`;
|
|
758
|
+
return _WrappedComponent;
|
|
759
|
+
}
|
|
760
|
+
function tastyElement(tastyOptions) {
|
|
761
|
+
const { as: originalAs = "div", element: defaultElement, styles: defaultStyles, styleProps, modProps: modPropsDef, tokenProps: tokenPropsDef, variants, tokens: defaultTokens, elements, ...defaultProps } = tastyOptions;
|
|
762
|
+
let variantStylesMap;
|
|
763
|
+
if (variants) {
|
|
764
|
+
let baseStyles = defaultStyles;
|
|
765
|
+
let extensionStyles;
|
|
766
|
+
if (defaultStyles) for (const key of Object.keys(defaultStyles)) {
|
|
767
|
+
if (isSelector(key)) continue;
|
|
768
|
+
const value = defaultStyles[key];
|
|
769
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && !("" in value)) {
|
|
770
|
+
if (!extensionStyles) {
|
|
771
|
+
baseStyles = { ...defaultStyles };
|
|
772
|
+
extensionStyles = {};
|
|
773
|
+
}
|
|
774
|
+
extensionStyles[key] = value;
|
|
775
|
+
delete baseStyles[key];
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
variantStylesMap = Object.entries(variants).reduce((map, [variant, variantStyles]) => {
|
|
779
|
+
map[variant] = extensionStyles ? mergeStyles(baseStyles, variantStyles, extensionStyles) : mergeStyles(baseStyles, variantStyles);
|
|
780
|
+
return map;
|
|
781
|
+
}, {});
|
|
782
|
+
if (!variantStylesMap["default"]) variantStylesMap["default"] = defaultStyles;
|
|
783
|
+
}
|
|
784
|
+
const { qa: defaultQa, qaVal: defaultQaVal, ...otherDefaultProps } = defaultProps ?? {};
|
|
785
|
+
const ownPropsToCheck = styleProps ? styleProps.concat(BASE_STYLES) : BASE_STYLES;
|
|
786
|
+
let propsToCheck = ownPropsToCheck;
|
|
787
|
+
let propsToCheckVersion = -1;
|
|
788
|
+
const modPropsKeys = modPropsDef ? Array.isArray(modPropsDef) ? modPropsDef : Object.keys(modPropsDef) : void 0;
|
|
789
|
+
const tokenPropsMapping = tokenPropsDef ? buildTokenPropsMapping(tokenPropsDef) : void 0;
|
|
790
|
+
const classNameCache = /* @__PURE__ */ new Map();
|
|
791
|
+
const STABLE_STYLES = { stableStyles: true };
|
|
792
|
+
const _TastyComponent = forwardRef((incomingProps, ref) => {
|
|
793
|
+
const applyPropHandlers = propHandlerRegistry.apply;
|
|
794
|
+
const { as, styles: rawStyles, variant, mods, element, qa, qaVal, className: userClassName, tokens, style, theme, ...otherProps } = applyPropHandlers ? applyPropHandlers(incomingProps) : incomingProps;
|
|
795
|
+
let styles = rawStyles;
|
|
796
|
+
let propStyles = null;
|
|
797
|
+
if (propsToCheckVersion !== baseStylePropsRegistry.version) {
|
|
798
|
+
const promoted = baseStylePropsRegistry.list;
|
|
799
|
+
propsToCheck = promoted.length === 0 ? ownPropsToCheck : ownPropsToCheck.concat(promoted);
|
|
800
|
+
propsToCheckVersion = baseStylePropsRegistry.version;
|
|
801
|
+
}
|
|
802
|
+
for (const prop of propsToCheck) {
|
|
803
|
+
const key = prop;
|
|
804
|
+
if (key in otherProps) {
|
|
805
|
+
if (!propStyles) propStyles = {};
|
|
806
|
+
const value = otherProps[key];
|
|
807
|
+
propStyles[key] = value;
|
|
808
|
+
delete otherProps[key];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!styles || styles && !hasKeys(styles)) styles = void 0;
|
|
812
|
+
let propMods;
|
|
813
|
+
if (modPropsKeys) {
|
|
814
|
+
for (const key of modPropsKeys) if (key in otherProps) {
|
|
815
|
+
if (!propMods) propMods = {};
|
|
816
|
+
propMods[key] = otherProps[key];
|
|
817
|
+
delete otherProps[key];
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
let propTokens;
|
|
821
|
+
if (tokenPropsMapping) {
|
|
822
|
+
for (const [propName, tokenKey] of tokenPropsMapping) if (propName in otherProps) {
|
|
823
|
+
if (!propTokens) propTokens = {};
|
|
824
|
+
propTokens[tokenKey] = otherProps[propName];
|
|
825
|
+
delete otherProps[propName];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const baseStyles = variantStylesMap ? variantStylesMap[variant || "default"] ?? variantStylesMap["default"] : defaultStyles;
|
|
829
|
+
const hasInstanceStyles = styles && hasKeys(styles);
|
|
830
|
+
const hasPropStyles = propStyles && hasKeys(propStyles);
|
|
831
|
+
const allStyles = hasInstanceStyles || hasPropStyles ? mergeStyles(baseStyles, styles, propStyles) : baseStyles;
|
|
832
|
+
const useFactoryCache = typeof document !== "undefined";
|
|
833
|
+
let stylesResult;
|
|
834
|
+
if (useFactoryCache && allStyles === baseStyles && classNameCache.has(allStyles)) {
|
|
835
|
+
stylesResult = { className: classNameCache.get(allStyles) };
|
|
836
|
+
touch(stylesResult.className);
|
|
837
|
+
} else {
|
|
838
|
+
stylesResult = computeStyles(allStyles, !useFactoryCache && allStyles === baseStyles ? STABLE_STYLES : void 0);
|
|
839
|
+
if (useFactoryCache && allStyles === baseStyles) classNameCache.set(allStyles, stylesResult.className);
|
|
840
|
+
}
|
|
841
|
+
let mergedTokens;
|
|
842
|
+
if (defaultTokens || tokens || propTokens) if (!defaultTokens && !propTokens) mergedTokens = tokens;
|
|
843
|
+
else if (!tokens && !propTokens) mergedTokens = defaultTokens;
|
|
844
|
+
else mergedTokens = {
|
|
845
|
+
...defaultTokens,
|
|
846
|
+
...tokens,
|
|
847
|
+
...propTokens
|
|
848
|
+
};
|
|
849
|
+
const processedTokenStyle = processTokens(mergedTokens);
|
|
850
|
+
let mergedStyle;
|
|
851
|
+
if (processedTokenStyle || style) if (!processedTokenStyle) mergedStyle = style;
|
|
852
|
+
else if (!style) mergedStyle = processedTokenStyle;
|
|
853
|
+
else mergedStyle = {
|
|
854
|
+
...processedTokenStyle,
|
|
855
|
+
...style
|
|
856
|
+
};
|
|
857
|
+
const mergedMods = propMods ? {
|
|
858
|
+
...mods,
|
|
859
|
+
...propMods
|
|
860
|
+
} : mods;
|
|
861
|
+
let modDataAttrs;
|
|
862
|
+
if (mergedMods) modDataAttrs = _modAttrs(mergedMods);
|
|
863
|
+
const finalClassName = [userClassName || "", stylesResult.className].filter(Boolean).join(" ");
|
|
864
|
+
const elementProps = {
|
|
865
|
+
"data-element": element || defaultElement,
|
|
866
|
+
"data-qa": qa || defaultQa,
|
|
867
|
+
"data-qaval": qaVal || defaultQaVal,
|
|
868
|
+
"data-theme": theme,
|
|
869
|
+
...otherDefaultProps,
|
|
870
|
+
...modDataAttrs || {},
|
|
871
|
+
...otherProps,
|
|
872
|
+
className: finalClassName,
|
|
873
|
+
style: mergedStyle,
|
|
874
|
+
ref
|
|
875
|
+
};
|
|
876
|
+
handleIsProperties(elementProps);
|
|
877
|
+
if (elementProps["data-theme"] === void 0) delete elementProps["data-theme"];
|
|
878
|
+
const el = createElement(as ?? originalAs, elementProps);
|
|
879
|
+
if (stylesResult.css) {
|
|
880
|
+
const nonce = getConfig().nonce;
|
|
881
|
+
return createElement(Fragment, null, createElement("style", {
|
|
882
|
+
"data-tasty-rsc": "",
|
|
883
|
+
nonce,
|
|
884
|
+
dangerouslySetInnerHTML: { __html: stylesResult.css }
|
|
885
|
+
}), el);
|
|
886
|
+
}
|
|
887
|
+
return el;
|
|
888
|
+
});
|
|
889
|
+
_TastyComponent.displayName = `TastyComponent(${defaultProps.qa || originalAs})`;
|
|
890
|
+
if (elements) {
|
|
891
|
+
const subElements = Object.entries(elements).reduce((acc, [name, definition]) => {
|
|
892
|
+
acc[name] = createSubElement(name, definition);
|
|
893
|
+
return acc;
|
|
894
|
+
}, {});
|
|
895
|
+
return Object.assign(_TastyComponent, subElements);
|
|
896
|
+
}
|
|
897
|
+
return _TastyComponent;
|
|
898
|
+
}
|
|
899
|
+
const Element = /* @__PURE__ */ tasty({});
|
|
900
|
+
//#endregion
|
|
901
|
+
//#region src/ssr/context.ts
|
|
902
|
+
/**
|
|
903
|
+
* React context for SSR collector discovery.
|
|
904
|
+
*
|
|
905
|
+
* Used by Next.js TastyRegistry to provide the ServerStyleCollector
|
|
906
|
+
* to useStyles() via React context (the streaming-compatible path).
|
|
907
|
+
*
|
|
908
|
+
* This avoids relying on globalThis for cross-layer communication,
|
|
909
|
+
* which leaks between RSC and SSR module graphs in Next.js App Router.
|
|
910
|
+
*
|
|
911
|
+
* The context is created lazily to avoid calling createContext() at
|
|
912
|
+
* module evaluation time, which would break Server Components that
|
|
913
|
+
* import from `@tenphi/tasty` without using any React hooks.
|
|
914
|
+
*/
|
|
915
|
+
let _ctx;
|
|
916
|
+
function getTastySSRContext() {
|
|
917
|
+
if (!_ctx) _ctx = createContext(null);
|
|
918
|
+
return _ctx;
|
|
919
|
+
}
|
|
920
|
+
//#endregion
|
|
921
|
+
//#region src/hooks/useStyles.ts
|
|
922
|
+
/**
|
|
923
|
+
* Hook to generate CSS classes from Tasty styles.
|
|
924
|
+
* Thin wrapper around `computeStyles()` that adds React context-based
|
|
925
|
+
* SSR collector discovery for backward compatibility with TastyRegistry.
|
|
926
|
+
*
|
|
927
|
+
* For hook-free usage (e.g. in server components), use `computeStyles()` directly.
|
|
928
|
+
*
|
|
929
|
+
* @example
|
|
930
|
+
* ```tsx
|
|
931
|
+
* function MyComponent() {
|
|
932
|
+
* const { className } = useStyles({
|
|
933
|
+
* padding: '2x',
|
|
934
|
+
* fill: '#purple',
|
|
935
|
+
* radius: '1r',
|
|
936
|
+
* });
|
|
937
|
+
*
|
|
938
|
+
* return <div className={className}>Styled content</div>;
|
|
939
|
+
* }
|
|
940
|
+
* ```
|
|
941
|
+
*/
|
|
942
|
+
function useStyles(styles, options) {
|
|
943
|
+
return computeStyles(styles, {
|
|
944
|
+
ssrCollector: useContext(getTastySSRContext()),
|
|
945
|
+
root: options?.root
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/hooks/useGlobalStyles.ts
|
|
950
|
+
const getClientGlobalSlots = /* @__PURE__ */ createClientState(() => /* @__PURE__ */ new Map());
|
|
951
|
+
const noop = () => {};
|
|
952
|
+
/**
|
|
953
|
+
* Inject global styles for a given selector.
|
|
954
|
+
* Useful for styling elements by selector without generating classNames.
|
|
955
|
+
*
|
|
956
|
+
* SSR-aware: when a ServerStyleCollector is available, CSS is collected
|
|
957
|
+
* during the render phase instead of being injected into the DOM.
|
|
958
|
+
*
|
|
959
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
960
|
+
*
|
|
961
|
+
* Injected styles are permanent — they are not cleaned up on component unmount.
|
|
962
|
+
* Use the `id` option for update tracking when styles change over the
|
|
963
|
+
* component lifecycle.
|
|
964
|
+
*
|
|
965
|
+
* Update tracking is per-slot and per-root: a slot (`id`, or the selector when
|
|
966
|
+
* no `id` is given) holds exactly one injection per `root`. Changing the styles
|
|
967
|
+
* replaces it; rendering styles that produce no CSS clears it.
|
|
968
|
+
*
|
|
969
|
+
* @param selector - CSS selector to apply styles to (e.g., '.my-class', ':root', 'body')
|
|
970
|
+
* @param styles - Tasty styles object
|
|
971
|
+
* @param options - Optional settings including `id` for update tracking
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* ```tsx
|
|
975
|
+
* function MyComponent() {
|
|
976
|
+
* useGlobalStyles('.card', {
|
|
977
|
+
* padding: '2x',
|
|
978
|
+
* radius: '1r',
|
|
979
|
+
* fill: '#white',
|
|
980
|
+
* });
|
|
981
|
+
*
|
|
982
|
+
* return <div className="card">Content</div>;
|
|
983
|
+
* }
|
|
984
|
+
* ```
|
|
985
|
+
*/
|
|
986
|
+
function useGlobalStyles(selector, styles, options) {
|
|
987
|
+
if (!styles) return;
|
|
988
|
+
if (!selector) {
|
|
989
|
+
console.warn("[Tasty] useGlobalStyles: selector is required and cannot be empty. Styles will not be injected.");
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const target = getStyleTarget();
|
|
993
|
+
const slots = target.mode === "client" ? getClientGlobalSlots(options?.root ?? document) : null;
|
|
994
|
+
const slotKey = options?.id ?? selector;
|
|
995
|
+
const stylesKey = slots ? JSON.stringify(styles) : "";
|
|
996
|
+
const existing = slots?.get(slotKey);
|
|
997
|
+
if (existing && existing.stylesKey === stylesKey) return;
|
|
998
|
+
const resolvedStyles = resolveRecipes(styles);
|
|
999
|
+
const styleResults = renderStyles(resolvedStyles, selector);
|
|
1000
|
+
if (styleResults.length === 0) {
|
|
1001
|
+
if (slots) {
|
|
1002
|
+
existing?.dispose();
|
|
1003
|
+
slots.set(slotKey, {
|
|
1004
|
+
stylesKey,
|
|
1005
|
+
dispose: noop
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (target.mode === "ssr") {
|
|
1011
|
+
target.collector.collectInternals();
|
|
1012
|
+
const css = formatGlobalRules(styleResults);
|
|
1013
|
+
if (css) {
|
|
1014
|
+
const key = options?.id ? `global:${options.id}` : `global:${selector}:${hashString(css)}`;
|
|
1015
|
+
target.collector.collectGlobalStyles(key, css, options?.id != null);
|
|
1016
|
+
}
|
|
1017
|
+
if (getConfig().autoPropertyTypes !== false) collectAutoInferredProperties(styleResults, target.collector, resolvedStyles);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (target.mode === "rsc") {
|
|
1021
|
+
const css = formatGlobalRules(styleResults);
|
|
1022
|
+
if (css) {
|
|
1023
|
+
const key = options?.id ? `__global:${options.id}` : `__global:${selector}:${hashString(css)}`;
|
|
1024
|
+
pushRSCCSS(target.cache, key, css, options?.id != null);
|
|
1025
|
+
}
|
|
1026
|
+
if (getConfig().autoPropertyTypes !== false) collectAutoInferredPropertiesRSC(styleResults, target.cache, resolvedStyles);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (slots) {
|
|
1030
|
+
existing?.dispose();
|
|
1031
|
+
const { dispose } = injectGlobal(styleResults, { root: options?.root });
|
|
1032
|
+
slots.set(slotKey, {
|
|
1033
|
+
stylesKey,
|
|
1034
|
+
dispose
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region src/hooks/useRawCSS.ts
|
|
1040
|
+
const getClientState$1 = /* @__PURE__ */ createClientState(() => ({
|
|
1041
|
+
entries: /* @__PURE__ */ new Map(),
|
|
1042
|
+
contentDedup: /* @__PURE__ */ new Set(),
|
|
1043
|
+
factoryDeps: /* @__PURE__ */ new Map()
|
|
1044
|
+
}));
|
|
1045
|
+
/**
|
|
1046
|
+
* Inject raw CSS text directly without parsing.
|
|
1047
|
+
* This is a low-overhead alternative for injecting global CSS that doesn't need tasty processing.
|
|
1048
|
+
*
|
|
1049
|
+
* The CSS is inserted into a separate style element (data-tasty-raw) to avoid conflicts
|
|
1050
|
+
* with tasty's chunked style sheets.
|
|
1051
|
+
*
|
|
1052
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
1053
|
+
*
|
|
1054
|
+
* Injected styles are permanent — they are not cleaned up on component unmount.
|
|
1055
|
+
* Use the `id` option for update tracking when styles change over the
|
|
1056
|
+
* component lifecycle.
|
|
1057
|
+
*
|
|
1058
|
+
* @example Static CSS string
|
|
1059
|
+
* ```tsx
|
|
1060
|
+
* function GlobalStyles() {
|
|
1061
|
+
* useRawCSS(`
|
|
1062
|
+
* body {
|
|
1063
|
+
* margin: 0;
|
|
1064
|
+
* padding: 0;
|
|
1065
|
+
* font-family: sans-serif;
|
|
1066
|
+
* }
|
|
1067
|
+
* `);
|
|
1068
|
+
*
|
|
1069
|
+
* return null;
|
|
1070
|
+
* }
|
|
1071
|
+
* ```
|
|
1072
|
+
*
|
|
1073
|
+
* @example Factory function with dependencies
|
|
1074
|
+
* ```tsx
|
|
1075
|
+
* function ThemeStyles({ theme }: { theme: 'light' | 'dark' }) {
|
|
1076
|
+
* useRawCSS(() => `
|
|
1077
|
+
* :root {
|
|
1078
|
+
* --bg-color: ${theme === 'dark' ? '#1a1a1a' : '#ffffff'};
|
|
1079
|
+
* --text-color: ${theme === 'dark' ? '#ffffff' : '#1a1a1a'};
|
|
1080
|
+
* }
|
|
1081
|
+
* `, [theme], { id: 'theme-vars' });
|
|
1082
|
+
*
|
|
1083
|
+
* return null;
|
|
1084
|
+
* }
|
|
1085
|
+
* ```
|
|
1086
|
+
*
|
|
1087
|
+
* @example With options
|
|
1088
|
+
* ```tsx
|
|
1089
|
+
* function ShadowStyles({ shadowRoot }) {
|
|
1090
|
+
* useRawCSS(() => `.scoped { color: red; }`, [], { root: shadowRoot });
|
|
1091
|
+
* return null;
|
|
1092
|
+
* }
|
|
1093
|
+
* ```
|
|
1094
|
+
*/
|
|
1095
|
+
function useRawCSS(cssOrFactory, depsOrOptions, options) {
|
|
1096
|
+
const isFactory = typeof cssOrFactory === "function";
|
|
1097
|
+
const deps = isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : void 0;
|
|
1098
|
+
const opts = isFactory ? options : depsOrOptions;
|
|
1099
|
+
const target = getStyleTarget();
|
|
1100
|
+
const state = target.mode === "client" ? getClientState$1(opts?.root ?? document) : null;
|
|
1101
|
+
if (isFactory && deps && opts?.id && state) {
|
|
1102
|
+
const cachedDeps = state.factoryDeps.get(opts.id);
|
|
1103
|
+
if (cachedDeps && depsEqual(cachedDeps, deps)) return;
|
|
1104
|
+
}
|
|
1105
|
+
const css = isFactory ? cssOrFactory() : cssOrFactory;
|
|
1106
|
+
if (!css.trim()) return;
|
|
1107
|
+
if (target.mode === "ssr") {
|
|
1108
|
+
const key = opts?.id ? `raw:${opts.id}` : `raw:${hashString(css)}`;
|
|
1109
|
+
target.collector.collectRawCSS(key, css, opts?.id != null);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (target.mode === "rsc") {
|
|
1113
|
+
const key = opts?.id ? `__raw:${opts.id}` : `__raw:${hashString(css)}`;
|
|
1114
|
+
pushRSCCSS(target.cache, key, css, opts?.id != null);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
if (!state) return;
|
|
1118
|
+
const id = opts?.id;
|
|
1119
|
+
if (id) {
|
|
1120
|
+
const existing = state.entries.get(id);
|
|
1121
|
+
if (existing) {
|
|
1122
|
+
if (existing.contentKey === css) return;
|
|
1123
|
+
existing.dispose();
|
|
1124
|
+
}
|
|
1125
|
+
const { dispose } = injectRawCSS(css, opts);
|
|
1126
|
+
state.entries.set(id, {
|
|
1127
|
+
contentKey: css,
|
|
1128
|
+
dispose
|
|
1129
|
+
});
|
|
1130
|
+
if (deps) state.factoryDeps.set(id, deps);
|
|
1131
|
+
} else {
|
|
1132
|
+
const contentKey = hashString(css);
|
|
1133
|
+
if (state.contentDedup.has(contentKey)) return;
|
|
1134
|
+
state.contentDedup.add(contentKey);
|
|
1135
|
+
injectRawCSS(css, opts);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
//#endregion
|
|
1139
|
+
//#region src/hooks/useKeyframes.ts
|
|
1140
|
+
const getClientState = /* @__PURE__ */ createClientState(() => ({
|
|
1141
|
+
contentToName: /* @__PURE__ */ new Map(),
|
|
1142
|
+
namedSlots: /* @__PURE__ */ new Map(),
|
|
1143
|
+
factoryDeps: /* @__PURE__ */ new Map()
|
|
1144
|
+
}));
|
|
1145
|
+
function useKeyframes(stepsOrFactory, depsOrOptions, options) {
|
|
1146
|
+
const isFactory = typeof stepsOrFactory === "function";
|
|
1147
|
+
const deps = isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : void 0;
|
|
1148
|
+
const opts = isFactory ? options : depsOrOptions;
|
|
1149
|
+
const target = getStyleTarget();
|
|
1150
|
+
const clientState = target.mode === "client" ? getClientState(opts?.root ?? document) : null;
|
|
1151
|
+
if (isFactory && deps && opts?.name && clientState) {
|
|
1152
|
+
const cached = clientState.factoryDeps.get(opts.name);
|
|
1153
|
+
if (cached && depsEqual(cached.deps, deps)) return cached.name;
|
|
1154
|
+
}
|
|
1155
|
+
const steps = isFactory ? stepsOrFactory() : stepsOrFactory;
|
|
1156
|
+
if (!steps || Object.keys(steps).length === 0) return "";
|
|
1157
|
+
if (target.mode === "ssr") {
|
|
1158
|
+
const actualName = target.collector.allocateKeyframeName(opts?.name);
|
|
1159
|
+
const css = formatKeyframesCSS(actualName, steps);
|
|
1160
|
+
target.collector.collectKeyframes(actualName, css);
|
|
1161
|
+
return actualName;
|
|
1162
|
+
}
|
|
1163
|
+
if (target.mode === "rsc") {
|
|
1164
|
+
const serializedContent = JSON.stringify(steps);
|
|
1165
|
+
const key = `__kf:${opts?.name ?? ""}:${serializedContent}`;
|
|
1166
|
+
const existingName = target.cache.generatedNames.get(key);
|
|
1167
|
+
if (existingName) return existingName;
|
|
1168
|
+
const actualName = opts?.name ?? makeKeyframeName(getNamePrefix(), hashString(serializedContent));
|
|
1169
|
+
const css = formatKeyframesCSS(actualName, steps);
|
|
1170
|
+
pushRSCCSS(target.cache, key, css);
|
|
1171
|
+
target.cache.generatedNames.set(key, actualName);
|
|
1172
|
+
return actualName;
|
|
1173
|
+
}
|
|
1174
|
+
const state = clientState ?? getClientState(opts?.root ?? document);
|
|
1175
|
+
const serializedContent = JSON.stringify(steps);
|
|
1176
|
+
const cacheKey = `${opts?.name ?? ""}:${serializedContent}`;
|
|
1177
|
+
const cachedName = state.contentToName.get(cacheKey);
|
|
1178
|
+
if (cachedName) return cachedName;
|
|
1179
|
+
const providedName = opts?.name;
|
|
1180
|
+
if (providedName) {
|
|
1181
|
+
const slot = state.namedSlots.get(providedName);
|
|
1182
|
+
if (slot && slot.cacheKey !== cacheKey) {
|
|
1183
|
+
slot.dispose();
|
|
1184
|
+
state.contentToName.delete(slot.cacheKey);
|
|
1185
|
+
state.namedSlots.delete(providedName);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const result = keyframes(steps, {
|
|
1189
|
+
name: providedName,
|
|
1190
|
+
root: opts?.root
|
|
1191
|
+
});
|
|
1192
|
+
const name = result.toString();
|
|
1193
|
+
state.contentToName.set(cacheKey, name);
|
|
1194
|
+
if (providedName) {
|
|
1195
|
+
state.namedSlots.set(providedName, {
|
|
1196
|
+
cacheKey,
|
|
1197
|
+
dispose: result.dispose
|
|
1198
|
+
});
|
|
1199
|
+
if (deps) state.factoryDeps.set(providedName, {
|
|
1200
|
+
deps,
|
|
1201
|
+
name
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
return name;
|
|
1205
|
+
}
|
|
1206
|
+
//#endregion
|
|
1207
|
+
//#region src/hooks/useProperty.ts
|
|
1208
|
+
/**
|
|
1209
|
+
* Register a CSS @property custom property.
|
|
1210
|
+
* This enables advanced features like animating custom properties.
|
|
1211
|
+
*
|
|
1212
|
+
* Note: @property rules are global and persistent once defined.
|
|
1213
|
+
* The function ensures the property is only registered once per root.
|
|
1214
|
+
*
|
|
1215
|
+
* Accepts tasty token syntax for the property name:
|
|
1216
|
+
* - `$name` → defines `--name`
|
|
1217
|
+
* - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')
|
|
1218
|
+
* - `--name` → defines `--name` (legacy format)
|
|
1219
|
+
*
|
|
1220
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
1221
|
+
*
|
|
1222
|
+
* @param name - The property token ($name, #name) or CSS property name (--name)
|
|
1223
|
+
* @param options - Property configuration
|
|
1224
|
+
*
|
|
1225
|
+
* @example Basic property with token syntax
|
|
1226
|
+
* ```tsx
|
|
1227
|
+
* function Spinner() {
|
|
1228
|
+
* useProperty('$rotation', {
|
|
1229
|
+
* syntax: '<angle>',
|
|
1230
|
+
* inherits: false,
|
|
1231
|
+
* initialValue: '0deg',
|
|
1232
|
+
* });
|
|
1233
|
+
*
|
|
1234
|
+
* return <div className="spinner" />;
|
|
1235
|
+
* }
|
|
1236
|
+
* ```
|
|
1237
|
+
*
|
|
1238
|
+
* @example Color property with token syntax (auto-sets syntax)
|
|
1239
|
+
* ```tsx
|
|
1240
|
+
* function MyComponent() {
|
|
1241
|
+
* useProperty('#theme', {
|
|
1242
|
+
* initialValue: 'red', // syntax: '<color>' is auto-set
|
|
1243
|
+
* });
|
|
1244
|
+
*
|
|
1245
|
+
* // Now --theme-color can be animated with CSS transitions
|
|
1246
|
+
* return <div style={{ '--theme-color': 'blue' } as React.CSSProperties}>Colored</div>;
|
|
1247
|
+
* }
|
|
1248
|
+
* ```
|
|
1249
|
+
*
|
|
1250
|
+
* @example Legacy format (still supported)
|
|
1251
|
+
* ```tsx
|
|
1252
|
+
* function ResizableBox() {
|
|
1253
|
+
* useProperty('--box-size', {
|
|
1254
|
+
* syntax: '<length>',
|
|
1255
|
+
* initialValue: '100px',
|
|
1256
|
+
* });
|
|
1257
|
+
*
|
|
1258
|
+
* return <div style={{ width: 'var(--box-size)' }} />;
|
|
1259
|
+
* }
|
|
1260
|
+
* ```
|
|
1261
|
+
*/
|
|
1262
|
+
function useProperty(name, options) {
|
|
1263
|
+
if (!name) {
|
|
1264
|
+
console.warn(`[Tasty] useProperty: property name is required`);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const target = getStyleTarget();
|
|
1268
|
+
if (target.mode === "ssr") {
|
|
1269
|
+
target.collector.collectInternals();
|
|
1270
|
+
const css = formatPropertyCSS(name, {
|
|
1271
|
+
syntax: options?.syntax,
|
|
1272
|
+
inherits: options?.inherits,
|
|
1273
|
+
initialValue: options?.initialValue
|
|
1274
|
+
});
|
|
1275
|
+
if (css) target.collector.collectProperty(name, css);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (target.mode === "rsc") {
|
|
1279
|
+
const css = formatPropertyCSS(name, {
|
|
1280
|
+
syntax: options?.syntax,
|
|
1281
|
+
inherits: options?.inherits,
|
|
1282
|
+
initialValue: options?.initialValue
|
|
1283
|
+
});
|
|
1284
|
+
if (css) pushRSCCSS(target.cache, `__prop:${name}`, css);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const injector = getGlobalInjector();
|
|
1288
|
+
if (injector.isPropertyDefined(name, { root: options?.root })) return;
|
|
1289
|
+
injector.property(name, {
|
|
1290
|
+
syntax: options?.syntax,
|
|
1291
|
+
inherits: options?.inherits,
|
|
1292
|
+
initialValue: options?.initialValue,
|
|
1293
|
+
root: options?.root
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
//#endregion
|
|
1297
|
+
//#region src/hooks/useFontFace.ts
|
|
1298
|
+
/**
|
|
1299
|
+
* Inject CSS @font-face rules.
|
|
1300
|
+
* Permanent — no cleanup on unmount. Deduplicates by content hash.
|
|
1301
|
+
*
|
|
1302
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
1303
|
+
*
|
|
1304
|
+
* @param family - The font-family name
|
|
1305
|
+
* @param input - Single descriptor object or array of descriptors (for multiple weights/styles)
|
|
1306
|
+
* @param options - Optional settings (e.g. Shadow DOM root)
|
|
1307
|
+
*
|
|
1308
|
+
* @example Single weight
|
|
1309
|
+
* ```tsx
|
|
1310
|
+
* function App() {
|
|
1311
|
+
* useFontFace('Brand Sans', {
|
|
1312
|
+
* src: 'url("/fonts/brand-sans.woff2") format("woff2")',
|
|
1313
|
+
* fontWeight: '400 700',
|
|
1314
|
+
* fontDisplay: 'swap',
|
|
1315
|
+
* });
|
|
1316
|
+
*
|
|
1317
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
1318
|
+
* }
|
|
1319
|
+
* ```
|
|
1320
|
+
*
|
|
1321
|
+
* @example Multiple weights
|
|
1322
|
+
* ```tsx
|
|
1323
|
+
* function App() {
|
|
1324
|
+
* useFontFace('Brand Sans', [
|
|
1325
|
+
* { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
|
|
1326
|
+
* { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
|
|
1327
|
+
* ]);
|
|
1328
|
+
*
|
|
1329
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
1330
|
+
* }
|
|
1331
|
+
* ```
|
|
1332
|
+
*/
|
|
1333
|
+
function useFontFace(family, input, options) {
|
|
1334
|
+
if (!family) return;
|
|
1335
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
1336
|
+
const target = getStyleTarget();
|
|
1337
|
+
if (target.mode === "ssr") {
|
|
1338
|
+
for (const desc of descriptors) {
|
|
1339
|
+
const hash = fontFaceContentHash(family, desc);
|
|
1340
|
+
const css = formatFontFaceRule(family, desc);
|
|
1341
|
+
target.collector.collectFontFace(hash, css);
|
|
1342
|
+
}
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (target.mode === "rsc") {
|
|
1346
|
+
for (const desc of descriptors) {
|
|
1347
|
+
const hash = fontFaceContentHash(family, desc);
|
|
1348
|
+
const css = formatFontFaceRule(family, desc);
|
|
1349
|
+
pushRSCCSS(target.cache, `__ff:${hash}`, css);
|
|
1350
|
+
}
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const injector = getGlobalInjector();
|
|
1354
|
+
for (const desc of descriptors) injector.fontFace(family, desc, { root: options?.root });
|
|
1355
|
+
}
|
|
1356
|
+
//#endregion
|
|
1357
|
+
//#region src/hooks/useCounterStyle.ts
|
|
1358
|
+
let clientCounterStyleCounter = 0;
|
|
1359
|
+
const getClientContentToName = /* @__PURE__ */ createClientState(() => /* @__PURE__ */ new Map());
|
|
1360
|
+
/**
|
|
1361
|
+
* Inject a CSS @counter-style rule and return the generated name.
|
|
1362
|
+
* Permanent — no cleanup on unmount. Deduplicates by name.
|
|
1363
|
+
*
|
|
1364
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
1365
|
+
*
|
|
1366
|
+
* @example Basic usage
|
|
1367
|
+
* ```tsx
|
|
1368
|
+
* function EmojiList() {
|
|
1369
|
+
* const styleName = useCounterStyle({
|
|
1370
|
+
* system: 'cyclic',
|
|
1371
|
+
* symbols: '"👍"',
|
|
1372
|
+
* suffix: '" "',
|
|
1373
|
+
* }, { name: 'thumbs' });
|
|
1374
|
+
*
|
|
1375
|
+
* return (
|
|
1376
|
+
* <ol style={{ listStyleType: styleName }}>
|
|
1377
|
+
* <li>First</li>
|
|
1378
|
+
* <li>Second</li>
|
|
1379
|
+
* </ol>
|
|
1380
|
+
* );
|
|
1381
|
+
* }
|
|
1382
|
+
* ```
|
|
1383
|
+
*
|
|
1384
|
+
*/
|
|
1385
|
+
function useCounterStyle(descriptors, options) {
|
|
1386
|
+
if (!descriptors || !descriptors.system) return "";
|
|
1387
|
+
const target = getStyleTarget();
|
|
1388
|
+
if (target.mode === "ssr") {
|
|
1389
|
+
const actualName = target.collector.allocateCounterStyleName(options?.name);
|
|
1390
|
+
const css = formatCounterStyleRule(actualName, descriptors);
|
|
1391
|
+
target.collector.collectCounterStyle(actualName, css);
|
|
1392
|
+
return actualName;
|
|
1393
|
+
}
|
|
1394
|
+
if (target.mode === "rsc") {
|
|
1395
|
+
const serializedContent = JSON.stringify(descriptors);
|
|
1396
|
+
const key = `__cs:${options?.name ?? ""}:${serializedContent}`;
|
|
1397
|
+
const existingName = target.cache.generatedNames.get(key);
|
|
1398
|
+
if (existingName) return existingName;
|
|
1399
|
+
const actualName = options?.name ?? makeCounterStyleName(getNamePrefix(), hashString(serializedContent));
|
|
1400
|
+
const css = formatCounterStyleRule(actualName, descriptors);
|
|
1401
|
+
pushRSCCSS(target.cache, key, css);
|
|
1402
|
+
target.cache.generatedNames.set(key, actualName);
|
|
1403
|
+
return actualName;
|
|
1404
|
+
}
|
|
1405
|
+
const contentToName = getClientContentToName(options?.root ?? document);
|
|
1406
|
+
const serializedContent = JSON.stringify(descriptors);
|
|
1407
|
+
const cacheKey = `${options?.name ?? ""}:${serializedContent}`;
|
|
1408
|
+
const existingName = contentToName.get(cacheKey);
|
|
1409
|
+
if (existingName) return existingName;
|
|
1410
|
+
const name = options?.name ?? makeCounterStyleName(getNamePrefix(), String(clientCounterStyleCounter++));
|
|
1411
|
+
contentToName.set(cacheKey, name);
|
|
1412
|
+
getGlobalInjector().counterStyle(name, descriptors, { root: options?.root });
|
|
1413
|
+
return name;
|
|
1414
|
+
}
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/hooks/useFunction.ts
|
|
1417
|
+
/**
|
|
1418
|
+
* Register a CSS @function (custom function).
|
|
1419
|
+
*
|
|
1420
|
+
* @function rules are global and persistent once defined. The hook ensures the
|
|
1421
|
+
* function is only registered once per root (deduplicated by function name).
|
|
1422
|
+
*
|
|
1423
|
+
* Accepts tasty token syntax for the function name:
|
|
1424
|
+
* - `$$name` → defines `--name` (matches the call site `$$name(...)`)
|
|
1425
|
+
* - `$name` / `--name` → also accepted
|
|
1426
|
+
*
|
|
1427
|
+
* Works in all environments: client, SSR with collector, and React Server Components.
|
|
1428
|
+
*
|
|
1429
|
+
* @param name - The function name token (`$$name`, `$name`, or `--name`)
|
|
1430
|
+
* @param definition - Function definition (args, returns, result, local vars)
|
|
1431
|
+
*
|
|
1432
|
+
* Call the function through the Tasty DSL, not a raw `style` prop: an inline
|
|
1433
|
+
* `style` value reaches the browser unparsed, so the `$$name(...)` sugar is never
|
|
1434
|
+
* expanded and `polyfills.functions` cannot rewrite it either.
|
|
1435
|
+
*
|
|
1436
|
+
* @example
|
|
1437
|
+
* ```tsx
|
|
1438
|
+
* const Box = tasty({ styles: { marginTop: '$$negative(10px)' } });
|
|
1439
|
+
*
|
|
1440
|
+
* function Layout() {
|
|
1441
|
+
* useFunction('$$negative', { args: ['$value'], result: '(-1 * $value)' });
|
|
1442
|
+
* return <Box />;
|
|
1443
|
+
* }
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
function useFunction(name, definition, options) {
|
|
1447
|
+
if (!name) {
|
|
1448
|
+
console.warn(`[Tasty] useFunction: function name is required`);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (isFunctionsPolyfillEnabled()) {
|
|
1452
|
+
registerFunctionPolyfill(name, definition);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const target = getStyleTarget();
|
|
1456
|
+
if (target.mode === "ssr") {
|
|
1457
|
+
target.collector.collectInternals();
|
|
1458
|
+
const css = formatFunctionRule(name, definition);
|
|
1459
|
+
if (css) target.collector.collectFunction(parseFunctionName(name), css);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (target.mode === "rsc") {
|
|
1463
|
+
const css = formatFunctionRule(name, definition);
|
|
1464
|
+
if (css) pushRSCCSS(target.cache, `__func:${parseFunctionName(name)}`, css);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
getGlobalInjector().func(name, definition, { root: options?.root });
|
|
1468
|
+
}
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/batch-provider.tsx
|
|
1471
|
+
/**
|
|
1472
|
+
* Opens a *batch window* for the commit it renders in, so `batchInjection`
|
|
1473
|
+
* can defer stylesheet writes without ever letting a layout effect measure an
|
|
1474
|
+
* unstyled element.
|
|
1475
|
+
*
|
|
1476
|
+
* Every `insertRule()` on a live sheet invalidates style for that sheet's
|
|
1477
|
+
* scope. When components inject during React's render phase while others read
|
|
1478
|
+
* layout in the same pass, the two interleave and the browser recalculates
|
|
1479
|
+
* style between every injection. Batching collapses that into one invalidation
|
|
1480
|
+
* per flush — but only if the flush happens before anything can measure.
|
|
1481
|
+
*
|
|
1482
|
+
* ```
|
|
1483
|
+
* provider renders -> window OPEN
|
|
1484
|
+
* children render -> injections queued
|
|
1485
|
+
* provider insertionEffect -> FLUSH, window CLOSED
|
|
1486
|
+
* layout effects run -> rules are in the sheet
|
|
1487
|
+
* ```
|
|
1488
|
+
*
|
|
1489
|
+
* `useInsertionEffect` runs in React's mutation phase, after every render in
|
|
1490
|
+
* the commit and before any `useLayoutEffect` — which is exactly why React
|
|
1491
|
+
* added it for CSS-in-JS libraries. Effects fire child-first, so this
|
|
1492
|
+
* provider's runs after every descendant's and still before all layout
|
|
1493
|
+
* effects.
|
|
1494
|
+
*
|
|
1495
|
+
* A commit that does not re-render this provider gets no window, and those
|
|
1496
|
+
* injections are written synchronously instead. That is the point: turning
|
|
1497
|
+
* `batchInjection: true` on can only ever make injection cheaper, never make a
|
|
1498
|
+
* measurement wrong. It also means batching applies to commits this provider
|
|
1499
|
+
* takes part in, so mount it as high in the tree as you can.
|
|
1500
|
+
*
|
|
1501
|
+
* Requires `configure({ batchInjection: true })`; without it this component
|
|
1502
|
+
* only renders its children. `batchInjection: 'always'` does not need the
|
|
1503
|
+
* provider at all — see that option's docs for the trade-off it accepts.
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* ```tsx
|
|
1507
|
+
* configure({ batchInjection: true });
|
|
1508
|
+
*
|
|
1509
|
+
* createRoot(el).render(
|
|
1510
|
+
* <TastyBatchProvider>
|
|
1511
|
+
* <App />
|
|
1512
|
+
* </TastyBatchProvider>,
|
|
1513
|
+
* );
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
function TastyBatchProvider({ children }) {
|
|
1517
|
+
openBatchWindow();
|
|
1518
|
+
useInsertionEffect(() => {
|
|
1519
|
+
closeBatchWindow();
|
|
1520
|
+
});
|
|
1521
|
+
return children;
|
|
1522
|
+
}
|
|
1523
|
+
//#endregion
|
|
1524
|
+
export { computeStyles as _, useProperty as a, useGlobalStyles as c, Element as d, tasty as f, filterBaseProps as g, _modAttrs as h, useFontFace as i, useStyles as l, processTokens as m, useFunction as n, useKeyframes as o, getDisplayName as p, useCounterStyle as r, useRawCSS as s, TastyBatchProvider as t, getTastySSRContext as u };
|
|
1525
|
+
|
|
1526
|
+
//# sourceMappingURL=react-runtime-so_X_yMo.js.map
|