@salty-css/core 0.1.0-alpha.27 → 0.1.0-alpha.28

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 CHANGED
@@ -321,6 +321,88 @@ Example usage:
321
321
  styled('div', { base: { textStyle: 'headline.large', card: '20px' } });
322
322
  ```
323
323
 
324
+ ### Template variants
325
+
326
+ Static templates can opt into named variants by switching a node from a plain styles object to a "rich" shape with `base` and `variants` keys — the same authoring API as `styled`. Variants declared at a parent node are inherited by every descendant leaf, so one declaration of `weight` on `heading` flows down to `heading.large`, `heading.small`, etc.
327
+
328
+ ```ts
329
+ // /styles/templates.css.ts
330
+ import { defineTemplates } from '@salty-css/core/factories';
331
+
332
+ export default defineTemplates({
333
+ textStyle: {
334
+ heading: {
335
+ // Rich node: variants declared here are available to every child leaf.
336
+ base: {
337
+ fontFamily: '{fontFamily.heading}',
338
+ lineHeight: '1.1em',
339
+ },
340
+ variants: {
341
+ weight: {
342
+ light: { fontWeight: 300 },
343
+ regular: { fontWeight: 500 },
344
+ heavy: { fontWeight: 800 },
345
+ },
346
+ italic: {
347
+ true: { fontStyle: 'italic' },
348
+ },
349
+ },
350
+ defaultVariants: {
351
+ weight: 'regular',
352
+ },
353
+ compoundVariants: [
354
+ // Applied when ALL listed axes match.
355
+ { weight: 'heavy', italic: true, css: { letterSpacing: '-0.01em' } },
356
+ ],
357
+ // Leaves can be plain styles…
358
+ small: { fontSize: '{fontSize.heading.small}' },
359
+ regular: { fontSize: '{fontSize.heading.regular}' },
360
+ // …or rich, with their own additional variants / overrides.
361
+ large: {
362
+ base: { fontSize: '{fontSize.heading.large}' },
363
+ variants: {
364
+ weight: {
365
+ // Override the inherited bundle just for `large`.
366
+ heavy: { fontWeight: 900, letterSpacing: '-0.02em' },
367
+ },
368
+ },
369
+ },
370
+ },
371
+ },
372
+ });
373
+ ```
374
+
375
+ Apply variants at the call site in either of two equivalent forms — string query or object:
376
+
377
+ ```ts
378
+ styled('h1', {
379
+ base: {
380
+ // String form: `path@axis=value&axis=value&boolFlag`
381
+ textStyle: 'heading.large@weight=heavy&italic',
382
+ },
383
+ });
384
+
385
+ styled('h2', {
386
+ base: {
387
+ // Object form: `name` is the dot-path, the rest are axis values.
388
+ textStyle: { name: 'heading.large', weight: 'heavy', italic: true },
389
+ },
390
+ });
391
+
392
+ // No variants — existing simple usage still works.
393
+ styled('p', { base: { textStyle: 'heading.regular' } });
394
+ ```
395
+
396
+ Behaviour worth knowing:
397
+
398
+ - **Inheritance is parent → leaf only.** A leaf sees variants from its ancestors; siblings and children are invisible.
399
+ - **Closest wins.** If the same axis/value bundle is declared at multiple levels, the deepest one replaces (not merges) the ancestor's bundle for that single call.
400
+ - **`defaultVariants` apply when the call site omits an axis.** Walked bottom-up, same closest-wins rule.
401
+ - **`compoundVariants` (AND) and `anyOfVariants` (OR) are accumulated top-down** across the path — every matching rule contributes.
402
+ - **Boolean axes accept a shorthand.** `@italic` is equivalent to `@italic=true`; in object form pass `italic: true`.
403
+ - **Reserved keys** inside a rich node: `base`, `variants`, `defaultVariants`, `compoundVariants`, `anyOfVariants`. Don't use `name` as an axis (reserved for the object call-site form).
404
+ - **Function templates** (e.g. `card: (v) => ({ … })`) don't support variants — keep them as plain functions.
405
+
324
406
  ## Custom fonts
325
407
 
326
408
  Register custom fonts that will be emitted as `@font-face` declarations and exposed as a CSS variable. Mirrors the developer experience of Next.js / Astro font loaders, but generated at build time alongside the rest of your Salty CSS output.
@@ -2,7 +2,7 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- const parseStyles = require("./parse-styles-jPtMfgXH.cjs");
5
+ const parseStyles = require("./parse-styles-BFoV7HiL.cjs");
6
6
  const dashCase = require("./dash-case-DIwKaYgE.cjs");
7
7
  const toHash = require("./to-hash-C05Y906F.cjs");
8
8
  class StylesGenerator {
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { p as parseAndJoinStyles } from "./parse-styles--vHKY6Mw.js";
4
+ import { p as parseAndJoinStyles } from "./parse-styles-CtA-RKqt.js";
5
5
  import { d as dashCase } from "./dash-case-DblXvymC.js";
6
6
  import { t as toHash } from "./to-hash-DAN2LcHK.js";
7
7
  class StylesGenerator {
@@ -14,7 +14,7 @@ const dashCase = require("../dash-case-DIwKaYgE.cjs");
14
14
  const toHash = require("../to-hash-C05Y906F.cjs");
15
15
  const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
16
16
  const module$1 = require("module");
17
- const parseStyles = require("../parse-styles-jPtMfgXH.cjs");
17
+ const parseStyles = require("../parse-styles-BFoV7HiL.cjs");
18
18
  const css_merge = require("../css/merge.cjs");
19
19
  const parsers_index = require("../parsers/index.cjs");
20
20
  const moduleType = require("../util/module-type");
@@ -553,6 +553,7 @@ ${css}
553
553
  const templates = css_merge.mergeObjects(config.templates, generationResults.templates);
554
554
  const templateStylesString = await parsers_index.parseTemplates(templates);
555
555
  const templateTokens = parsers_index.getTemplateTypes(templates);
556
+ const templateVariantMaps = parsers_index.getTemplateVariantMaps(templates);
556
557
  fs.writeFileSync(templateStylesPath, `@layer templates { ${templateStylesString} }`);
557
558
  configCacheContent.templates = templates;
558
559
  const importsPath = path.join(destDir, "css/_imports.css");
@@ -592,16 +593,38 @@ ${css}
592
593
  }
593
594
  const tsTokensPath = path.join(destDir, "types/css-tokens.d.ts");
594
595
  const tsVariableTokens = [...variableTokens].join("|");
596
+ const pascal = (str) => str.split(/[.\-_]/).filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
597
+ const templateVariantMapEntries = Object.entries(templateVariantMaps);
598
+ const hasVariantMaps = templateVariantMapEntries.some(([, pathMap]) => Object.keys(pathMap).length > 0);
599
+ const tsTemplateVariantMap = hasVariantMaps ? `type TemplateVariantTokens = {
600
+ ${templateVariantMapEntries.filter(([, pathMap]) => Object.keys(pathMap).length > 0).map(([templateKey, pathMap]) => {
601
+ const pathEntries = Object.entries(pathMap).map(
602
+ ([dotPath, axes]) => `'${dotPath}': { ${Object.entries(axes).map(([axis, valueType]) => `${axis}?: ${valueType}`).join("; ")} }`
603
+ ).join(";\n ");
604
+ return `${templateKey}: {
605
+ ${pathEntries}
606
+ }`;
607
+ }).join(";\n ")}
608
+ }` : `type TemplateVariantTokens = Record<string, Record<string, Record<string, never>>>;`;
609
+ const tsTemplateVariantAliases = templateVariantMapEntries.flatMap(
610
+ ([templateKey, pathMap]) => Object.keys(pathMap).map(
611
+ (dotPath) => `type ${pascal(templateKey)}${pascal(dotPath)}Variants = TemplateVariantTokens['${templateKey}']['${dotPath}'];`
612
+ )
613
+ ).join("\n ");
595
614
  const tsTokensTypes = `
596
615
  // Variable types
597
- type VariableTokens = ${tsVariableTokens || `''`};
616
+ type VariableTokens = ${tsVariableTokens || `''`};
598
617
  type PropertyValueToken = \`{\${VariableTokens}}\`;
599
-
618
+
600
619
  // Template types
601
620
  type TemplateTokens = {
602
621
  ${Object.entries(templateTokens).map(([key, value]) => `${key}?: ${value}`).join("\n")}
603
622
  }
604
-
623
+
624
+ // Template variant types (per docs/template-variants-spec.md §7)
625
+ ${tsTemplateVariantMap}
626
+ ${tsTemplateVariantAliases}
627
+
605
628
  // Media query types
606
629
  type MediaQueryKeys = ${mediaQueryKeys || `''`};
607
630
  `;
@@ -12,9 +12,9 @@ import { d as dashCase } from "../dash-case-DblXvymC.js";
12
12
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
13
13
  import { d as defineTemplates } from "../define-templates-CVhhgPnd.js";
14
14
  import { createRequire } from "module";
15
- import { p as parseAndJoinStyles, b as parseVariableTokens } from "../parse-styles--vHKY6Mw.js";
15
+ import { p as parseAndJoinStyles, b as parseVariableTokens } from "../parse-styles-CtA-RKqt.js";
16
16
  import { mergeObjects, mergeFactories } from "../css/merge.js";
17
- import { parseTemplates, getTemplateTypes } from "../parsers/index.js";
17
+ import { parseTemplates, getTemplateTypes, getTemplateVariantMaps } from "../parsers/index.js";
18
18
  import { detectCurrentModuleType } from "../util/module-type";
19
19
  import console from "console";
20
20
  const logger = createLogger({
@@ -534,6 +534,7 @@ ${css}
534
534
  const templates = mergeObjects(config.templates, generationResults.templates);
535
535
  const templateStylesString = await parseTemplates(templates);
536
536
  const templateTokens = getTemplateTypes(templates);
537
+ const templateVariantMaps = getTemplateVariantMaps(templates);
537
538
  writeFileSync(templateStylesPath, `@layer templates { ${templateStylesString} }`);
538
539
  configCacheContent.templates = templates;
539
540
  const importsPath = join(destDir, "css/_imports.css");
@@ -573,16 +574,38 @@ ${css}
573
574
  }
574
575
  const tsTokensPath = join(destDir, "types/css-tokens.d.ts");
575
576
  const tsVariableTokens = [...variableTokens].join("|");
577
+ const pascal = (str) => str.split(/[.\-_]/).filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
578
+ const templateVariantMapEntries = Object.entries(templateVariantMaps);
579
+ const hasVariantMaps = templateVariantMapEntries.some(([, pathMap]) => Object.keys(pathMap).length > 0);
580
+ const tsTemplateVariantMap = hasVariantMaps ? `type TemplateVariantTokens = {
581
+ ${templateVariantMapEntries.filter(([, pathMap]) => Object.keys(pathMap).length > 0).map(([templateKey, pathMap]) => {
582
+ const pathEntries = Object.entries(pathMap).map(
583
+ ([dotPath, axes]) => `'${dotPath}': { ${Object.entries(axes).map(([axis, valueType]) => `${axis}?: ${valueType}`).join("; ")} }`
584
+ ).join(";\n ");
585
+ return `${templateKey}: {
586
+ ${pathEntries}
587
+ }`;
588
+ }).join(";\n ")}
589
+ }` : `type TemplateVariantTokens = Record<string, Record<string, Record<string, never>>>;`;
590
+ const tsTemplateVariantAliases = templateVariantMapEntries.flatMap(
591
+ ([templateKey, pathMap]) => Object.keys(pathMap).map(
592
+ (dotPath) => `type ${pascal(templateKey)}${pascal(dotPath)}Variants = TemplateVariantTokens['${templateKey}']['${dotPath}'];`
593
+ )
594
+ ).join("\n ");
576
595
  const tsTokensTypes = `
577
596
  // Variable types
578
- type VariableTokens = ${tsVariableTokens || `''`};
597
+ type VariableTokens = ${tsVariableTokens || `''`};
579
598
  type PropertyValueToken = \`{\${VariableTokens}}\`;
580
-
599
+
581
600
  // Template types
582
601
  type TemplateTokens = {
583
602
  ${Object.entries(templateTokens).map(([key, value]) => `${key}?: ${value}`).join("\n")}
584
603
  }
585
-
604
+
605
+ // Template variant types (per docs/template-variants-spec.md §7)
606
+ ${tsTemplateVariantMap}
607
+ ${tsTemplateVariantAliases}
608
+
586
609
  // Media query types
587
610
  type MediaQueryKeys = ${mediaQueryKeys || `''`};
588
611
  `;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-jPtMfgXH.cjs");
3
+ const parseStyles = require("../parse-styles-BFoV7HiL.cjs");
4
4
  const toHash = require("../to-hash-C05Y906F.cjs");
5
5
  const cache_resolveDynamicConfigCache = require("../cache/resolve-dynamic-config-cache.cjs");
6
6
  const getDynamicStylesClassName = (styles) => {
@@ -1,4 +1,4 @@
1
- import { a as parseStyles } from "../parse-styles--vHKY6Mw.js";
1
+ import { a as parseStyles } from "../parse-styles-CtA-RKqt.js";
2
2
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
3
3
  import { resolveDynamicConfigCache } from "../cache/resolve-dynamic-config-cache.js";
4
4
  const getDynamicStylesClassName = (styles) => {
package/css/keyframes.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-jPtMfgXH.cjs");
3
+ const parseStyles = require("../parse-styles-BFoV7HiL.cjs");
4
4
  const toHash = require("../to-hash-C05Y906F.cjs");
5
5
  const keyframes = ({ animationName: _name, params: _params, appendInitialStyles, ...keyframes2 }) => {
6
6
  const modifyKeyframes = async (params = {}) => {
package/css/keyframes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { p as parseAndJoinStyles } from "../parse-styles--vHKY6Mw.js";
1
+ import { p as parseAndJoinStyles } from "../parse-styles-CtA-RKqt.js";
2
2
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
3
3
  const keyframes = ({ animationName: _name, params: _params, appendInitialStyles, ...keyframes2 }) => {
4
4
  const modifyKeyframes = async (params = {}) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const classNameGenerator = require("../class-name-generator-BIYysuhW.cjs");
3
+ const classNameGenerator = require("../class-name-generator-DZcz_NT4.cjs");
4
4
  const dashCase = require("../dash-case-DIwKaYgE.cjs");
5
5
  class StyledGenerator extends classNameGenerator.StylesGenerator {
6
6
  constructor(tagName, _params) {
@@ -1,5 +1,5 @@
1
- import { S as StylesGenerator } from "../class-name-generator-B2LriwKm.js";
2
- import { C } from "../class-name-generator-B2LriwKm.js";
1
+ import { S as StylesGenerator } from "../class-name-generator-zQ8zLbPI.js";
2
+ import { C } from "../class-name-generator-zQ8zLbPI.js";
3
3
  import { d as dashCase } from "../dash-case-DblXvymC.js";
4
4
  class StyledGenerator extends StylesGenerator {
5
5
  constructor(tagName, _params) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const classNameGenerator = require("../class-name-generator-BIYysuhW.cjs");
3
+ const classNameGenerator = require("../class-name-generator-DZcz_NT4.cjs");
4
4
  const classNameInstance = (params) => {
5
5
  const generator = new classNameGenerator.ClassNameGenerator(params);
6
6
  const createClass = (classNameStr) => {
@@ -1,4 +1,4 @@
1
- import { C as ClassNameGenerator } from "../class-name-generator-B2LriwKm.js";
1
+ import { C as ClassNameGenerator } from "../class-name-generator-zQ8zLbPI.js";
2
2
  const classNameInstance = (params) => {
3
3
  const generator = new ClassNameGenerator(params);
4
4
  const createClass = (classNameStr) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salty-css/core",
3
- "version": "0.1.0-alpha.27",
3
+ "version": "0.1.0-alpha.28",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "typings": "./dist/index.d.ts",
@@ -94,6 +94,170 @@ const reportParserIssue = (strict, message) => {
94
94
  const pseudoTypoRegex = /^&(hover|focus(-(visible|within))?|active|visited|checked|disabled|enabled|empty|target|first-child|last-child|first-of-type|last-of-type|placeholder|placeholder-shown|root)\b/;
95
95
  const templateLiteralLeftoverRegex = /\$\{[^}]+\}/;
96
96
  const bareAtRuleRegex = /^@(media|supports|container|layer)\s*$/;
97
+ const isRichTemplateNode = (node) => {
98
+ if (!node || typeof node !== "object") return false;
99
+ if (Array.isArray(node)) return false;
100
+ const keys = Object.keys(node);
101
+ return keys.includes("base") || keys.includes("variants");
102
+ };
103
+ const parseTemplateCallSite = (value) => {
104
+ if (typeof value === "string") {
105
+ const [rawPath, rawQuery] = value.split("@", 2);
106
+ const path = rawPath.split(".").filter(Boolean);
107
+ if (!path.length) return void 0;
108
+ const variants = {};
109
+ if (rawQuery) {
110
+ for (const segment of rawQuery.split("&")) {
111
+ if (!segment) continue;
112
+ const eq = segment.indexOf("=");
113
+ if (eq === -1) {
114
+ variants[segment.trim()] = true;
115
+ } else {
116
+ const axis = segment.slice(0, eq).trim();
117
+ const raw = segment.slice(eq + 1).trim();
118
+ if (!axis) continue;
119
+ if (raw === "true") variants[axis] = true;
120
+ else if (raw === "false") variants[axis] = false;
121
+ else variants[axis] = raw;
122
+ }
123
+ }
124
+ }
125
+ return { path, variants };
126
+ }
127
+ if (value && typeof value === "object" && !Array.isArray(value)) {
128
+ const obj = value;
129
+ const name = obj["name"];
130
+ if (typeof name !== "string") return void 0;
131
+ const path = name.split(".").filter(Boolean);
132
+ if (!path.length) return void 0;
133
+ const variants = {};
134
+ for (const [k, v] of Object.entries(obj)) {
135
+ if (k === "name") continue;
136
+ if (typeof v === "string" || typeof v === "boolean") variants[k] = v;
137
+ }
138
+ return { path, variants };
139
+ }
140
+ return void 0;
141
+ };
142
+ const normalizeAxisValue = (v) => {
143
+ if (v === void 0) return void 0;
144
+ return typeof v === "boolean" ? String(v) : v;
145
+ };
146
+ const buildPathStack = (root, path) => {
147
+ const stack = [{ node: root, isRich: isRichTemplateNode(root) }];
148
+ let cursor = root;
149
+ for (const segment of path) {
150
+ if (cursor == null || typeof cursor !== "object") return void 0;
151
+ const next = cursor[segment];
152
+ if (next === void 0) return void 0;
153
+ stack.push({ node: next, isRich: isRichTemplateNode(next) });
154
+ cursor = next;
155
+ }
156
+ return stack;
157
+ };
158
+ const pathHasRichNode = (root, path) => {
159
+ const stack = buildPathStack(root, path);
160
+ if (!stack) return false;
161
+ return stack.some((entry) => entry.isRich);
162
+ };
163
+ const matchesAll = (entry, effective) => {
164
+ for (const [axis, raw] of Object.entries(entry)) {
165
+ if (axis === "css") continue;
166
+ if (effective[axis] !== normalizeAxisValue(raw)) return false;
167
+ }
168
+ return true;
169
+ };
170
+ const matchesAny = (entry, effective) => {
171
+ let any = false;
172
+ for (const [axis, raw] of Object.entries(entry)) {
173
+ if (axis === "css") continue;
174
+ any = true;
175
+ if (effective[axis] === normalizeAxisValue(raw)) return true;
176
+ }
177
+ return !any;
178
+ };
179
+ const resolveRichTemplate = (root, path, callSiteVariants, templateName) => {
180
+ var _a, _b, _c, _d;
181
+ const stack = buildPathStack(root, path);
182
+ if (!stack) return void 0;
183
+ const rich = stack.map((entry) => {
184
+ if (entry.isRich) return entry.node;
185
+ if (entry.node && typeof entry.node === "object" && !Array.isArray(entry.node)) {
186
+ const onlyChildKeys = Object.keys(entry.node).every((k) => entry.node[k] && typeof entry.node[k] === "object" && !isRichTemplateNode(entry.node[k]));
187
+ return onlyChildKeys ? {} : { base: entry.node };
188
+ }
189
+ return {};
190
+ });
191
+ const declaredAxes = /* @__PURE__ */ new Set();
192
+ for (const r of rich) {
193
+ if (r.variants) for (const k of Object.keys(r.variants)) declaredAxes.add(k);
194
+ }
195
+ for (const r of rich) {
196
+ if (r.defaultVariants) for (const k of Object.keys(r.defaultVariants)) declaredAxes.add(k);
197
+ }
198
+ const effective = {};
199
+ for (const axis of declaredAxes) {
200
+ if (axis in callSiteVariants) {
201
+ effective[axis] = normalizeAxisValue(callSiteVariants[axis]);
202
+ continue;
203
+ }
204
+ for (let i = rich.length - 1; i >= 0; i--) {
205
+ const d = (_a = rich[i].defaultVariants) == null ? void 0 : _a[axis];
206
+ if (d !== void 0) {
207
+ effective[axis] = normalizeAxisValue(d);
208
+ break;
209
+ }
210
+ }
211
+ }
212
+ for (const [axis, raw] of Object.entries(callSiteVariants)) {
213
+ if (!declaredAxes.has(axis)) {
214
+ console.warn(`Template "${templateName}" path "${path.join(".")}" has no variant axis "${axis}"; ignored.`);
215
+ continue;
216
+ }
217
+ const wanted = normalizeAxisValue(raw);
218
+ let found = false;
219
+ for (const r of rich) {
220
+ if (((_b = r.variants) == null ? void 0 : _b[axis]) && wanted !== void 0 && wanted in r.variants[axis]) {
221
+ found = true;
222
+ break;
223
+ }
224
+ }
225
+ if (!found && wanted !== void 0) {
226
+ const anyBundle = rich.some((r) => r.variants && axis in r.variants);
227
+ if (anyBundle) {
228
+ console.warn(`Template "${templateName}" axis "${axis}" has no value "${wanted}" on path "${path.join(".")}"; ignored.`);
229
+ }
230
+ }
231
+ }
232
+ const acc = {};
233
+ for (const r of rich) {
234
+ if (r.base) Object.assign(acc, r.base);
235
+ }
236
+ for (const axis of Object.keys(effective)) {
237
+ const value = effective[axis];
238
+ if (value === void 0) continue;
239
+ for (let i = rich.length - 1; i >= 0; i--) {
240
+ const bundle = (_d = (_c = rich[i].variants) == null ? void 0 : _c[axis]) == null ? void 0 : _d[value];
241
+ if (bundle) {
242
+ Object.assign(acc, bundle);
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ for (const r of rich) {
248
+ if (r.compoundVariants) {
249
+ for (const entry of r.compoundVariants) {
250
+ if (matchesAll(entry, effective) && entry.css) Object.assign(acc, entry.css);
251
+ }
252
+ }
253
+ if (r.anyOfVariants) {
254
+ for (const entry of r.anyOfVariants) {
255
+ if (matchesAny(entry, effective) && entry.css) Object.assign(acc, entry.css);
256
+ }
257
+ }
258
+ }
259
+ return acc;
260
+ };
97
261
  const parseStyles = async (styles, currentScope = "", config, omitTemplates = false) => {
98
262
  if (!styles) throw new Error("No styles provided to parseStyles function!");
99
263
  const cssStyles = /* @__PURE__ */ new Set();
@@ -137,13 +301,29 @@ const parseStyles = async (styles, currentScope = "", config, omitTemplates = fa
137
301
  }
138
302
  if ((config == null ? void 0 : config.templates) && config.templates[_key]) {
139
303
  if (omitTemplates) return void 0;
140
- const path = value.split(".");
141
- const templateStyles = path.reduce((acc, key2) => acc[key2], config.templates[_key]);
142
- if (templateStyles) {
143
- const [result] = await parseStyles(templateStyles, "");
144
- return result;
304
+ const root = config.templates[_key];
305
+ const callSite = parseTemplateCallSite(value);
306
+ if (callSite) {
307
+ const { path, variants } = callSite;
308
+ const hasCallSiteVariants = Object.keys(variants).length > 0;
309
+ if (hasCallSiteVariants || pathHasRichNode(root, path)) {
310
+ const resolved2 = resolveRichTemplate(root, path, variants, _key);
311
+ if (resolved2) {
312
+ const [result] = await parseStyles(resolved2, "");
313
+ return result;
314
+ }
315
+ console.warn(`Template "${_key}" with path of "${path.join(".")}" was not found in config!`);
316
+ return void 0;
317
+ }
318
+ const templateStyles = path.reduce((acc, key2) => acc == null ? void 0 : acc[key2], root);
319
+ if (templateStyles) {
320
+ const [result] = await parseStyles(templateStyles, "");
321
+ return result;
322
+ }
323
+ console.warn(`Template "${_key}" with path of "${path.join(".")}" was not found in config!`);
324
+ return void 0;
145
325
  }
146
- console.warn(`Template "${_key}" with path of "${value}" was not found in config!`);
326
+ console.warn(`Template "${_key}" received an unsupported call-site value.`);
147
327
  return void 0;
148
328
  }
149
329
  const isVariantArrayKey = _key === "compoundVariants" || _key === "anyOfVariants";
@@ -323,6 +503,7 @@ const combineSelectors = (currentScope, key) => {
323
503
  }
324
504
  return combos.join(", ");
325
505
  };
506
+ exports.isRichTemplateNode = isRichTemplateNode;
326
507
  exports.parseAndJoinStyles = parseAndJoinStyles;
327
508
  exports.parseStyles = parseStyles;
328
509
  exports.parseValueModifiers = parseValueModifiers;
@@ -93,6 +93,170 @@ const reportParserIssue = (strict, message) => {
93
93
  const pseudoTypoRegex = /^&(hover|focus(-(visible|within))?|active|visited|checked|disabled|enabled|empty|target|first-child|last-child|first-of-type|last-of-type|placeholder|placeholder-shown|root)\b/;
94
94
  const templateLiteralLeftoverRegex = /\$\{[^}]+\}/;
95
95
  const bareAtRuleRegex = /^@(media|supports|container|layer)\s*$/;
96
+ const isRichTemplateNode = (node) => {
97
+ if (!node || typeof node !== "object") return false;
98
+ if (Array.isArray(node)) return false;
99
+ const keys = Object.keys(node);
100
+ return keys.includes("base") || keys.includes("variants");
101
+ };
102
+ const parseTemplateCallSite = (value) => {
103
+ if (typeof value === "string") {
104
+ const [rawPath, rawQuery] = value.split("@", 2);
105
+ const path = rawPath.split(".").filter(Boolean);
106
+ if (!path.length) return void 0;
107
+ const variants = {};
108
+ if (rawQuery) {
109
+ for (const segment of rawQuery.split("&")) {
110
+ if (!segment) continue;
111
+ const eq = segment.indexOf("=");
112
+ if (eq === -1) {
113
+ variants[segment.trim()] = true;
114
+ } else {
115
+ const axis = segment.slice(0, eq).trim();
116
+ const raw = segment.slice(eq + 1).trim();
117
+ if (!axis) continue;
118
+ if (raw === "true") variants[axis] = true;
119
+ else if (raw === "false") variants[axis] = false;
120
+ else variants[axis] = raw;
121
+ }
122
+ }
123
+ }
124
+ return { path, variants };
125
+ }
126
+ if (value && typeof value === "object" && !Array.isArray(value)) {
127
+ const obj = value;
128
+ const name = obj["name"];
129
+ if (typeof name !== "string") return void 0;
130
+ const path = name.split(".").filter(Boolean);
131
+ if (!path.length) return void 0;
132
+ const variants = {};
133
+ for (const [k, v] of Object.entries(obj)) {
134
+ if (k === "name") continue;
135
+ if (typeof v === "string" || typeof v === "boolean") variants[k] = v;
136
+ }
137
+ return { path, variants };
138
+ }
139
+ return void 0;
140
+ };
141
+ const normalizeAxisValue = (v) => {
142
+ if (v === void 0) return void 0;
143
+ return typeof v === "boolean" ? String(v) : v;
144
+ };
145
+ const buildPathStack = (root, path) => {
146
+ const stack = [{ node: root, isRich: isRichTemplateNode(root) }];
147
+ let cursor = root;
148
+ for (const segment of path) {
149
+ if (cursor == null || typeof cursor !== "object") return void 0;
150
+ const next = cursor[segment];
151
+ if (next === void 0) return void 0;
152
+ stack.push({ node: next, isRich: isRichTemplateNode(next) });
153
+ cursor = next;
154
+ }
155
+ return stack;
156
+ };
157
+ const pathHasRichNode = (root, path) => {
158
+ const stack = buildPathStack(root, path);
159
+ if (!stack) return false;
160
+ return stack.some((entry) => entry.isRich);
161
+ };
162
+ const matchesAll = (entry, effective) => {
163
+ for (const [axis, raw] of Object.entries(entry)) {
164
+ if (axis === "css") continue;
165
+ if (effective[axis] !== normalizeAxisValue(raw)) return false;
166
+ }
167
+ return true;
168
+ };
169
+ const matchesAny = (entry, effective) => {
170
+ let any = false;
171
+ for (const [axis, raw] of Object.entries(entry)) {
172
+ if (axis === "css") continue;
173
+ any = true;
174
+ if (effective[axis] === normalizeAxisValue(raw)) return true;
175
+ }
176
+ return !any;
177
+ };
178
+ const resolveRichTemplate = (root, path, callSiteVariants, templateName) => {
179
+ var _a, _b, _c, _d;
180
+ const stack = buildPathStack(root, path);
181
+ if (!stack) return void 0;
182
+ const rich = stack.map((entry) => {
183
+ if (entry.isRich) return entry.node;
184
+ if (entry.node && typeof entry.node === "object" && !Array.isArray(entry.node)) {
185
+ const onlyChildKeys = Object.keys(entry.node).every((k) => entry.node[k] && typeof entry.node[k] === "object" && !isRichTemplateNode(entry.node[k]));
186
+ return onlyChildKeys ? {} : { base: entry.node };
187
+ }
188
+ return {};
189
+ });
190
+ const declaredAxes = /* @__PURE__ */ new Set();
191
+ for (const r of rich) {
192
+ if (r.variants) for (const k of Object.keys(r.variants)) declaredAxes.add(k);
193
+ }
194
+ for (const r of rich) {
195
+ if (r.defaultVariants) for (const k of Object.keys(r.defaultVariants)) declaredAxes.add(k);
196
+ }
197
+ const effective = {};
198
+ for (const axis of declaredAxes) {
199
+ if (axis in callSiteVariants) {
200
+ effective[axis] = normalizeAxisValue(callSiteVariants[axis]);
201
+ continue;
202
+ }
203
+ for (let i = rich.length - 1; i >= 0; i--) {
204
+ const d = (_a = rich[i].defaultVariants) == null ? void 0 : _a[axis];
205
+ if (d !== void 0) {
206
+ effective[axis] = normalizeAxisValue(d);
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ for (const [axis, raw] of Object.entries(callSiteVariants)) {
212
+ if (!declaredAxes.has(axis)) {
213
+ console.warn(`Template "${templateName}" path "${path.join(".")}" has no variant axis "${axis}"; ignored.`);
214
+ continue;
215
+ }
216
+ const wanted = normalizeAxisValue(raw);
217
+ let found = false;
218
+ for (const r of rich) {
219
+ if (((_b = r.variants) == null ? void 0 : _b[axis]) && wanted !== void 0 && wanted in r.variants[axis]) {
220
+ found = true;
221
+ break;
222
+ }
223
+ }
224
+ if (!found && wanted !== void 0) {
225
+ const anyBundle = rich.some((r) => r.variants && axis in r.variants);
226
+ if (anyBundle) {
227
+ console.warn(`Template "${templateName}" axis "${axis}" has no value "${wanted}" on path "${path.join(".")}"; ignored.`);
228
+ }
229
+ }
230
+ }
231
+ const acc = {};
232
+ for (const r of rich) {
233
+ if (r.base) Object.assign(acc, r.base);
234
+ }
235
+ for (const axis of Object.keys(effective)) {
236
+ const value = effective[axis];
237
+ if (value === void 0) continue;
238
+ for (let i = rich.length - 1; i >= 0; i--) {
239
+ const bundle = (_d = (_c = rich[i].variants) == null ? void 0 : _c[axis]) == null ? void 0 : _d[value];
240
+ if (bundle) {
241
+ Object.assign(acc, bundle);
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ for (const r of rich) {
247
+ if (r.compoundVariants) {
248
+ for (const entry of r.compoundVariants) {
249
+ if (matchesAll(entry, effective) && entry.css) Object.assign(acc, entry.css);
250
+ }
251
+ }
252
+ if (r.anyOfVariants) {
253
+ for (const entry of r.anyOfVariants) {
254
+ if (matchesAny(entry, effective) && entry.css) Object.assign(acc, entry.css);
255
+ }
256
+ }
257
+ }
258
+ return acc;
259
+ };
96
260
  const parseStyles = async (styles, currentScope = "", config, omitTemplates = false) => {
97
261
  if (!styles) throw new Error("No styles provided to parseStyles function!");
98
262
  const cssStyles = /* @__PURE__ */ new Set();
@@ -136,13 +300,29 @@ const parseStyles = async (styles, currentScope = "", config, omitTemplates = fa
136
300
  }
137
301
  if ((config == null ? void 0 : config.templates) && config.templates[_key]) {
138
302
  if (omitTemplates) return void 0;
139
- const path = value.split(".");
140
- const templateStyles = path.reduce((acc, key2) => acc[key2], config.templates[_key]);
141
- if (templateStyles) {
142
- const [result] = await parseStyles(templateStyles, "");
143
- return result;
303
+ const root = config.templates[_key];
304
+ const callSite = parseTemplateCallSite(value);
305
+ if (callSite) {
306
+ const { path, variants } = callSite;
307
+ const hasCallSiteVariants = Object.keys(variants).length > 0;
308
+ if (hasCallSiteVariants || pathHasRichNode(root, path)) {
309
+ const resolved2 = resolveRichTemplate(root, path, variants, _key);
310
+ if (resolved2) {
311
+ const [result] = await parseStyles(resolved2, "");
312
+ return result;
313
+ }
314
+ console.warn(`Template "${_key}" with path of "${path.join(".")}" was not found in config!`);
315
+ return void 0;
316
+ }
317
+ const templateStyles = path.reduce((acc, key2) => acc == null ? void 0 : acc[key2], root);
318
+ if (templateStyles) {
319
+ const [result] = await parseStyles(templateStyles, "");
320
+ return result;
321
+ }
322
+ console.warn(`Template "${_key}" with path of "${path.join(".")}" was not found in config!`);
323
+ return void 0;
144
324
  }
145
- console.warn(`Template "${_key}" with path of "${value}" was not found in config!`);
325
+ console.warn(`Template "${_key}" received an unsupported call-site value.`);
146
326
  return void 0;
147
327
  }
148
328
  const isVariantArrayKey = _key === "compoundVariants" || _key === "anyOfVariants";
@@ -327,6 +507,7 @@ export {
327
507
  parseVariableTokens as b,
328
508
  parseValueModifiers as c,
329
509
  parseValueTokens as d,
510
+ isRichTemplateNode as i,
330
511
  parseAndJoinStyles as p,
331
512
  reportParserIssue as r
332
513
  };
package/parsers/index.cjs CHANGED
@@ -1,11 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-jPtMfgXH.cjs");
3
+ const parseStyles = require("../parse-styles-BFoV7HiL.cjs");
4
4
  const dashCase = require("../dash-case-DIwKaYgE.cjs");
5
5
  const toHash = require("../to-hash-C05Y906F.cjs");
6
+ const RICH_META_KEYS = /* @__PURE__ */ new Set(["base", "variants", "defaultVariants", "compoundVariants", "anyOfVariants"]);
7
+ const isChildEntry = (key, value) => {
8
+ if (RICH_META_KEYS.has(key)) return false;
9
+ return !!value && typeof value === "object" && !Array.isArray(value);
10
+ };
6
11
  const parseTemplates = async (obj, path = []) => {
7
12
  if (!obj) return "";
8
13
  const classes = [];
14
+ if (parseStyles.isRichTemplateNode(obj)) {
15
+ const rich = obj;
16
+ if (rich.base) {
17
+ const className = path.map((p) => dashCase.dashCase(String(p))).join("-");
18
+ const hashClass = "t_" + toHash.toHash(className, 4);
19
+ const result = await parseStyles.parseAndJoinStyles(rich.base, `.${className}, .${hashClass}`);
20
+ classes.push(result);
21
+ }
22
+ for (const [key, value] of Object.entries(rich)) {
23
+ if (!isChildEntry(key, value)) continue;
24
+ const result = await parseTemplates(value, [...path, key.trim()]);
25
+ classes.push(result);
26
+ }
27
+ return classes.join("\n");
28
+ }
9
29
  const levelStyles = {};
10
30
  for (const [key, value] of Object.entries(obj)) {
11
31
  if (typeof value === "function") ;
@@ -18,7 +38,7 @@ const parseTemplates = async (obj, path = []) => {
18
38
  }
19
39
  }
20
40
  if (Object.keys(levelStyles).length) {
21
- const className = path.map(dashCase.dashCase).join("-");
41
+ const className = path.map((p) => dashCase.dashCase(String(p))).join("-");
22
42
  const hashClass = "t_" + toHash.toHash(className, 4);
23
43
  const result = await parseStyles.parseAndJoinStyles(levelStyles, `.${className}, .${hashClass}`);
24
44
  classes.push(result);
@@ -40,13 +60,69 @@ const getTemplateTypes = (templates) => {
40
60
  };
41
61
  const getTemplateTokens = (templates, parent = "", templateTokens = /* @__PURE__ */ new Set()) => {
42
62
  if (!templates) return [];
63
+ if (parseStyles.isRichTemplateNode(templates)) {
64
+ if (parent) templateTokens.add(parent);
65
+ Object.entries(templates).forEach(([key, value]) => {
66
+ if (!isChildEntry(key, value)) return;
67
+ const keyValue = parent ? `${parent}.${key}` : key;
68
+ getTemplateTokens(value, keyValue, templateTokens);
69
+ });
70
+ return [...templateTokens];
71
+ }
43
72
  Object.entries(templates).forEach(([key, value]) => {
44
73
  const keyValue = parent ? `${parent}.${key}` : key;
45
- if (typeof value === "object") return getTemplateTokens(value, keyValue, templateTokens);
74
+ if (value && typeof value === "object") return getTemplateTokens(value, keyValue, templateTokens);
46
75
  return templateTokens.add(parent);
47
76
  });
48
77
  return [...templateTokens];
49
78
  };
79
+ const getTemplateVariantMaps = (templates) => {
80
+ const result = {};
81
+ if (!templates) return result;
82
+ for (const [topKey, topNode] of Object.entries(templates)) {
83
+ if (!topNode || typeof topNode !== "object" || typeof topNode === "function") continue;
84
+ walk(topNode, [], result[topKey] || (result[topKey] = {}), {});
85
+ }
86
+ return result;
87
+ };
88
+ const mergeAxes = (inherited, node) => {
89
+ const next = {};
90
+ for (const [axis, vals] of Object.entries(inherited)) {
91
+ next[axis] = { ...vals };
92
+ }
93
+ if (parseStyles.isRichTemplateNode(node) && node.variants) {
94
+ for (const [axis, valueMap] of Object.entries(node.variants)) {
95
+ next[axis] = next[axis] || {};
96
+ for (const value of Object.keys(valueMap)) next[axis][value] = true;
97
+ }
98
+ }
99
+ return next;
100
+ };
101
+ const walk = (node, path, out, inheritedAxes) => {
102
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
103
+ const axes = mergeAxes(inheritedAxes, node);
104
+ const dot = path.join(".");
105
+ if (path.length && (parseStyles.isRichTemplateNode(node) || Object.keys(axes).length)) {
106
+ const axisMap = {};
107
+ for (const [axis, valSet] of Object.entries(axes)) {
108
+ const values = Object.keys(valSet);
109
+ const isBooleanOnly = values.length === 1 && values[0] === "true";
110
+ axisMap[axis] = isBooleanOnly ? "boolean" : values.map((v) => `"${v}"`).join(" | ");
111
+ }
112
+ if (Object.keys(axisMap).length) out[dot] = axisMap;
113
+ }
114
+ if (parseStyles.isRichTemplateNode(node)) {
115
+ for (const [key, value] of Object.entries(node)) {
116
+ if (!isChildEntry(key, value)) continue;
117
+ walk(value, [...path, key.trim()], out, axes);
118
+ }
119
+ return;
120
+ }
121
+ for (const [key, value] of Object.entries(node)) {
122
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
123
+ walk(value, [...path, key.trim()], out, axes);
124
+ }
125
+ };
50
126
  exports.parseAndJoinStyles = parseStyles.parseAndJoinStyles;
51
127
  exports.parseStyles = parseStyles.parseStyles;
52
128
  exports.parseValueModifiers = parseStyles.parseValueModifiers;
@@ -56,4 +132,5 @@ exports.reportParserIssue = parseStyles.reportParserIssue;
56
132
  exports.getTemplateKeys = getTemplateKeys;
57
133
  exports.getTemplateTokens = getTemplateTokens;
58
134
  exports.getTemplateTypes = getTemplateTypes;
135
+ exports.getTemplateVariantMaps = getTemplateVariantMaps;
59
136
  exports.parseTemplates = parseTemplates;
package/parsers/index.js CHANGED
@@ -1,10 +1,30 @@
1
- import { p as parseAndJoinStyles } from "../parse-styles--vHKY6Mw.js";
2
- import { a, c, d, b, r } from "../parse-styles--vHKY6Mw.js";
1
+ import { i as isRichTemplateNode, p as parseAndJoinStyles } from "../parse-styles-CtA-RKqt.js";
2
+ import { a, c, d, b, r } from "../parse-styles-CtA-RKqt.js";
3
3
  import { d as dashCase } from "../dash-case-DblXvymC.js";
4
4
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
5
+ const RICH_META_KEYS = /* @__PURE__ */ new Set(["base", "variants", "defaultVariants", "compoundVariants", "anyOfVariants"]);
6
+ const isChildEntry = (key, value) => {
7
+ if (RICH_META_KEYS.has(key)) return false;
8
+ return !!value && typeof value === "object" && !Array.isArray(value);
9
+ };
5
10
  const parseTemplates = async (obj, path = []) => {
6
11
  if (!obj) return "";
7
12
  const classes = [];
13
+ if (isRichTemplateNode(obj)) {
14
+ const rich = obj;
15
+ if (rich.base) {
16
+ const className = path.map((p) => dashCase(String(p))).join("-");
17
+ const hashClass = "t_" + toHash(className, 4);
18
+ const result = await parseAndJoinStyles(rich.base, `.${className}, .${hashClass}`);
19
+ classes.push(result);
20
+ }
21
+ for (const [key, value] of Object.entries(rich)) {
22
+ if (!isChildEntry(key, value)) continue;
23
+ const result = await parseTemplates(value, [...path, key.trim()]);
24
+ classes.push(result);
25
+ }
26
+ return classes.join("\n");
27
+ }
8
28
  const levelStyles = {};
9
29
  for (const [key, value] of Object.entries(obj)) {
10
30
  if (typeof value === "function") ;
@@ -17,7 +37,7 @@ const parseTemplates = async (obj, path = []) => {
17
37
  }
18
38
  }
19
39
  if (Object.keys(levelStyles).length) {
20
- const className = path.map(dashCase).join("-");
40
+ const className = path.map((p) => dashCase(String(p))).join("-");
21
41
  const hashClass = "t_" + toHash(className, 4);
22
42
  const result = await parseAndJoinStyles(levelStyles, `.${className}, .${hashClass}`);
23
43
  classes.push(result);
@@ -39,17 +59,74 @@ const getTemplateTypes = (templates) => {
39
59
  };
40
60
  const getTemplateTokens = (templates, parent = "", templateTokens = /* @__PURE__ */ new Set()) => {
41
61
  if (!templates) return [];
62
+ if (isRichTemplateNode(templates)) {
63
+ if (parent) templateTokens.add(parent);
64
+ Object.entries(templates).forEach(([key, value]) => {
65
+ if (!isChildEntry(key, value)) return;
66
+ const keyValue = parent ? `${parent}.${key}` : key;
67
+ getTemplateTokens(value, keyValue, templateTokens);
68
+ });
69
+ return [...templateTokens];
70
+ }
42
71
  Object.entries(templates).forEach(([key, value]) => {
43
72
  const keyValue = parent ? `${parent}.${key}` : key;
44
- if (typeof value === "object") return getTemplateTokens(value, keyValue, templateTokens);
73
+ if (value && typeof value === "object") return getTemplateTokens(value, keyValue, templateTokens);
45
74
  return templateTokens.add(parent);
46
75
  });
47
76
  return [...templateTokens];
48
77
  };
78
+ const getTemplateVariantMaps = (templates) => {
79
+ const result = {};
80
+ if (!templates) return result;
81
+ for (const [topKey, topNode] of Object.entries(templates)) {
82
+ if (!topNode || typeof topNode !== "object" || typeof topNode === "function") continue;
83
+ walk(topNode, [], result[topKey] || (result[topKey] = {}), {});
84
+ }
85
+ return result;
86
+ };
87
+ const mergeAxes = (inherited, node) => {
88
+ const next = {};
89
+ for (const [axis, vals] of Object.entries(inherited)) {
90
+ next[axis] = { ...vals };
91
+ }
92
+ if (isRichTemplateNode(node) && node.variants) {
93
+ for (const [axis, valueMap] of Object.entries(node.variants)) {
94
+ next[axis] = next[axis] || {};
95
+ for (const value of Object.keys(valueMap)) next[axis][value] = true;
96
+ }
97
+ }
98
+ return next;
99
+ };
100
+ const walk = (node, path, out, inheritedAxes) => {
101
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
102
+ const axes = mergeAxes(inheritedAxes, node);
103
+ const dot = path.join(".");
104
+ if (path.length && (isRichTemplateNode(node) || Object.keys(axes).length)) {
105
+ const axisMap = {};
106
+ for (const [axis, valSet] of Object.entries(axes)) {
107
+ const values = Object.keys(valSet);
108
+ const isBooleanOnly = values.length === 1 && values[0] === "true";
109
+ axisMap[axis] = isBooleanOnly ? "boolean" : values.map((v) => `"${v}"`).join(" | ");
110
+ }
111
+ if (Object.keys(axisMap).length) out[dot] = axisMap;
112
+ }
113
+ if (isRichTemplateNode(node)) {
114
+ for (const [key, value] of Object.entries(node)) {
115
+ if (!isChildEntry(key, value)) continue;
116
+ walk(value, [...path, key.trim()], out, axes);
117
+ }
118
+ return;
119
+ }
120
+ for (const [key, value] of Object.entries(node)) {
121
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
122
+ walk(value, [...path, key.trim()], out, axes);
123
+ }
124
+ };
49
125
  export {
50
126
  getTemplateKeys,
51
127
  getTemplateTokens,
52
128
  getTemplateTypes,
129
+ getTemplateVariantMaps,
53
130
  parseAndJoinStyles,
54
131
  a as parseStyles,
55
132
  parseTemplates,
@@ -2,3 +2,13 @@ export declare const parseTemplates: <T extends object>(obj: T, path?: PropertyK
2
2
  export declare const getTemplateKeys: <T extends object>(templates: T) => string[];
3
3
  export declare const getTemplateTypes: <T extends object>(templates: T) => Record<string, string>;
4
4
  export declare const getTemplateTokens: <T extends object>(templates: T, parent?: string, templateTokens?: Set<string>) => string[];
5
+ /**
6
+ * Walk every rich-template path in a templates root and emit, for each reachable dot-path, the set of
7
+ * variant axes valid at that path (axis name → union of value names, with `boolean` for axes whose only
8
+ * declared value is `true`).
9
+ *
10
+ * Inheritance: a leaf's axis enum is the union of its own values and every ancestor's values for the
11
+ * same axis. Matches the resolver's bottom-up lookup semantics — anything reachable via fallback is a
12
+ * valid call-site value.
13
+ */
14
+ export declare const getTemplateVariantMaps: (templates: Record<string, any>) => Record<string, Record<string, Record<string, string>>>;
@@ -0,0 +1,21 @@
1
+ import { RichTemplateNode } from '../types/config-types';
2
+ export type TemplateCallSite = {
3
+ path: string[];
4
+ variants: Record<string, string | boolean>;
5
+ };
6
+ export declare const isRichTemplateNode: (node: unknown) => node is RichTemplateNode;
7
+ /**
8
+ * Parse a template call-site value into its dot-path and the axis values requested at the call site.
9
+ *
10
+ * String form: `'heading.large@weight=heavy&emphasis=loud&italic'`
11
+ * Object form: `{ name: 'heading.large', weight: 'heavy', emphasis: 'loud', italic: true }`
12
+ */
13
+ export declare const parseTemplateCallSite: (value: unknown) => TemplateCallSite | undefined;
14
+ export declare const pathHasRichNode: (root: any, path: string[]) => boolean;
15
+ /**
16
+ * Resolve a rich-template invocation into a flat CSS-in-JS object.
17
+ * Implements docs/template-variants-spec.md §5 (parent-to-leaf inheritance, replace semantics).
18
+ *
19
+ * Returns `undefined` if the path doesn't resolve. Emits console.warn for unknown axes / values.
20
+ */
21
+ export declare const resolveRichTemplate: (root: any, path: string[], callSiteVariants: Record<string, string | boolean>, templateName: string) => Record<string, any> | undefined;
package/runtime/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-jPtMfgXH.cjs");
3
+ const parseStyles = require("../parse-styles-BFoV7HiL.cjs");
4
4
  const defineRuntime = (config) => {
5
5
  const getDynamicStylesCss = async (styles, scope) => {
6
6
  const parsed = await parseStyles.parseStyles(styles, scope, config);
package/runtime/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as parseStyles } from "../parse-styles--vHKY6Mw.js";
1
+ import { a as parseStyles } from "../parse-styles-CtA-RKqt.js";
2
2
  const defineRuntime = (config) => {
3
3
  const getDynamicStylesCss = async (styles, scope) => {
4
4
  const parsed = await parseStyles(styles, scope, config);
@@ -16,7 +16,38 @@ export interface SaltyVariables {
16
16
  conditional?: CssConditionalVariables;
17
17
  [key: string]: undefined | string | number | CssVariableTokensObject;
18
18
  }
19
- type CssTemplate = MediaQueryStyles | CssStyles | {
19
+ /**
20
+ * A variant axis value bundle: a map from axis name to a map from value name (string keys or `'true'`)
21
+ * to a CSS-in-JS block. `name` is reserved as the call-site object form's path key and cannot be used
22
+ * as an axis name (build-time error).
23
+ */
24
+ export type CssVariantAxes = {
25
+ [axis: string]: {
26
+ [value: string]: CssStyles;
27
+ };
28
+ } & {
29
+ name?: never;
30
+ };
31
+ export interface CssCompoundVariant {
32
+ css: CssStyles;
33
+ [axis: string]: any;
34
+ }
35
+ /**
36
+ * A "rich" template node: declares its own base styles, named variant bundles, defaults, and compound rules.
37
+ * May coexist with child template nodes (descendants) as additional sibling keys.
38
+ */
39
+ export type RichTemplateNode = {
40
+ base?: CssStyles;
41
+ variants?: CssVariantAxes;
42
+ defaultVariants?: {
43
+ [axis: string]: string | boolean;
44
+ };
45
+ compoundVariants?: CssCompoundVariant[];
46
+ anyOfVariants?: CssCompoundVariant[];
47
+ } & {
48
+ [key: PropertyKey]: any;
49
+ };
50
+ type CssTemplate = MediaQueryStyles | CssStyles | RichTemplateNode | {
20
51
  [key: PropertyKey]: CssTemplate;
21
52
  };
22
53
  export interface CssTemplateObject {