@tenphi/tasty 0.0.0-snapshot.056b911
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/LICENSE +21 -0
- package/README.md +635 -0
- package/dist/_virtual/_rolldown/runtime.js +7 -0
- package/dist/chunks/cacheKey.d.ts +1 -0
- package/dist/chunks/cacheKey.js +77 -0
- package/dist/chunks/cacheKey.js.map +1 -0
- package/dist/chunks/definitions.d.ts +37 -0
- package/dist/chunks/definitions.js +258 -0
- package/dist/chunks/definitions.js.map +1 -0
- package/dist/chunks/index.d.ts +1 -0
- package/dist/chunks/renderChunk.d.ts +1 -0
- package/dist/chunks/renderChunk.js +59 -0
- package/dist/chunks/renderChunk.js.map +1 -0
- package/dist/compute-styles.d.ts +31 -0
- package/dist/compute-styles.js +335 -0
- package/dist/compute-styles.js.map +1 -0
- package/dist/config.d.ts +409 -0
- package/dist/config.js +584 -0
- package/dist/config.js.map +1 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +27 -0
- package/dist/counter-style/index.js +51 -0
- package/dist/counter-style/index.js.map +1 -0
- package/dist/debug.d.ts +89 -0
- package/dist/debug.js +453 -0
- package/dist/debug.js.map +1 -0
- package/dist/font-face/index.js +63 -0
- package/dist/font-face/index.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/useCounterStyle.d.ts +36 -0
- package/dist/hooks/useCounterStyle.js +64 -0
- package/dist/hooks/useCounterStyle.js.map +1 -0
- package/dist/hooks/useFontFace.d.ts +45 -0
- package/dist/hooks/useFontFace.js +66 -0
- package/dist/hooks/useFontFace.js.map +1 -0
- package/dist/hooks/useGlobalStyles.d.ts +46 -0
- package/dist/hooks/useGlobalStyles.js +88 -0
- package/dist/hooks/useGlobalStyles.js.map +1 -0
- package/dist/hooks/useKeyframes.d.ts +58 -0
- package/dist/hooks/useKeyframes.js +54 -0
- package/dist/hooks/useKeyframes.js.map +1 -0
- package/dist/hooks/useProperty.d.ts +81 -0
- package/dist/hooks/useProperty.js +96 -0
- package/dist/hooks/useProperty.js.map +1 -0
- package/dist/hooks/useRawCSS.d.ts +22 -0
- package/dist/hooks/useRawCSS.js +103 -0
- package/dist/hooks/useRawCSS.js.map +1 -0
- package/dist/hooks/useStyles.d.ts +40 -0
- package/dist/hooks/useStyles.js +31 -0
- package/dist/hooks/useStyles.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +36 -0
- package/dist/injector/index.d.ts +182 -0
- package/dist/injector/index.js +185 -0
- package/dist/injector/index.js.map +1 -0
- package/dist/injector/injector.d.ts +193 -0
- package/dist/injector/injector.js +564 -0
- package/dist/injector/injector.js.map +1 -0
- package/dist/injector/sheet-manager.d.ts +132 -0
- package/dist/injector/sheet-manager.js +698 -0
- package/dist/injector/sheet-manager.js.map +1 -0
- package/dist/injector/types.d.ts +228 -0
- package/dist/keyframes/index.js +206 -0
- package/dist/keyframes/index.js.map +1 -0
- package/dist/parser/classify.js +319 -0
- package/dist/parser/classify.js.map +1 -0
- package/dist/parser/const.js +60 -0
- package/dist/parser/const.js.map +1 -0
- package/dist/parser/lru.js +109 -0
- package/dist/parser/lru.js.map +1 -0
- package/dist/parser/parser.d.ts +25 -0
- package/dist/parser/parser.js +115 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/tokenizer.js +69 -0
- package/dist/parser/tokenizer.js.map +1 -0
- package/dist/parser/types.d.ts +51 -0
- package/dist/parser/types.js +46 -0
- package/dist/parser/types.js.map +1 -0
- package/dist/pipeline/conditions.d.ts +134 -0
- package/dist/pipeline/conditions.js +406 -0
- package/dist/pipeline/conditions.js.map +1 -0
- package/dist/pipeline/exclusive.js +230 -0
- package/dist/pipeline/exclusive.js.map +1 -0
- package/dist/pipeline/index.d.ts +55 -0
- package/dist/pipeline/index.js +708 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/materialize.js +1103 -0
- package/dist/pipeline/materialize.js.map +1 -0
- package/dist/pipeline/parseStateKey.d.ts +15 -0
- package/dist/pipeline/parseStateKey.js +446 -0
- package/dist/pipeline/parseStateKey.js.map +1 -0
- package/dist/pipeline/simplify.js +515 -0
- package/dist/pipeline/simplify.js.map +1 -0
- package/dist/pipeline/warnings.js +18 -0
- package/dist/pipeline/warnings.js.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/okhsl-plugin.d.ts +35 -0
- package/dist/plugins/okhsl-plugin.js +97 -0
- package/dist/plugins/okhsl-plugin.js.map +1 -0
- package/dist/plugins/types.d.ts +87 -0
- package/dist/properties/index.js +222 -0
- package/dist/properties/index.js.map +1 -0
- package/dist/properties/property-type-resolver.d.ts +24 -0
- package/dist/properties/property-type-resolver.js +90 -0
- package/dist/properties/property-type-resolver.js.map +1 -0
- package/dist/rsc-cache.js +81 -0
- package/dist/rsc-cache.js.map +1 -0
- package/dist/ssr/astro-client.d.ts +1 -0
- package/dist/ssr/astro-client.js +24 -0
- package/dist/ssr/astro-client.js.map +1 -0
- package/dist/ssr/astro-middleware.d.ts +15 -0
- package/dist/ssr/astro-middleware.js +19 -0
- package/dist/ssr/astro-middleware.js.map +1 -0
- package/dist/ssr/astro.d.ts +106 -0
- package/dist/ssr/astro.js +149 -0
- package/dist/ssr/astro.js.map +1 -0
- package/dist/ssr/async-storage.d.ts +17 -0
- package/dist/ssr/async-storage.js +44 -0
- package/dist/ssr/async-storage.js.map +1 -0
- package/dist/ssr/collect-auto-properties.js +58 -0
- package/dist/ssr/collect-auto-properties.js.map +1 -0
- package/dist/ssr/collector.d.ts +102 -0
- package/dist/ssr/collector.js +227 -0
- package/dist/ssr/collector.js.map +1 -0
- package/dist/ssr/context.js +16 -0
- package/dist/ssr/context.js.map +1 -0
- package/dist/ssr/format-global-rules.js +22 -0
- package/dist/ssr/format-global-rules.js.map +1 -0
- package/dist/ssr/format-keyframes.js +69 -0
- package/dist/ssr/format-keyframes.js.map +1 -0
- package/dist/ssr/format-property.js +49 -0
- package/dist/ssr/format-property.js.map +1 -0
- package/dist/ssr/format-rules.js +73 -0
- package/dist/ssr/format-rules.js.map +1 -0
- package/dist/ssr/hydrate.d.ts +22 -0
- package/dist/ssr/hydrate.js +49 -0
- package/dist/ssr/hydrate.js.map +1 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.js +10 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/ssr/next.d.ts +45 -0
- package/dist/ssr/next.js +75 -0
- package/dist/ssr/next.js.map +1 -0
- package/dist/ssr/ssr-collector-ref.js +29 -0
- package/dist/ssr/ssr-collector-ref.js.map +1 -0
- package/dist/states/index.d.ts +49 -0
- package/dist/states/index.js +170 -0
- package/dist/states/index.js.map +1 -0
- package/dist/static/index.d.ts +5 -0
- package/dist/static/index.js +4 -0
- package/dist/static/inject.d.ts +5 -0
- package/dist/static/inject.js +17 -0
- package/dist/static/inject.js.map +1 -0
- package/dist/static/tastyStatic.d.ts +46 -0
- package/dist/static/tastyStatic.js +30 -0
- package/dist/static/tastyStatic.js.map +1 -0
- package/dist/static/types.d.ts +49 -0
- package/dist/static/types.js +24 -0
- package/dist/static/types.js.map +1 -0
- package/dist/styles/border.d.ts +25 -0
- package/dist/styles/border.js +120 -0
- package/dist/styles/border.js.map +1 -0
- package/dist/styles/color.d.ts +14 -0
- package/dist/styles/color.js +26 -0
- package/dist/styles/color.js.map +1 -0
- package/dist/styles/const.js +17 -0
- package/dist/styles/const.js.map +1 -0
- package/dist/styles/createStyle.js +79 -0
- package/dist/styles/createStyle.js.map +1 -0
- package/dist/styles/dimension.js +109 -0
- package/dist/styles/dimension.js.map +1 -0
- package/dist/styles/directional.js +133 -0
- package/dist/styles/directional.js.map +1 -0
- package/dist/styles/display.d.ts +30 -0
- package/dist/styles/display.js +73 -0
- package/dist/styles/display.js.map +1 -0
- package/dist/styles/fade.d.ts +15 -0
- package/dist/styles/fade.js +62 -0
- package/dist/styles/fade.js.map +1 -0
- package/dist/styles/fill.d.ts +42 -0
- package/dist/styles/fill.js +51 -0
- package/dist/styles/fill.js.map +1 -0
- package/dist/styles/flow.d.ts +16 -0
- package/dist/styles/flow.js +12 -0
- package/dist/styles/flow.js.map +1 -0
- package/dist/styles/gap.d.ts +31 -0
- package/dist/styles/gap.js +38 -0
- package/dist/styles/gap.js.map +1 -0
- package/dist/styles/height.d.ts +17 -0
- package/dist/styles/height.js +19 -0
- package/dist/styles/height.js.map +1 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.js +8 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/inset.d.ts +24 -0
- package/dist/styles/inset.js +34 -0
- package/dist/styles/inset.js.map +1 -0
- package/dist/styles/list.d.ts +16 -0
- package/dist/styles/list.js +100 -0
- package/dist/styles/list.js.map +1 -0
- package/dist/styles/margin.d.ts +24 -0
- package/dist/styles/margin.js +32 -0
- package/dist/styles/margin.js.map +1 -0
- package/dist/styles/outline.d.ts +29 -0
- package/dist/styles/outline.js +55 -0
- package/dist/styles/outline.js.map +1 -0
- package/dist/styles/padding.d.ts +24 -0
- package/dist/styles/padding.js +32 -0
- package/dist/styles/padding.js.map +1 -0
- package/dist/styles/placement.d.ts +37 -0
- package/dist/styles/placement.js +74 -0
- package/dist/styles/placement.js.map +1 -0
- package/dist/styles/predefined.d.ts +71 -0
- package/dist/styles/predefined.js +237 -0
- package/dist/styles/predefined.js.map +1 -0
- package/dist/styles/preset.d.ts +52 -0
- package/dist/styles/preset.js +127 -0
- package/dist/styles/preset.js.map +1 -0
- package/dist/styles/radius.d.ts +12 -0
- package/dist/styles/radius.js +83 -0
- package/dist/styles/radius.js.map +1 -0
- package/dist/styles/scrollMargin.d.ts +24 -0
- package/dist/styles/scrollMargin.js +32 -0
- package/dist/styles/scrollMargin.js.map +1 -0
- package/dist/styles/scrollbar.d.ts +25 -0
- package/dist/styles/scrollbar.js +51 -0
- package/dist/styles/scrollbar.js.map +1 -0
- package/dist/styles/shadow.d.ts +14 -0
- package/dist/styles/shadow.js +25 -0
- package/dist/styles/shadow.js.map +1 -0
- package/dist/styles/shared.js +17 -0
- package/dist/styles/shared.js.map +1 -0
- package/dist/styles/transition.d.ts +14 -0
- package/dist/styles/transition.js +159 -0
- package/dist/styles/transition.js.map +1 -0
- package/dist/styles/types.d.ts +564 -0
- package/dist/styles/width.d.ts +17 -0
- package/dist/styles/width.js +19 -0
- package/dist/styles/width.js.map +1 -0
- package/dist/tasty.d.ts +134 -0
- package/dist/tasty.js +243 -0
- package/dist/tasty.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/utils/cache-wrapper.js +21 -0
- package/dist/utils/cache-wrapper.js.map +1 -0
- package/dist/utils/case-converter.js +8 -0
- package/dist/utils/case-converter.js.map +1 -0
- package/dist/utils/color-math.d.ts +46 -0
- package/dist/utils/color-math.js +749 -0
- package/dist/utils/color-math.js.map +1 -0
- package/dist/utils/color-space.d.ts +5 -0
- package/dist/utils/color-space.js +228 -0
- package/dist/utils/color-space.js.map +1 -0
- package/dist/utils/colors.d.ts +5 -0
- package/dist/utils/colors.js +10 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/css-types.d.ts +7 -0
- package/dist/utils/deps-equal.js +15 -0
- package/dist/utils/deps-equal.js.map +1 -0
- package/dist/utils/dotize.d.ts +26 -0
- package/dist/utils/dotize.js +122 -0
- package/dist/utils/dotize.js.map +1 -0
- package/dist/utils/filter-base-props.d.ts +15 -0
- package/dist/utils/filter-base-props.js +45 -0
- package/dist/utils/filter-base-props.js.map +1 -0
- package/dist/utils/get-display-name.d.ts +7 -0
- package/dist/utils/get-display-name.js +10 -0
- package/dist/utils/get-display-name.js.map +1 -0
- package/dist/utils/has-keys.js +13 -0
- package/dist/utils/has-keys.js.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/is-dev-env.js +19 -0
- package/dist/utils/is-dev-env.js.map +1 -0
- package/dist/utils/is-valid-element-type.js +15 -0
- package/dist/utils/is-valid-element-type.js.map +1 -0
- package/dist/utils/merge-styles.d.ts +7 -0
- package/dist/utils/merge-styles.js +145 -0
- package/dist/utils/merge-styles.js.map +1 -0
- package/dist/utils/mod-attrs.d.ts +6 -0
- package/dist/utils/mod-attrs.js +20 -0
- package/dist/utils/mod-attrs.js.map +1 -0
- package/dist/utils/process-tokens.d.ts +17 -0
- package/dist/utils/process-tokens.js +83 -0
- package/dist/utils/process-tokens.js.map +1 -0
- package/dist/utils/resolve-recipes.d.ts +17 -0
- package/dist/utils/resolve-recipes.js +146 -0
- package/dist/utils/resolve-recipes.js.map +1 -0
- package/dist/utils/selector-transform.js +32 -0
- package/dist/utils/selector-transform.js.map +1 -0
- package/dist/utils/string.js +8 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/utils/styles.d.ts +99 -0
- package/dist/utils/styles.js +220 -0
- package/dist/utils/styles.js.map +1 -0
- package/dist/utils/typography.d.ts +58 -0
- package/dist/utils/typography.js +51 -0
- package/dist/utils/typography.js.map +1 -0
- package/dist/utils/warnings.d.ts +16 -0
- package/dist/utils/warnings.js +16 -0
- package/dist/utils/warnings.js.map +1 -0
- package/dist/zero/babel.d.ts +195 -0
- package/dist/zero/babel.js +456 -0
- package/dist/zero/babel.js.map +1 -0
- package/dist/zero/css-writer.d.ts +45 -0
- package/dist/zero/css-writer.js +73 -0
- package/dist/zero/css-writer.js.map +1 -0
- package/dist/zero/extractor.d.ts +24 -0
- package/dist/zero/extractor.js +266 -0
- package/dist/zero/extractor.js.map +1 -0
- package/dist/zero/index.d.ts +3 -0
- package/dist/zero/index.js +3 -0
- package/dist/zero/next.d.ts +86 -0
- package/dist/zero/next.js +143 -0
- package/dist/zero/next.js.map +1 -0
- package/docs/PIPELINE.md +519 -0
- package/docs/README.md +31 -0
- package/docs/adoption.md +298 -0
- package/docs/comparison.md +419 -0
- package/docs/configuration.md +389 -0
- package/docs/debug.md +318 -0
- package/docs/design-system.md +436 -0
- package/docs/dsl.md +688 -0
- package/docs/getting-started.md +217 -0
- package/docs/injector.md +544 -0
- package/docs/methodology.md +616 -0
- package/docs/react-api.md +557 -0
- package/docs/ssr.md +440 -0
- package/docs/styles.md +596 -0
- package/docs/tasty-static.md +532 -0
- package/package.json +221 -0
- package/tasty.config.ts +14 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/ssr/format-rules.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve selectors for a rule, applying className-based specificity doubling
|
|
4
|
+
* and rootPrefix handling. Mirrors the logic in StyleInjector.inject().
|
|
5
|
+
*/
|
|
6
|
+
function resolveSelector(rule, className) {
|
|
7
|
+
let selector = rule.selector;
|
|
8
|
+
if (rule.needsClassName) {
|
|
9
|
+
const selectorParts = selector ? selector.split("|||") : [""];
|
|
10
|
+
const classPrefix = `.${className}.${className}`;
|
|
11
|
+
selector = selectorParts.map((part) => {
|
|
12
|
+
const classSelector = part ? `${classPrefix}${part}` : classPrefix;
|
|
13
|
+
if (rule.rootPrefix) return `${rule.rootPrefix} ${classSelector}`;
|
|
14
|
+
return classSelector;
|
|
15
|
+
}).join(", ");
|
|
16
|
+
}
|
|
17
|
+
return selector;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Group rules by selector + at-rules + startingStyle and merge their declarations.
|
|
21
|
+
* Mirrors the grouping logic in SheetManager.insertRule().
|
|
22
|
+
*/
|
|
23
|
+
function groupRules(rules) {
|
|
24
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
25
|
+
const order = [];
|
|
26
|
+
const atKey = (at) => at && at.length ? at.join("|") : "";
|
|
27
|
+
for (const r of rules) {
|
|
28
|
+
const key = `${atKey(r.atRules)}||${r.selector}||${r.startingStyle ? "1" : "0"}`;
|
|
29
|
+
const existing = groupMap.get(key);
|
|
30
|
+
if (existing) existing.declarations = existing.declarations ? `${existing.declarations} ${r.declarations}` : r.declarations;
|
|
31
|
+
else {
|
|
32
|
+
groupMap.set(key, {
|
|
33
|
+
selector: r.selector,
|
|
34
|
+
atRules: r.atRules,
|
|
35
|
+
startingStyle: r.startingStyle,
|
|
36
|
+
declarations: r.declarations
|
|
37
|
+
});
|
|
38
|
+
order.push(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return order.map((key) => groupMap.get(key));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format an array of StyleResult rules into a CSS text string.
|
|
45
|
+
*
|
|
46
|
+
* Applies className-based specificity doubling (.cls.cls),
|
|
47
|
+
* groups rules by selector + at-rules, and wraps with at-rule blocks.
|
|
48
|
+
*
|
|
49
|
+
* Produces the same CSS text as SheetManager.insertRule() would insert
|
|
50
|
+
* into the DOM, but as a plain string suitable for SSR output.
|
|
51
|
+
*/
|
|
52
|
+
function formatRules(rules, className) {
|
|
53
|
+
if (rules.length === 0) return "";
|
|
54
|
+
const grouped = groupRules(rules.map((rule) => ({
|
|
55
|
+
selector: resolveSelector(rule, className),
|
|
56
|
+
declarations: rule.declarations,
|
|
57
|
+
atRules: rule.atRules,
|
|
58
|
+
startingStyle: rule.startingStyle
|
|
59
|
+
})));
|
|
60
|
+
const cssRules = [];
|
|
61
|
+
for (const rule of grouped) {
|
|
62
|
+
const innerContent = rule.startingStyle ? `@starting-style { ${rule.declarations} }` : rule.declarations;
|
|
63
|
+
const baseRule = `${rule.selector} { ${innerContent} }`;
|
|
64
|
+
let fullRule = baseRule;
|
|
65
|
+
if (rule.atRules && rule.atRules.length > 0) fullRule = rule.atRules.reduce((css, atRule) => `${atRule} { ${css} }`, baseRule);
|
|
66
|
+
cssRules.push(fullRule);
|
|
67
|
+
}
|
|
68
|
+
return cssRules.join("\n");
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { formatRules };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=format-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-rules.js","names":[],"sources":["../../src/ssr/format-rules.ts"],"sourcesContent":["/**\n * Shared CSS rule formatting utility.\n *\n * Extracted from SheetManager to allow both the DOM-based injector (client)\n * and the ServerStyleCollector (server) to produce identical CSS text\n * from StyleResult arrays.\n */\n\nimport type { StyleResult } from '../pipeline';\n\n/**\n * Resolve selectors for a rule, applying className-based specificity doubling\n * and rootPrefix handling. Mirrors the logic in StyleInjector.inject().\n */\nfunction resolveSelector(rule: StyleResult, className: string): string {\n let selector = rule.selector;\n\n if (rule.needsClassName) {\n const selectorParts = selector ? selector.split('|||') : [''];\n const classPrefix = `.${className}.${className}`;\n\n selector = selectorParts\n .map((part) => {\n const classSelector = part ? `${classPrefix}${part}` : classPrefix;\n\n if (rule.rootPrefix) {\n return `${rule.rootPrefix} ${classSelector}`;\n }\n return classSelector;\n })\n .join(', ');\n }\n\n return selector;\n}\n\ninterface GroupedRule {\n selector: string;\n declarations: string;\n atRules?: string[];\n startingStyle?: boolean;\n}\n\n/**\n * Group rules by selector + at-rules + startingStyle and merge their declarations.\n * Mirrors the grouping logic in SheetManager.insertRule().\n */\nfunction groupRules(rules: GroupedRule[]): GroupedRule[] {\n const groupMap = new Map<string, GroupedRule>();\n const order: string[] = [];\n\n const atKey = (at?: string[]) => (at && at.length ? at.join('|') : '');\n\n for (const r of rules) {\n const key = `${atKey(r.atRules)}||${r.selector}||${r.startingStyle ? '1' : '0'}`;\n const existing = groupMap.get(key);\n if (existing) {\n existing.declarations = existing.declarations\n ? `${existing.declarations} ${r.declarations}`\n : r.declarations;\n } else {\n groupMap.set(key, {\n selector: r.selector,\n atRules: r.atRules,\n startingStyle: r.startingStyle,\n declarations: r.declarations,\n });\n order.push(key);\n }\n }\n\n return order.map((key) => groupMap.get(key)!);\n}\n\n/**\n * Format an array of StyleResult rules into a CSS text string.\n *\n * Applies className-based specificity doubling (.cls.cls),\n * groups rules by selector + at-rules, and wraps with at-rule blocks.\n *\n * Produces the same CSS text as SheetManager.insertRule() would insert\n * into the DOM, but as a plain string suitable for SSR output.\n */\nexport function formatRules(rules: StyleResult[], className: string): string {\n if (rules.length === 0) return '';\n\n const resolvedRules = rules.map((rule) => ({\n selector: resolveSelector(rule, className),\n declarations: rule.declarations,\n atRules: rule.atRules,\n startingStyle: rule.startingStyle,\n }));\n\n const grouped = groupRules(resolvedRules);\n const cssRules: string[] = [];\n\n for (const rule of grouped) {\n const innerContent = rule.startingStyle\n ? `@starting-style { ${rule.declarations} }`\n : rule.declarations;\n const baseRule = `${rule.selector} { ${innerContent} }`;\n\n let fullRule = baseRule;\n if (rule.atRules && rule.atRules.length > 0) {\n fullRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n baseRule,\n );\n }\n\n cssRules.push(fullRule);\n }\n\n return cssRules.join('\\n');\n}\n"],"mappings":";;;;;AAcA,SAAS,gBAAgB,MAAmB,WAA2B;CACrE,IAAI,WAAW,KAAK;AAEpB,KAAI,KAAK,gBAAgB;EACvB,MAAM,gBAAgB,WAAW,SAAS,MAAM,MAAM,GAAG,CAAC,GAAG;EAC7D,MAAM,cAAc,IAAI,UAAU,GAAG;AAErC,aAAW,cACR,KAAK,SAAS;GACb,MAAM,gBAAgB,OAAO,GAAG,cAAc,SAAS;AAEvD,OAAI,KAAK,WACP,QAAO,GAAG,KAAK,WAAW,GAAG;AAE/B,UAAO;IACP,CACD,KAAK,KAAK;;AAGf,QAAO;;;;;;AAcT,SAAS,WAAW,OAAqC;CACvD,MAAM,2BAAW,IAAI,KAA0B;CAC/C,MAAM,QAAkB,EAAE;CAE1B,MAAM,SAAS,OAAmB,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,GAAG;AAEnE,MAAK,MAAM,KAAK,OAAO;EACrB,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,gBAAgB,MAAM;EAC3E,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,MAAI,SACF,UAAS,eAAe,SAAS,eAC7B,GAAG,SAAS,aAAa,GAAG,EAAE,iBAC9B,EAAE;OACD;AACL,YAAS,IAAI,KAAK;IAChB,UAAU,EAAE;IACZ,SAAS,EAAE;IACX,eAAe,EAAE;IACjB,cAAc,EAAE;IACjB,CAAC;AACF,SAAM,KAAK,IAAI;;;AAInB,QAAO,MAAM,KAAK,QAAQ,SAAS,IAAI,IAAI,CAAE;;;;;;;;;;;AAY/C,SAAgB,YAAY,OAAsB,WAA2B;AAC3E,KAAI,MAAM,WAAW,EAAG,QAAO;CAS/B,MAAM,UAAU,WAPM,MAAM,KAAK,UAAU;EACzC,UAAU,gBAAgB,MAAM,UAAU;EAC1C,cAAc,KAAK;EACnB,SAAS,KAAK;EACd,eAAe,KAAK;EACrB,EAAE,CAEsC;CACzC,MAAM,WAAqB,EAAE;AAE7B,MAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,eAAe,KAAK,gBACtB,qBAAqB,KAAK,aAAa,MACvC,KAAK;EACT,MAAM,WAAW,GAAG,KAAK,SAAS,KAAK,aAAa;EAEpD,IAAI,WAAW;AACf,MAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,EACxC,YAAW,KAAK,QAAQ,QACrB,KAAK,WAAW,GAAG,OAAO,KAAK,IAAI,KACpC,SACD;AAGH,WAAS,KAAK,SAAS;;AAGzB,QAAO,SAAS,KAAK,KAAK"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SSRCacheState } from "./collector.js";
|
|
2
|
+
|
|
3
|
+
//#region src/ssr/hydrate.d.ts
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
__TASTY_SSR_CACHE__?: SSRCacheState;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pre-populate the client-side style cache from the server's SSR state.
|
|
11
|
+
*
|
|
12
|
+
* Call this before ReactDOM.hydrateRoot() or ensure it runs before
|
|
13
|
+
* any tasty() component renders on the client.
|
|
14
|
+
*
|
|
15
|
+
* When called without arguments, reads state from:
|
|
16
|
+
* 1. `window.__TASTY_SSR_CACHE__` (streaming — populated by inline scripts)
|
|
17
|
+
* 2. `<script data-tasty-cache>` (non-streaming — JSON payload)
|
|
18
|
+
*/
|
|
19
|
+
declare function hydrateTastyCache(state?: SSRCacheState): void;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { hydrateTastyCache };
|
|
22
|
+
//# sourceMappingURL=hydrate.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getGlobalInjector } from "../config.js";
|
|
2
|
+
//#region src/ssr/hydrate.ts
|
|
3
|
+
/**
|
|
4
|
+
* Client-side cache hydration for SSR.
|
|
5
|
+
*
|
|
6
|
+
* Pre-populates the client injector's cacheKeyToClassName map from the
|
|
7
|
+
* server's serialized state. This ensures that useStyles() returns
|
|
8
|
+
* cache hits during hydration, skipping the entire rendering pipeline.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Pre-populate the client-side style cache from the server's SSR state.
|
|
12
|
+
*
|
|
13
|
+
* Call this before ReactDOM.hydrateRoot() or ensure it runs before
|
|
14
|
+
* any tasty() component renders on the client.
|
|
15
|
+
*
|
|
16
|
+
* When called without arguments, reads state from:
|
|
17
|
+
* 1. `window.__TASTY_SSR_CACHE__` (streaming — populated by inline scripts)
|
|
18
|
+
* 2. `<script data-tasty-cache>` (non-streaming — JSON payload)
|
|
19
|
+
*/
|
|
20
|
+
function hydrateTastyCache(state) {
|
|
21
|
+
if (typeof document === "undefined") return;
|
|
22
|
+
if (!state) {
|
|
23
|
+
state = (typeof window !== "undefined" ? window.__TASTY_SSR_CACHE__ : null) ?? void 0;
|
|
24
|
+
if (!state) {
|
|
25
|
+
const script = document.querySelector("script[data-tasty-cache]");
|
|
26
|
+
if (script) try {
|
|
27
|
+
state = JSON.parse(script.textContent);
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!state) return;
|
|
34
|
+
const registry = getGlobalInjector()._sheetManager.getRegistry(document);
|
|
35
|
+
registry.classCounter = Math.max(registry.classCounter, state.classCounter);
|
|
36
|
+
for (const [cacheKey, className] of Object.entries(state.entries)) {
|
|
37
|
+
registry.cacheKeyToClassName.set(cacheKey, className);
|
|
38
|
+
registry.rules.set(className, {
|
|
39
|
+
className,
|
|
40
|
+
ruleIndex: -2,
|
|
41
|
+
sheetIndex: -2
|
|
42
|
+
});
|
|
43
|
+
registry.refCounts.set(className, 0);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { hydrateTastyCache };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=hydrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydrate.js","names":[],"sources":["../../src/ssr/hydrate.ts"],"sourcesContent":["/**\n * Client-side cache hydration for SSR.\n *\n * Pre-populates the client injector's cacheKeyToClassName map from the\n * server's serialized state. This ensures that useStyles() returns\n * cache hits during hydration, skipping the entire rendering pipeline.\n */\n\nimport { getGlobalInjector } from '../config';\nimport type { SSRCacheState } from './collector';\n\ndeclare global {\n interface Window {\n __TASTY_SSR_CACHE__?: SSRCacheState;\n }\n}\n\n/**\n * Pre-populate the client-side style cache from the server's SSR state.\n *\n * Call this before ReactDOM.hydrateRoot() or ensure it runs before\n * any tasty() component renders on the client.\n *\n * When called without arguments, reads state from:\n * 1. `window.__TASTY_SSR_CACHE__` (streaming — populated by inline scripts)\n * 2. `<script data-tasty-cache>` (non-streaming — JSON payload)\n */\nexport function hydrateTastyCache(state?: SSRCacheState): void {\n if (typeof document === 'undefined') return;\n\n if (!state) {\n state =\n (typeof window !== 'undefined' ? window.__TASTY_SSR_CACHE__ : null) ??\n undefined;\n if (!state) {\n const script = document.querySelector('script[data-tasty-cache]');\n if (script) {\n try {\n state = JSON.parse(script.textContent!) as SSRCacheState;\n } catch {\n return;\n }\n }\n }\n }\n\n if (!state) return;\n\n const injector = getGlobalInjector();\n const registry = injector._sheetManager.getRegistry(document);\n\n registry.classCounter = Math.max(registry.classCounter, state.classCounter);\n\n for (const [cacheKey, className] of Object.entries(state.entries)) {\n registry.cacheKeyToClassName.set(cacheKey, className);\n registry.rules.set(className, {\n className,\n ruleIndex: -2,\n sheetIndex: -2,\n });\n registry.refCounts.set(className, 0);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,kBAAkB,OAA6B;AAC7D,KAAI,OAAO,aAAa,YAAa;AAErC,KAAI,CAAC,OAAO;AACV,WACG,OAAO,WAAW,cAAc,OAAO,sBAAsB,SAC9D,KAAA;AACF,MAAI,CAAC,OAAO;GACV,MAAM,SAAS,SAAS,cAAc,2BAA2B;AACjE,OAAI,OACF,KAAI;AACF,YAAQ,KAAK,MAAM,OAAO,YAAa;WACjC;AACN;;;;AAMR,KAAI,CAAC,MAAO;CAGZ,MAAM,WADW,mBAAmB,CACV,cAAc,YAAY,SAAS;AAE7D,UAAS,eAAe,KAAK,IAAI,SAAS,cAAc,MAAM,aAAa;AAE3E,MAAK,MAAM,CAAC,UAAU,cAAc,OAAO,QAAQ,MAAM,QAAQ,EAAE;AACjE,WAAS,oBAAoB,IAAI,UAAU,UAAU;AACrD,WAAS,MAAM,IAAI,WAAW;GAC5B;GACA,WAAW;GACX,YAAY;GACb,CAAC;AACF,WAAS,UAAU,IAAI,WAAW,EAAE"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SSRCacheState, ServerStyleCollector } from "./collector.js";
|
|
2
|
+
import { getSSRCollector, runWithCollector } from "./async-storage.js";
|
|
3
|
+
import { hydrateTastyCache } from "./hydrate.js";
|
|
4
|
+
export { type SSRCacheState, ServerStyleCollector, getSSRCollector, hydrateTastyCache, runWithCollector };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { registerSSRCollectorGetterGlobal } from "./ssr-collector-ref.js";
|
|
2
|
+
import { ServerStyleCollector } from "./collector.js";
|
|
3
|
+
import { getSSRCollector, runWithCollector } from "./async-storage.js";
|
|
4
|
+
import { hydrateTastyCache } from "./hydrate.js";
|
|
5
|
+
//#region src/ssr/index.ts
|
|
6
|
+
registerSSRCollectorGetterGlobal(getSSRCollector);
|
|
7
|
+
//#endregion
|
|
8
|
+
export { ServerStyleCollector, getSSRCollector, hydrateTastyCache, runWithCollector };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/ssr/index.ts"],"sourcesContent":["/**\n * SSR entry point for @tenphi/tasty.\n *\n * Provides the core SSR infrastructure: ServerStyleCollector,\n * AsyncLocalStorage integration, and cache hydration.\n *\n * Import from '@tenphi/tasty/ssr'.\n */\n\n// Core collector\nexport { ServerStyleCollector } from './collector';\nexport type { SSRCacheState } from './collector';\n\n// AsyncLocalStorage integration for Astro / generic frameworks\nexport { runWithCollector, getSSRCollector } from './async-storage';\n\n// Client-side cache hydration\nexport { hydrateTastyCache } from './hydrate';\n\n// Register the ALS getter so hooks can find the collector\n// without importing 'node:async_hooks' in the browser bundle.\n// Uses globalThis so the getter is visible across separate module graphs.\nimport { getSSRCollector } from './async-storage';\nimport { registerSSRCollectorGetterGlobal } from './ssr-collector-ref';\n\nregisterSSRCollectorGetterGlobal(getSSRCollector);\n"],"mappings":";;;;;AAyBA,iCAAiC,gBAAgB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ServerStyleCollector } from "./collector.js";
|
|
2
|
+
import * as _$react from "react";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/ssr/next.d.ts
|
|
6
|
+
interface TastyRegistryProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to embed the cache state script for client hydration.
|
|
10
|
+
* Set to false to skip cache transfer (useful when cache size
|
|
11
|
+
* exceeds the hydration benefit). Default: true.
|
|
12
|
+
*/
|
|
13
|
+
transferCache?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Next.js App Router registry for Tasty SSR.
|
|
17
|
+
*
|
|
18
|
+
* Wraps the component tree with a ServerStyleCollector and flushes
|
|
19
|
+
* collected CSS into the HTML stream via useServerInsertedHTML.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // app/tasty-registry.tsx
|
|
24
|
+
* 'use client';
|
|
25
|
+
* import { TastyRegistry } from '@tenphi/tasty/ssr/next';
|
|
26
|
+
* export default function TastyStyleRegistry({ children }) {
|
|
27
|
+
* return <TastyRegistry>{children}</TastyRegistry>;
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // app/layout.tsx
|
|
31
|
+
* import TastyStyleRegistry from './tasty-registry';
|
|
32
|
+
* export default function RootLayout({ children }) {
|
|
33
|
+
* return <html><body>
|
|
34
|
+
* <TastyStyleRegistry>{children}</TastyStyleRegistry>
|
|
35
|
+
* </body></html>;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function TastyRegistry({
|
|
40
|
+
children,
|
|
41
|
+
transferCache
|
|
42
|
+
}: TastyRegistryProps): _$react.FunctionComponentElement<_$react.ProviderProps<ServerStyleCollector | null>>;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { TastyRegistry, TastyRegistryProps };
|
|
45
|
+
//# sourceMappingURL=next.d.ts.map
|
package/dist/ssr/next.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getConfig } from "../config.js";
|
|
3
|
+
import { registerSSRCollectorGetter } from "./ssr-collector-ref.js";
|
|
4
|
+
import { TastySSRContext } from "./context.js";
|
|
5
|
+
import { ServerStyleCollector } from "./collector.js";
|
|
6
|
+
import { hydrateTastyCache } from "./hydrate.js";
|
|
7
|
+
import { Fragment, createElement, useState } from "react";
|
|
8
|
+
import { useServerInsertedHTML } from "next/navigation";
|
|
9
|
+
//#region src/ssr/next.ts
|
|
10
|
+
/**
|
|
11
|
+
* Next.js integration for Tasty SSR.
|
|
12
|
+
*
|
|
13
|
+
* Provides TastyRegistry for App Router (streaming via useServerInsertedHTML)
|
|
14
|
+
* and createTastySSRDocument for Pages Router (non-streaming).
|
|
15
|
+
*
|
|
16
|
+
* Import from '@tenphi/tasty/ssr/next'.
|
|
17
|
+
*/
|
|
18
|
+
if (typeof window !== "undefined" && window.__TASTY_SSR_CACHE__) hydrateTastyCache(window.__TASTY_SSR_CACHE__);
|
|
19
|
+
/**
|
|
20
|
+
* Next.js App Router registry for Tasty SSR.
|
|
21
|
+
*
|
|
22
|
+
* Wraps the component tree with a ServerStyleCollector and flushes
|
|
23
|
+
* collected CSS into the HTML stream via useServerInsertedHTML.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* // app/tasty-registry.tsx
|
|
28
|
+
* 'use client';
|
|
29
|
+
* import { TastyRegistry } from '@tenphi/tasty/ssr/next';
|
|
30
|
+
* export default function TastyStyleRegistry({ children }) {
|
|
31
|
+
* return <TastyRegistry>{children}</TastyRegistry>;
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* // app/layout.tsx
|
|
35
|
+
* import TastyStyleRegistry from './tasty-registry';
|
|
36
|
+
* export default function RootLayout({ children }) {
|
|
37
|
+
* return <html><body>
|
|
38
|
+
* <TastyStyleRegistry>{children}</TastyStyleRegistry>
|
|
39
|
+
* </body></html>;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function TastyRegistry({ children, transferCache = true }) {
|
|
44
|
+
const isClient = typeof window !== "undefined";
|
|
45
|
+
const [collector] = useState(() => {
|
|
46
|
+
if (isClient) return null;
|
|
47
|
+
const instance = new ServerStyleCollector();
|
|
48
|
+
registerSSRCollectorGetter(() => instance);
|
|
49
|
+
return instance;
|
|
50
|
+
});
|
|
51
|
+
const nonce = getConfig().nonce;
|
|
52
|
+
useServerInsertedHTML(() => {
|
|
53
|
+
if (!collector) return null;
|
|
54
|
+
const css = collector.flushCSS();
|
|
55
|
+
const cacheState = collector.getCacheState();
|
|
56
|
+
if (!css) return null;
|
|
57
|
+
const styleEl = createElement("style", {
|
|
58
|
+
key: "tasty-ssr-styles",
|
|
59
|
+
"data-tasty-ssr": "",
|
|
60
|
+
nonce,
|
|
61
|
+
dangerouslySetInnerHTML: { __html: css }
|
|
62
|
+
});
|
|
63
|
+
if (!transferCache) return styleEl;
|
|
64
|
+
return createElement(Fragment, null, styleEl, createElement("script", {
|
|
65
|
+
key: "tasty-ssr-cache",
|
|
66
|
+
nonce,
|
|
67
|
+
dangerouslySetInnerHTML: { __html: `(window.__TASTY_SSR_CACHE__=window.__TASTY_SSR_CACHE__||{entries:{},classCounter:0});Object.assign(window.__TASTY_SSR_CACHE__.entries,${JSON.stringify(cacheState.entries)});window.__TASTY_SSR_CACHE__.classCounter=${cacheState.classCounter};` }
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
return createElement(TastySSRContext.Provider, { value: collector }, children);
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { TastyRegistry };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","names":[],"sources":["../../src/ssr/next.ts"],"sourcesContent":["/**\n * Next.js integration for Tasty SSR.\n *\n * Provides TastyRegistry for App Router (streaming via useServerInsertedHTML)\n * and createTastySSRDocument for Pages Router (non-streaming).\n *\n * Import from '@tenphi/tasty/ssr/next'.\n */\n\n'use client';\n\n/// <reference path=\"./next-navigation.d.ts\" />\n\nimport { createElement, Fragment, useState, type ReactNode } from 'react';\nimport { useServerInsertedHTML } from 'next/navigation';\n\nimport { getConfig } from '../config';\nimport { ServerStyleCollector } from './collector';\nimport { TastySSRContext } from './context';\nimport { hydrateTastyCache } from './hydrate';\nimport { registerSSRCollectorGetter } from './ssr-collector-ref';\n\n// Auto-hydrate on module load (client only).\n// When this module is imported by the TastyRegistry client component,\n// the streaming cache scripts have already populated __TASTY_SSR_CACHE__.\nif (typeof window !== 'undefined' && window.__TASTY_SSR_CACHE__) {\n hydrateTastyCache(window.__TASTY_SSR_CACHE__);\n}\n\nexport interface TastyRegistryProps {\n children: ReactNode;\n /**\n * Whether to embed the cache state script for client hydration.\n * Set to false to skip cache transfer (useful when cache size\n * exceeds the hydration benefit). Default: true.\n */\n transferCache?: boolean;\n}\n\n/**\n * Next.js App Router registry for Tasty SSR.\n *\n * Wraps the component tree with a ServerStyleCollector and flushes\n * collected CSS into the HTML stream via useServerInsertedHTML.\n *\n * @example\n * ```tsx\n * // app/tasty-registry.tsx\n * 'use client';\n * import { TastyRegistry } from '@tenphi/tasty/ssr/next';\n * export default function TastyStyleRegistry({ children }) {\n * return <TastyRegistry>{children}</TastyRegistry>;\n * }\n *\n * // app/layout.tsx\n * import TastyStyleRegistry from './tasty-registry';\n * export default function RootLayout({ children }) {\n * return <html><body>\n * <TastyStyleRegistry>{children}</TastyStyleRegistry>\n * </body></html>;\n * }\n * ```\n */\nexport function TastyRegistry({\n children,\n transferCache = true,\n}: TastyRegistryProps) {\n const isClient = typeof window !== 'undefined';\n\n const [collector] = useState(() => {\n if (isClient) return null;\n\n const instance = new ServerStyleCollector();\n\n registerSSRCollectorGetter(() => instance);\n\n return instance;\n });\n const nonce = getConfig().nonce;\n\n useServerInsertedHTML(() => {\n if (!collector) return null;\n\n const css = collector.flushCSS();\n const cacheState = collector.getCacheState();\n\n if (!css) return null;\n\n const styleEl = createElement('style', {\n key: 'tasty-ssr-styles',\n 'data-tasty-ssr': '',\n nonce,\n dangerouslySetInnerHTML: { __html: css },\n });\n\n if (!transferCache) return styleEl;\n\n const scriptEl = createElement('script', {\n key: 'tasty-ssr-cache',\n nonce,\n dangerouslySetInnerHTML: {\n __html:\n `(window.__TASTY_SSR_CACHE__=window.__TASTY_SSR_CACHE__||{entries:{},classCounter:0});` +\n `Object.assign(window.__TASTY_SSR_CACHE__.entries,${JSON.stringify(cacheState.entries)});` +\n `window.__TASTY_SSR_CACHE__.classCounter=${cacheState.classCounter};`,\n },\n });\n\n return createElement(Fragment, null, styleEl, scriptEl);\n });\n\n return createElement(\n TastySSRContext.Provider,\n { value: collector },\n children,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,IAAI,OAAO,WAAW,eAAe,OAAO,oBAC1C,mBAAkB,OAAO,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;AAqC/C,SAAgB,cAAc,EAC5B,UACA,gBAAgB,QACK;CACrB,MAAM,WAAW,OAAO,WAAW;CAEnC,MAAM,CAAC,aAAa,eAAe;AACjC,MAAI,SAAU,QAAO;EAErB,MAAM,WAAW,IAAI,sBAAsB;AAE3C,mCAAiC,SAAS;AAE1C,SAAO;GACP;CACF,MAAM,QAAQ,WAAW,CAAC;AAE1B,6BAA4B;AAC1B,MAAI,CAAC,UAAW,QAAO;EAEvB,MAAM,MAAM,UAAU,UAAU;EAChC,MAAM,aAAa,UAAU,eAAe;AAE5C,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,UAAU,cAAc,SAAS;GACrC,KAAK;GACL,kBAAkB;GAClB;GACA,yBAAyB,EAAE,QAAQ,KAAK;GACzC,CAAC;AAEF,MAAI,CAAC,cAAe,QAAO;AAa3B,SAAO,cAAc,UAAU,MAAM,SAXpB,cAAc,UAAU;GACvC,KAAK;GACL;GACA,yBAAyB,EACvB,QACE,yIACoD,KAAK,UAAU,WAAW,QAAQ,CAAC,4CAC5C,WAAW,aAAa,IACtE;GACF,CAAC,CAEqD;GACvD;AAEF,QAAO,cACL,gBAAgB,UAChB,EAAE,OAAO,WAAW,EACpB,SACD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/ssr/ssr-collector-ref.ts
|
|
2
|
+
const GETTER_KEY = "__tasty_ssr_collector_getter__";
|
|
3
|
+
let _getSSRCollector = null;
|
|
4
|
+
/**
|
|
5
|
+
* Register the collector getter in the current module graph only.
|
|
6
|
+
* Used by Next.js TastyRegistry.
|
|
7
|
+
*/
|
|
8
|
+
function registerSSRCollectorGetter(fn) {
|
|
9
|
+
_getSSRCollector = fn;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register the collector getter on globalThis so it is visible across
|
|
13
|
+
* separate module graphs (e.g. Astro middleware ↔ page components).
|
|
14
|
+
*/
|
|
15
|
+
function registerSSRCollectorGetterGlobal(fn) {
|
|
16
|
+
globalThis[GETTER_KEY] = fn;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve the SSR collector: module-level first, globalThis fallback.
|
|
20
|
+
*/
|
|
21
|
+
function getRegisteredSSRCollector() {
|
|
22
|
+
if (_getSSRCollector) return _getSSRCollector();
|
|
23
|
+
const getter = globalThis[GETTER_KEY];
|
|
24
|
+
return getter ? getter() : null;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { getRegisteredSSRCollector, registerSSRCollectorGetter, registerSSRCollectorGetterGlobal };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=ssr-collector-ref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-collector-ref.js","names":[],"sources":["../../src/ssr/ssr-collector-ref.ts"],"sourcesContent":["/**\n * Global reference to the SSR collector getter function.\n *\n * This indirection avoids importing 'node:async_hooks' in the browser bundle.\n * The SSR entry point sets this ref when loaded on the server. The useStyles\n * hook calls it if set; on the client it stays null and is never called.\n *\n * Uses a module-level variable as the primary mechanism. In Next.js App\n * Router the RSC and SSR module graphs load separate copies of this module,\n * so the getter registered by TastyRegistry (SSR layer) is invisible to\n * server components (RSC layer) — which correctly fall through to inline\n * RSC styles.\n *\n * A globalThis fallback (`registerSSRCollectorGetterGlobal`) is provided\n * for frameworks like Astro where middleware and page components live in\n * different module graphs and must share the getter across them.\n */\n\nimport type { ServerStyleCollector } from './collector';\n\ntype SSRCollectorGetter = () => ServerStyleCollector | null;\n\nconst GETTER_KEY = '__tasty_ssr_collector_getter__';\n\nlet _getSSRCollector: SSRCollectorGetter | null = null;\n\n/**\n * Register the collector getter in the current module graph only.\n * Used by Next.js TastyRegistry.\n */\nexport function registerSSRCollectorGetter(fn: SSRCollectorGetter): void {\n _getSSRCollector = fn;\n}\n\n/**\n * Register the collector getter on globalThis so it is visible across\n * separate module graphs (e.g. Astro middleware ↔ page components).\n */\nexport function registerSSRCollectorGetterGlobal(fn: SSRCollectorGetter): void {\n (globalThis as Record<string, unknown>)[GETTER_KEY] = fn;\n}\n\n/**\n * Retrieve the SSR collector: module-level first, globalThis fallback.\n */\nexport function getRegisteredSSRCollector(): ServerStyleCollector | null {\n if (_getSSRCollector) return _getSSRCollector();\n const getter = (globalThis as Record<string, unknown>)[GETTER_KEY] as\n | SSRCollectorGetter\n | undefined;\n return getter ? getter() : null;\n}\n"],"mappings":";AAsBA,MAAM,aAAa;AAEnB,IAAI,mBAA8C;;;;;AAMlD,SAAgB,2BAA2B,IAA8B;AACvE,oBAAmB;;;;;;AAOrB,SAAgB,iCAAiC,IAA8B;AAC5E,YAAuC,cAAc;;;;;AAMxD,SAAgB,4BAAyD;AACvE,KAAI,iBAAkB,QAAO,kBAAkB;CAC/C,MAAM,SAAU,WAAuC;AAGvD,QAAO,SAAS,QAAQ,GAAG"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Styles } from "../styles/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/states/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parsed advanced state information
|
|
6
|
+
*/
|
|
7
|
+
interface ParsedAdvancedState {
|
|
8
|
+
type: 'media' | 'container' | 'root' | 'parent' | 'own' | 'starting' | 'predefined' | 'modifier';
|
|
9
|
+
condition: string;
|
|
10
|
+
containerName?: string;
|
|
11
|
+
raw: string;
|
|
12
|
+
mediaType?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Context for state parsing operations
|
|
16
|
+
*/
|
|
17
|
+
interface StateParserContext {
|
|
18
|
+
localPredefinedStates: Record<string, string>;
|
|
19
|
+
globalPredefinedStates: Record<string, string>;
|
|
20
|
+
isSubElement?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* At-rule context for CSS generation
|
|
24
|
+
*/
|
|
25
|
+
interface AtRuleContext {
|
|
26
|
+
media?: string[];
|
|
27
|
+
container?: {
|
|
28
|
+
name?: string;
|
|
29
|
+
condition: string;
|
|
30
|
+
}[];
|
|
31
|
+
startingStyle?: boolean;
|
|
32
|
+
rootStates?: string[];
|
|
33
|
+
negatedRootStates?: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configure global predefined states
|
|
37
|
+
*/
|
|
38
|
+
declare function setGlobalPredefinedStates(states: Record<string, string>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get global predefined states
|
|
41
|
+
*/
|
|
42
|
+
declare function getGlobalPredefinedStates(): Record<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Create a state parser context from styles
|
|
45
|
+
*/
|
|
46
|
+
declare function createStateParserContext(styles?: Styles, isSubElement?: boolean): StateParserContext;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { AtRuleContext, ParsedAdvancedState, StateParserContext, createStateParserContext, getGlobalPredefinedStates, setGlobalPredefinedStates };
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { isDevEnv } from "../utils/is-dev-env.js";
|
|
2
|
+
import { hasStylesGenerated } from "../config.js";
|
|
3
|
+
//#region src/states/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Advanced State Mapping - Predefined States Management
|
|
6
|
+
*
|
|
7
|
+
* This module handles global and local predefined states for the Tasty styling system.
|
|
8
|
+
* See ADVANCED_STATE_MAPPING.md for full specification.
|
|
9
|
+
*/
|
|
10
|
+
const BUILTIN_STATES = new Set([
|
|
11
|
+
"@starting",
|
|
12
|
+
"@keyframes",
|
|
13
|
+
"@properties",
|
|
14
|
+
"@fontFace",
|
|
15
|
+
"@counterStyle",
|
|
16
|
+
"@supports",
|
|
17
|
+
"@inherit"
|
|
18
|
+
]);
|
|
19
|
+
let globalPredefinedStates = {};
|
|
20
|
+
const emittedWarnings = /* @__PURE__ */ new Set();
|
|
21
|
+
const devMode = isDevEnv();
|
|
22
|
+
/**
|
|
23
|
+
* Emit a warning only once
|
|
24
|
+
*/
|
|
25
|
+
function warnOnce(key, message) {
|
|
26
|
+
if (devMode && !emittedWarnings.has(key)) {
|
|
27
|
+
emittedWarnings.add(key);
|
|
28
|
+
console.warn(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configure global predefined states
|
|
33
|
+
*/
|
|
34
|
+
function setGlobalPredefinedStates(states) {
|
|
35
|
+
if (hasStylesGenerated()) {
|
|
36
|
+
const newStateNames = Object.keys(states).join(", ");
|
|
37
|
+
warnOnce(`dynamic-states:${newStateNames}`, `[Tasty] Cannot update predefined states after styles have been generated.\nThe new definition(s) for ${newStateNames} will be ignored.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const [name, value] of Object.entries(states)) {
|
|
41
|
+
if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(name)) {
|
|
42
|
+
warnOnce(`invalid-state-name:${name}`, `[Tasty] Invalid predefined state name '${name}'. Must start with '@' followed by a letter.`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (BUILTIN_STATES.has(name)) {
|
|
46
|
+
warnOnce(`reserved-state:${name}`, `[Tasty] Cannot define predefined state '${name}'. This name is reserved for built-in functionality.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (name === "@media" || name === "@root" || name === "@parent" || name === "@own" || name.startsWith("@(")) {
|
|
50
|
+
warnOnce(`reserved-prefix:${name}`, `[Tasty] Cannot define predefined state '${name}'. This prefix is reserved for built-in functionality.`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const crossRefs = extractPredefinedStateRefs(value);
|
|
54
|
+
if (crossRefs.length > 0) {
|
|
55
|
+
warnOnce(`cross-ref:${name}`, `[Tasty] Predefined state '${name}' references another predefined state '${crossRefs[0]}'.\nPredefined states cannot reference each other. Use the full definition instead.`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (globalPredefinedStates[name] && globalPredefinedStates[name] !== value) warnOnce(`duplicate-state:${name}`, `[Tasty] Duplicate predefined state '${name}' in configure(). The last definition will be used.`);
|
|
59
|
+
globalPredefinedStates[name] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get global predefined states
|
|
64
|
+
*/
|
|
65
|
+
function getGlobalPredefinedStates() {
|
|
66
|
+
return globalPredefinedStates;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Regex to match predefined state references in a string
|
|
70
|
+
* Matches @name that is NOT followed by ( or : and is a complete word
|
|
71
|
+
* Uses word boundary and negative lookahead
|
|
72
|
+
*/
|
|
73
|
+
const PREDEFINED_STATE_PATTERN = /@([A-Za-z][A-Za-z0-9-]*)(?![A-Za-z0-9-:(])/g;
|
|
74
|
+
/**
|
|
75
|
+
* Extract predefined state references from a string
|
|
76
|
+
*/
|
|
77
|
+
function extractPredefinedStateRefs(value) {
|
|
78
|
+
const matches = value.matchAll(PREDEFINED_STATE_PATTERN);
|
|
79
|
+
const refs = [];
|
|
80
|
+
for (const match of matches) {
|
|
81
|
+
const stateName = "@" + match[1];
|
|
82
|
+
if (!BUILTIN_STATES.has(stateName) && !refs.includes(stateName)) refs.push(stateName);
|
|
83
|
+
}
|
|
84
|
+
return refs;
|
|
85
|
+
}
|
|
86
|
+
const _localStatesCache = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
/**
|
|
88
|
+
* Extract local predefined states from a styles object
|
|
89
|
+
* Local predefined states are top-level keys starting with @ that have string values
|
|
90
|
+
* and are valid predefined state names (not built-in like @media, @root, etc.)
|
|
91
|
+
*
|
|
92
|
+
* Results are cached by object identity via WeakMap since the same styles object
|
|
93
|
+
* is passed to this function multiple times per pipeline run (once per chunk
|
|
94
|
+
* in renderStylesForChunk and generateChunkCacheKey).
|
|
95
|
+
*/
|
|
96
|
+
function extractLocalPredefinedStates(styles) {
|
|
97
|
+
if (!styles || typeof styles !== "object") return {};
|
|
98
|
+
const cached = _localStatesCache.get(styles);
|
|
99
|
+
if (cached !== void 0) return cached;
|
|
100
|
+
const localStates = {};
|
|
101
|
+
for (const [key, value] of Object.entries(styles)) if (key.startsWith("@") && typeof value === "string") {
|
|
102
|
+
if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(key)) continue;
|
|
103
|
+
if (BUILTIN_STATES.has(key)) continue;
|
|
104
|
+
if (key === "@media" || key === "@root" || key === "@parent" || key === "@own" || key.startsWith("@(")) continue;
|
|
105
|
+
const crossRefs = extractPredefinedStateRefs(value);
|
|
106
|
+
if (crossRefs.length > 0) {
|
|
107
|
+
warnOnce(`local-cross-ref:${key}`, `[Tasty] Predefined state '${key}' references another predefined state '${crossRefs[0]}'.\nPredefined states cannot reference each other. Use the full definition instead.`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
localStates[key] = value;
|
|
111
|
+
}
|
|
112
|
+
_localStatesCache.set(styles, localStates);
|
|
113
|
+
return localStates;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create a state parser context from styles
|
|
117
|
+
*/
|
|
118
|
+
function createStateParserContext(styles, isSubElement) {
|
|
119
|
+
return {
|
|
120
|
+
localPredefinedStates: styles ? extractLocalPredefinedStates(styles) : {},
|
|
121
|
+
globalPredefinedStates: getGlobalPredefinedStates(),
|
|
122
|
+
isSubElement
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a predefined state reference to its value
|
|
127
|
+
* Returns the resolved value or null if not found
|
|
128
|
+
*/
|
|
129
|
+
function resolvePredefinedState(stateKey, ctx) {
|
|
130
|
+
if (ctx.localPredefinedStates[stateKey]) return ctx.localPredefinedStates[stateKey];
|
|
131
|
+
if (ctx.globalPredefinedStates[stateKey]) return ctx.globalPredefinedStates[stateKey];
|
|
132
|
+
warnOnce(`undefined-state:${stateKey}`, `[Tasty] Undefined predefined state '${stateKey}'.\nDefine it in configure({ states: { '${stateKey}': '...' } }) or in the component's styles.`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Expand dimension shorthands in a condition string
|
|
137
|
+
* w -> width, h -> height, is -> inline-size, bs -> block-size
|
|
138
|
+
*/
|
|
139
|
+
function expandDimensionShorthands(condition) {
|
|
140
|
+
let result = condition;
|
|
141
|
+
result = result.replace(/\bw\b/g, "width");
|
|
142
|
+
result = result.replace(/\bh\b/g, "height");
|
|
143
|
+
result = result.replace(/\bis\b/g, "inline-size");
|
|
144
|
+
result = result.replace(/\bbs\b/g, "block-size");
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Convert tasty units in a string (e.g., 40x -> calc(var(--gap) * 40))
|
|
149
|
+
*/
|
|
150
|
+
function expandTastyUnits(value) {
|
|
151
|
+
return value.replace(/(\d+(?:\.\d+)?)\s*x\b/g, (_, num) => {
|
|
152
|
+
return `calc(var(--gap) * ${num})`;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Find the index of the first comma at parentheses depth 0.
|
|
157
|
+
* Returns -1 if no top-level comma is found.
|
|
158
|
+
* This prevents splitting on commas inside function calls like scroll-state(a, b).
|
|
159
|
+
*/
|
|
160
|
+
function findTopLevelComma(s) {
|
|
161
|
+
let depth = 0;
|
|
162
|
+
for (let i = 0; i < s.length; i++) if (s[i] === "(") depth++;
|
|
163
|
+
else if (s[i] === ")") depth--;
|
|
164
|
+
else if (s[i] === "," && depth === 0) return i;
|
|
165
|
+
return -1;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { createStateParserContext, expandDimensionShorthands, expandTastyUnits, extractLocalPredefinedStates, extractPredefinedStateRefs, findTopLevelComma, getGlobalPredefinedStates, resolvePredefinedState, setGlobalPredefinedStates };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/states/index.ts"],"sourcesContent":["/**\n * Advanced State Mapping - Predefined States Management\n *\n * This module handles global and local predefined states for the Tasty styling system.\n * See ADVANCED_STATE_MAPPING.md for full specification.\n */\n\nimport { hasStylesGenerated } from '../config';\nimport type { Styles } from '../styles/types';\nimport { isDevEnv } from '../utils/is-dev-env';\n\n/**\n * Parsed advanced state information\n */\nexport interface ParsedAdvancedState {\n type:\n | 'media'\n | 'container'\n | 'root'\n | 'parent'\n | 'own'\n | 'starting'\n | 'predefined'\n | 'modifier';\n condition: string; // e.g., 'width <= 920px' or 'hovered'\n containerName?: string; // for container queries\n raw: string; // original state key\n mediaType?: string; // for @media:screen, @media:print, etc.\n}\n\n/**\n * Context for state parsing operations\n */\nexport interface StateParserContext {\n localPredefinedStates: Record<string, string>;\n globalPredefinedStates: Record<string, string>;\n isSubElement?: boolean; // true when processing sub-element styles (for @own() validation)\n}\n\n/**\n * At-rule context for CSS generation\n */\nexport interface AtRuleContext {\n media?: string[]; // @media conditions to wrap rule in (merged with 'and')\n container?: { name?: string; condition: string }[]; // @container conditions (nested)\n startingStyle?: boolean;\n rootStates?: string[]; // :root state selectors (e.g., '[data-theme=\"dark\"]')\n negatedRootStates?: string[]; // Negated :root state selectors for non-overlapping rules (e.g., ':not([data-theme=\"dark\"])')\n}\n\n// Built-in state names that cannot be overridden\nconst BUILTIN_STATES = new Set([\n '@starting',\n '@keyframes',\n '@properties',\n '@fontFace',\n '@counterStyle',\n '@supports',\n // @inherit is a value (not a key), but reserved here to prevent\n // users from accidentally defining a state named '@inherit'.\n '@inherit',\n]);\n\n// Reserved prefixes that are built-in\nconst RESERVED_PREFIXES = [\n '@media',\n '@root',\n '@parent',\n '@own',\n '@(',\n '@starting',\n '@keyframes',\n '@properties',\n '@supports',\n '@inherit',\n];\n\n// Global predefined states storage\nlet globalPredefinedStates: Record<string, string> = {};\n\n// Warnings tracking to avoid duplicates\nconst emittedWarnings = new Set<string>();\n\nconst devMode = isDevEnv();\n\n/**\n * Emit a warning only once\n */\nfunction warnOnce(key: string, message: string): void {\n if (devMode && !emittedWarnings.has(key)) {\n emittedWarnings.add(key);\n console.warn(message);\n }\n}\n\n/**\n * Configure global predefined states\n */\nexport function setGlobalPredefinedStates(\n states: Record<string, string>,\n): void {\n if (hasStylesGenerated()) {\n const newStateNames = Object.keys(states).join(', ');\n warnOnce(\n `dynamic-states:${newStateNames}`,\n `[Tasty] Cannot update predefined states after styles have been generated.\\n` +\n `The new definition(s) for ${newStateNames} will be ignored.`,\n );\n return;\n }\n\n // Validate state names\n for (const [name, value] of Object.entries(states)) {\n // Check for valid name format\n if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(name)) {\n warnOnce(\n `invalid-state-name:${name}`,\n `[Tasty] Invalid predefined state name '${name}'. Must start with '@' followed by a letter.`,\n );\n continue;\n }\n\n // Check for reserved names\n if (BUILTIN_STATES.has(name)) {\n warnOnce(\n `reserved-state:${name}`,\n `[Tasty] Cannot define predefined state '${name}'. This name is reserved for built-in functionality.`,\n );\n continue;\n }\n\n // Check for reserved prefixes (but only exact matches, not user-defined states like @mobile)\n // Reserved prefixes are: @media, @root, @parent, @own, @(\n // A user state like @mobile should NOT be blocked\n const isReservedPrefix =\n name === '@media' ||\n name === '@root' ||\n name === '@parent' ||\n name === '@own' ||\n name.startsWith('@(');\n\n if (isReservedPrefix) {\n warnOnce(\n `reserved-prefix:${name}`,\n `[Tasty] Cannot define predefined state '${name}'. This prefix is reserved for built-in functionality.`,\n );\n continue;\n }\n\n // Check for cross-references\n const crossRefs = extractPredefinedStateRefs(value);\n if (crossRefs.length > 0) {\n warnOnce(\n `cross-ref:${name}`,\n `[Tasty] Predefined state '${name}' references another predefined state '${crossRefs[0]}'.\\n` +\n `Predefined states cannot reference each other. Use the full definition instead.`,\n );\n continue;\n }\n\n // Check for duplicates\n if (\n globalPredefinedStates[name] &&\n globalPredefinedStates[name] !== value\n ) {\n warnOnce(\n `duplicate-state:${name}`,\n `[Tasty] Duplicate predefined state '${name}' in configure(). The last definition will be used.`,\n );\n }\n\n globalPredefinedStates[name] = value;\n }\n}\n\n/**\n * Get global predefined states\n */\nexport function getGlobalPredefinedStates(): Record<string, string> {\n return globalPredefinedStates;\n}\n\n/**\n * Clear global predefined states (for testing only)\n */\nexport function clearGlobalPredefinedStates(): void {\n globalPredefinedStates = {};\n emittedWarnings.clear();\n}\n\n/**\n * Regex to match predefined state references in a string\n * Matches @name that is NOT followed by ( or : and is a complete word\n * Uses word boundary and negative lookahead\n */\nconst PREDEFINED_STATE_PATTERN = /@([A-Za-z][A-Za-z0-9-]*)(?![A-Za-z0-9-:(])/g;\n\n/**\n * Extract predefined state references from a string\n */\nexport function extractPredefinedStateRefs(value: string): string[] {\n const matches = value.matchAll(PREDEFINED_STATE_PATTERN);\n const refs: string[] = [];\n\n for (const match of matches) {\n const stateName = '@' + match[1];\n // Skip built-in states (@starting) and duplicates\n // Note: @media, @root, @own are always followed by '(' so the regex\n // negative lookahead (?![A-Za-z0-9-:(]) already excludes them\n if (!BUILTIN_STATES.has(stateName) && !refs.includes(stateName)) {\n refs.push(stateName);\n }\n }\n\n return refs;\n}\n\n/**\n * Check if a state key is a predefined state reference\n */\nexport function isPredefinedStateRef(stateKey: string): boolean {\n if (!stateKey.startsWith('@')) return false;\n if (BUILTIN_STATES.has(stateKey)) return false;\n\n // Check if it's NOT a built-in prefix\n for (const prefix of RESERVED_PREFIXES) {\n if (stateKey === prefix || stateKey.startsWith(prefix)) {\n // Check if it's exactly @media, @root, @parent, @own, or starts with @( or @media(\n if (\n stateKey === '@media' ||\n stateKey.startsWith('@media(') ||\n stateKey.startsWith('@media:')\n ) {\n return false;\n }\n if (stateKey === '@root' || stateKey.startsWith('@root(')) return false;\n if (stateKey === '@parent' || stateKey.startsWith('@parent('))\n return false;\n if (stateKey === '@own' || stateKey.startsWith('@own(')) return false;\n if (stateKey.startsWith('@(')) return false;\n }\n }\n\n // Must match the predefined state pattern\n return /^@[A-Za-z][A-Za-z0-9-]*$/.test(stateKey);\n}\n\nconst _localStatesCache = new WeakMap<object, Record<string, string>>();\n\n/**\n * Extract local predefined states from a styles object\n * Local predefined states are top-level keys starting with @ that have string values\n * and are valid predefined state names (not built-in like @media, @root, etc.)\n *\n * Results are cached by object identity via WeakMap since the same styles object\n * is passed to this function multiple times per pipeline run (once per chunk\n * in renderStylesForChunk and generateChunkCacheKey).\n */\nexport function extractLocalPredefinedStates(\n styles: Styles,\n): Record<string, string> {\n if (!styles || typeof styles !== 'object') {\n return {};\n }\n\n const cached = _localStatesCache.get(styles as object);\n if (cached !== undefined) return cached;\n\n const localStates: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(styles)) {\n // Check if it's a predefined state definition (starts with @, has string value)\n if (key.startsWith('@') && typeof value === 'string') {\n // Validate name format - must be @[letter][letters/numbers/dashes]*\n if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(key)) {\n continue; // Skip invalid names silently (might be something else)\n }\n\n // Skip built-in states\n if (BUILTIN_STATES.has(key)) {\n continue;\n }\n\n // Skip reserved prefixes\n if (\n key === '@media' ||\n key === '@root' ||\n key === '@parent' ||\n key === '@own' ||\n key.startsWith('@(')\n ) {\n continue;\n }\n\n // Check for cross-references (predefined states cannot reference each other)\n const crossRefs = extractPredefinedStateRefs(value);\n if (crossRefs.length > 0) {\n warnOnce(\n `local-cross-ref:${key}`,\n `[Tasty] Predefined state '${key}' references another predefined state '${crossRefs[0]}'.\\n` +\n `Predefined states cannot reference each other. Use the full definition instead.`,\n );\n continue;\n }\n\n localStates[key] = value;\n }\n }\n\n _localStatesCache.set(styles as object, localStates);\n\n return localStates;\n}\n\n/**\n * Create a state parser context from styles\n */\nexport function createStateParserContext(\n styles?: Styles,\n isSubElement?: boolean,\n): StateParserContext {\n const localStates = styles ? extractLocalPredefinedStates(styles) : {};\n\n return {\n localPredefinedStates: localStates,\n globalPredefinedStates: getGlobalPredefinedStates(),\n isSubElement,\n };\n}\n\n/**\n * Resolve a predefined state reference to its value\n * Returns the resolved value or null if not found\n */\nexport function resolvePredefinedState(\n stateKey: string,\n ctx: StateParserContext,\n): string | null {\n // Check local first (higher priority)\n if (ctx.localPredefinedStates[stateKey]) {\n return ctx.localPredefinedStates[stateKey];\n }\n\n // Then check global\n if (ctx.globalPredefinedStates[stateKey]) {\n return ctx.globalPredefinedStates[stateKey];\n }\n\n // Not found - emit warning\n warnOnce(\n `undefined-state:${stateKey}`,\n `[Tasty] Undefined predefined state '${stateKey}'.\\n` +\n `Define it in configure({ states: { '${stateKey}': '...' } }) or in the component's styles.`,\n );\n\n return null;\n}\n\n/**\n * Normalize state key by trimming whitespace and removing trailing/leading operators\n */\nexport function normalizeStateKey(stateKey: string): {\n key: string;\n warnings: string[];\n} {\n const warnings: string[] = [];\n let key = stateKey;\n\n // Check for whitespace-only\n if (key.trim() === '') {\n if (key !== '') {\n warnings.push(\n `[Tasty] Whitespace-only state key normalized to default state ''.`,\n );\n }\n return { key: '', warnings };\n }\n\n // Trim whitespace\n key = key.trim();\n\n // Remove trailing operators\n const trailingOpMatch = key.match(/\\s*[&|^]\\s*$/);\n if (trailingOpMatch) {\n const originalKey = key;\n key = key.slice(0, -trailingOpMatch[0].length).trim();\n warnings.push(\n `[Tasty] State key '${originalKey}' has trailing operator. Normalized to '${key}'.`,\n );\n }\n\n // Remove leading operators (except !)\n const leadingOpMatch = key.match(/^\\s*[&|^]\\s*/);\n if (leadingOpMatch) {\n const originalKey = key;\n key = key.slice(leadingOpMatch[0].length).trim();\n warnings.push(\n `[Tasty] State key '${originalKey}' has leading operator. Normalized to '${key}'.`,\n );\n }\n\n return { key, warnings };\n}\n\n/**\n * Expand dimension shorthands in a condition string\n * w -> width, h -> height, is -> inline-size, bs -> block-size\n */\nexport function expandDimensionShorthands(condition: string): string {\n // Replace dimension shorthands (only when they appear as standalone words)\n let result = condition;\n\n // w -> width (but not part of other words)\n result = result.replace(/\\bw\\b/g, 'width');\n\n // h -> height\n result = result.replace(/\\bh\\b/g, 'height');\n\n // is -> inline-size\n result = result.replace(/\\bis\\b/g, 'inline-size');\n\n // bs -> block-size\n result = result.replace(/\\bbs\\b/g, 'block-size');\n\n return result;\n}\n\n/**\n * Convert tasty units in a string (e.g., 40x -> calc(var(--gap) * 40))\n */\nexport function expandTastyUnits(value: string): string {\n // Match number followed by 'x' unit (tasty gap unit)\n return value.replace(/(\\d+(?:\\.\\d+)?)\\s*x\\b/g, (_, num) => {\n return `calc(var(--gap) * ${num})`;\n });\n}\n\n/**\n * Parse an advanced state key and return its type and components\n */\nexport function parseAdvancedState(\n stateKey: string,\n ctx: StateParserContext,\n): ParsedAdvancedState {\n const raw = stateKey;\n\n // Check for @starting (exact match)\n if (stateKey === '@starting') {\n return {\n type: 'starting',\n condition: '',\n raw,\n };\n }\n\n // Check for @media:type (e.g., @media:print)\n if (stateKey.startsWith('@media:')) {\n const mediaType = stateKey.slice(7); // Remove '@media:'\n if (!['all', 'screen', 'print', 'speech'].includes(mediaType)) {\n warnOnce(\n `unknown-media-type:${mediaType}`,\n `[Tasty] Unknown media type '${mediaType}'. Valid types: all, screen, print, speech.`,\n );\n }\n return {\n type: 'media',\n condition: '',\n mediaType,\n raw,\n };\n }\n\n // Check for @media(...) - media query with condition\n if (stateKey.startsWith('@media(')) {\n const endParen = findMatchingParen(stateKey, 6);\n if (endParen === -1) {\n warnOnce(\n `unclosed-media:${stateKey}`,\n `[Tasty] Unclosed media query '${stateKey}'. Missing closing parenthesis.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n let condition = stateKey.slice(7, endParen);\n\n // Check for empty condition\n if (!condition.trim()) {\n warnOnce(\n `empty-media:${stateKey}`,\n `[Tasty] Empty media query condition '${stateKey}'.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n // Expand shorthands and units\n condition = expandDimensionShorthands(condition);\n condition = expandTastyUnits(condition);\n\n return {\n type: 'media',\n condition,\n raw,\n };\n }\n\n // Check for @root(...) - root state\n if (stateKey.startsWith('@root(')) {\n const endParen = findMatchingParen(stateKey, 5);\n if (endParen === -1) {\n warnOnce(\n `unclosed-root:${stateKey}`,\n `[Tasty] Unclosed root state '${stateKey}'. Missing closing parenthesis.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n const condition = stateKey.slice(6, endParen);\n\n if (!condition.trim()) {\n warnOnce(\n `empty-root:${stateKey}`,\n `[Tasty] Empty root state condition '${stateKey}'.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n return {\n type: 'root',\n condition,\n raw,\n };\n }\n\n // Check for @parent(...) - parent element state\n if (stateKey.startsWith('@parent(')) {\n const endParen = findMatchingParen(stateKey, 7);\n if (endParen === -1) {\n warnOnce(\n `unclosed-parent:${stateKey}`,\n `[Tasty] Unclosed parent state '${stateKey}'. Missing closing parenthesis.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n const condition = stateKey.slice(8, endParen);\n\n if (!condition.trim()) {\n warnOnce(\n `empty-parent:${stateKey}`,\n `[Tasty] Empty parent state condition '${stateKey}'.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n return {\n type: 'parent',\n condition,\n raw,\n };\n }\n\n // Check for @own(...) - sub-element own state\n if (stateKey.startsWith('@own(')) {\n const endParen = findMatchingParen(stateKey, 4);\n if (endParen === -1) {\n warnOnce(\n `unclosed-own:${stateKey}`,\n `[Tasty] Unclosed own state '${stateKey}'. Missing closing parenthesis.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n const condition = stateKey.slice(5, endParen);\n\n if (!condition.trim()) {\n warnOnce(\n `empty-own:${stateKey}`,\n `[Tasty] Empty own state condition '${stateKey}'.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n // Check if used outside sub-element context\n if (!ctx.isSubElement) {\n warnOnce(\n `own-outside-subelement:${stateKey}`,\n `[Tasty] @own(${condition}) used outside sub-element context.\\n` +\n `@own() is equivalent to '${condition}' at the root level. ` +\n `Did you mean to use it inside a sub-element?`,\n );\n // Treat as regular modifier\n return {\n type: 'modifier',\n condition,\n raw,\n };\n }\n\n return {\n type: 'own',\n condition,\n raw,\n };\n }\n\n // Check for @(...) - container query (unnamed or named)\n if (stateKey.startsWith('@(')) {\n const endParen = findMatchingParen(stateKey, 1);\n if (endParen === -1) {\n warnOnce(\n `unclosed-container:${stateKey}`,\n `[Tasty] Unclosed container query '${stateKey}'. Missing closing parenthesis.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n const content = stateKey.slice(2, endParen);\n\n if (!content.trim()) {\n warnOnce(\n `empty-container:${stateKey}`,\n `[Tasty] Empty container query '${stateKey}'.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n\n // Check if named container (first token is name, followed by comma)\n // Use parentheses-aware comma search so inner commas (e.g., scroll-state(a, b)) are skipped\n const commaIndex = findTopLevelComma(content);\n let containerName: string | undefined;\n let condition: string;\n\n if (commaIndex !== -1) {\n // Named container: @(layout, w < 600px)\n containerName = content.slice(0, commaIndex).trim();\n condition = content.slice(commaIndex + 1).trim();\n } else {\n // Unnamed container: @(w < 600px)\n condition = content.trim();\n }\n\n // Check for style query shorthand (starts with $)\n if (condition.startsWith('$')) {\n // Style query: @(layout, $compact) or @(layout, $variant=compact)\n const styleCondition = parseStyleQuery(condition);\n if (!styleCondition) {\n warnOnce(\n `invalid-style-query:${stateKey}`,\n `[Tasty] Invalid style query '${condition}' in container query.`,\n );\n return { type: 'modifier', condition: stateKey, raw };\n }\n condition = styleCondition;\n } else if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) {\n // Function-like syntax: scroll-state(...), style(...), etc.\n // Pass through verbatim — no dimension expansion needed\n } else {\n // Dimension query - expand shorthands and units\n condition = expandDimensionShorthands(condition);\n condition = expandTastyUnits(condition);\n }\n\n return {\n type: 'container',\n condition,\n containerName,\n raw,\n };\n }\n\n // Check for predefined state reference\n if (isPredefinedStateRef(stateKey)) {\n const resolved = resolvePredefinedState(stateKey, ctx);\n if (resolved) {\n // Recursively parse the resolved value to extract the actual state type\n // (e.g., @mobile -> @media(w < 768px) should become a media state)\n const parsedResolved = parseAdvancedState(resolved, ctx);\n return {\n ...parsedResolved,\n raw, // Keep original raw for traceability\n };\n }\n // If not resolved, treat as modifier (will likely produce invalid CSS)\n return {\n type: 'modifier',\n condition: stateKey,\n raw,\n };\n }\n\n // Regular modifier (boolean mod, pseudo-class, class, attribute)\n return {\n type: 'modifier',\n condition: stateKey,\n raw,\n };\n}\n\n/**\n * Parse a style query condition (e.g., $compact, $variant=compact, $variant=\"very compact\")\n */\nfunction parseStyleQuery(condition: string): string | null {\n // Remove $ prefix\n const query = condition.slice(1);\n\n // Check for comparison operators (not supported)\n if (/[<>]/.test(query)) {\n warnOnce(\n `style-query-comparison:${condition}`,\n `[Tasty] Style queries only support equality. '${condition}' is invalid. Use '${condition.split(/[<>]/)[0]}=...' instead.`,\n );\n return null;\n }\n\n // Check for equality\n const eqIndex = query.indexOf('=');\n if (eqIndex === -1) {\n // Just existence check: style(--compact)\n return `style(--${query})`;\n }\n\n const propName = query.slice(0, eqIndex).trim();\n let value = query.slice(eqIndex + 1).trim();\n\n // Handle quoted values\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n // Keep quotes for CSS\n } else {\n // Add quotes if needed\n value = `\"${value}\"`;\n }\n\n // Expand tasty units in value\n value = expandTastyUnits(value);\n\n return `style(--${propName}: ${value})`;\n}\n\n/**\n * Find the index of the first comma at parentheses depth 0.\n * Returns -1 if no top-level comma is found.\n * This prevents splitting on commas inside function calls like scroll-state(a, b).\n */\nexport function findTopLevelComma(s: string): number {\n let depth = 0;\n\n for (let i = 0; i < s.length; i++) {\n if (s[i] === '(') depth++;\n else if (s[i] === ')') depth--;\n else if (s[i] === ',' && depth === 0) return i;\n }\n\n return -1;\n}\n\nfunction findMatchingParen(str: string, startIndex: number): number {\n let depth = 1;\n let i = startIndex + 1;\n\n while (i < str.length && depth > 0) {\n if (str[i] === '(') depth++;\n else if (str[i] === ')') depth--;\n i++;\n }\n\n return depth === 0 ? i - 1 : -1;\n}\n\n/**\n * Check if a state key is an advanced state (starts with @)\n */\nexport function isAdvancedState(stateKey: string): boolean {\n return stateKey.startsWith('@') || stateKey.startsWith('!@');\n}\n\n/**\n * Detect the type of advanced state from a raw state key\n */\nexport function detectAdvancedStateType(\n stateKey: string,\n): ParsedAdvancedState['type'] {\n // Handle negation prefix\n const key = stateKey.startsWith('!') ? stateKey.slice(1) : stateKey;\n\n if (key === '@starting') return 'starting';\n if (key.startsWith('@media')) return 'media';\n if (key.startsWith('@root(')) return 'root';\n if (key.startsWith('@parent(')) return 'parent';\n if (key.startsWith('@own(')) return 'own';\n if (key.startsWith('@(')) return 'container';\n if (isPredefinedStateRef(key)) return 'predefined';\n return 'modifier';\n}\n"],"mappings":";;;;;;;;;AAmDA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CAGA;CACD,CAAC;AAiBF,IAAI,yBAAiD,EAAE;AAGvD,MAAM,kCAAkB,IAAI,KAAa;AAEzC,MAAM,UAAU,UAAU;;;;AAK1B,SAAS,SAAS,KAAa,SAAuB;AACpD,KAAI,WAAW,CAAC,gBAAgB,IAAI,IAAI,EAAE;AACxC,kBAAgB,IAAI,IAAI;AACxB,UAAQ,KAAK,QAAQ;;;;;;AAOzB,SAAgB,0BACd,QACM;AACN,KAAI,oBAAoB,EAAE;EACxB,MAAM,gBAAgB,OAAO,KAAK,OAAO,CAAC,KAAK,KAAK;AACpD,WACE,kBAAkB,iBAClB,wGAC+B,cAAc,mBAC9C;AACD;;AAIF,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;AAElD,MAAI,CAAC,2BAA2B,KAAK,KAAK,EAAE;AAC1C,YACE,sBAAsB,QACtB,0CAA0C,KAAK,8CAChD;AACD;;AAIF,MAAI,eAAe,IAAI,KAAK,EAAE;AAC5B,YACE,kBAAkB,QAClB,2CAA2C,KAAK,sDACjD;AACD;;AAaF,MANE,SAAS,YACT,SAAS,WACT,SAAS,aACT,SAAS,UACT,KAAK,WAAW,KAAK,EAED;AACpB,YACE,mBAAmB,QACnB,2CAA2C,KAAK,wDACjD;AACD;;EAIF,MAAM,YAAY,2BAA2B,MAAM;AACnD,MAAI,UAAU,SAAS,GAAG;AACxB,YACE,aAAa,QACb,6BAA6B,KAAK,yCAAyC,UAAU,GAAG,qFAEzF;AACD;;AAIF,MACE,uBAAuB,SACvB,uBAAuB,UAAU,MAEjC,UACE,mBAAmB,QACnB,uCAAuC,KAAK,qDAC7C;AAGH,yBAAuB,QAAQ;;;;;;AAOnC,SAAgB,4BAAoD;AAClE,QAAO;;;;;;;AAgBT,MAAM,2BAA2B;;;;AAKjC,SAAgB,2BAA2B,OAAyB;CAClE,MAAM,UAAU,MAAM,SAAS,yBAAyB;CACxD,MAAM,OAAiB,EAAE;AAEzB,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,YAAY,MAAM,MAAM;AAI9B,MAAI,CAAC,eAAe,IAAI,UAAU,IAAI,CAAC,KAAK,SAAS,UAAU,CAC7D,MAAK,KAAK,UAAU;;AAIxB,QAAO;;AAiCT,MAAM,oCAAoB,IAAI,SAAyC;;;;;;;;;;AAWvE,SAAgB,6BACd,QACwB;AACxB,KAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,QAAO,EAAE;CAGX,MAAM,SAAS,kBAAkB,IAAI,OAAiB;AACtD,KAAI,WAAW,KAAA,EAAW,QAAO;CAEjC,MAAM,cAAsC,EAAE;AAE9C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAE/C,KAAI,IAAI,WAAW,IAAI,IAAI,OAAO,UAAU,UAAU;AAEpD,MAAI,CAAC,2BAA2B,KAAK,IAAI,CACvC;AAIF,MAAI,eAAe,IAAI,IAAI,CACzB;AAIF,MACE,QAAQ,YACR,QAAQ,WACR,QAAQ,aACR,QAAQ,UACR,IAAI,WAAW,KAAK,CAEpB;EAIF,MAAM,YAAY,2BAA2B,MAAM;AACnD,MAAI,UAAU,SAAS,GAAG;AACxB,YACE,mBAAmB,OACnB,6BAA6B,IAAI,yCAAyC,UAAU,GAAG,qFAExF;AACD;;AAGF,cAAY,OAAO;;AAIvB,mBAAkB,IAAI,QAAkB,YAAY;AAEpD,QAAO;;;;;AAMT,SAAgB,yBACd,QACA,cACoB;AAGpB,QAAO;EACL,uBAHkB,SAAS,6BAA6B,OAAO,GAAG,EAAE;EAIpE,wBAAwB,2BAA2B;EACnD;EACD;;;;;;AAOH,SAAgB,uBACd,UACA,KACe;AAEf,KAAI,IAAI,sBAAsB,UAC5B,QAAO,IAAI,sBAAsB;AAInC,KAAI,IAAI,uBAAuB,UAC7B,QAAO,IAAI,uBAAuB;AAIpC,UACE,mBAAmB,YACnB,uCAAuC,SAAS,0CACP,SAAS,6CACnD;AAED,QAAO;;;;;;AAqDT,SAAgB,0BAA0B,WAA2B;CAEnE,IAAI,SAAS;AAGb,UAAS,OAAO,QAAQ,UAAU,QAAQ;AAG1C,UAAS,OAAO,QAAQ,UAAU,SAAS;AAG3C,UAAS,OAAO,QAAQ,WAAW,cAAc;AAGjD,UAAS,OAAO,QAAQ,WAAW,aAAa;AAEhD,QAAO;;;;;AAMT,SAAgB,iBAAiB,OAAuB;AAEtD,QAAO,MAAM,QAAQ,2BAA2B,GAAG,QAAQ;AACzD,SAAO,qBAAqB,IAAI;GAChC;;;;;;;AAwTJ,SAAgB,kBAAkB,GAAmB;CACnD,IAAI,QAAQ;AAEZ,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,EAAE,OAAO,IAAK;UACT,EAAE,OAAO,IAAK;UACd,EAAE,OAAO,OAAO,UAAU,EAAG,QAAO;AAG/C,QAAO"}
|