@homebound/truss 2.22.0 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1360,6 +1360,21 @@ function normalizePseudoIdentifier(pseudo) {
1360
1360
  return `${prefix}${name}`;
1361
1361
  }
1362
1362
 
1363
+ // src/spacing-css-var.ts
1364
+ var SPACING_CUSTOM_PROPERTY = "--t-spacing";
1365
+ function incrementCssValue(multiplier) {
1366
+ return `calc(var(${SPACING_CUSTOM_PROPERTY}) * ${multiplier})`;
1367
+ }
1368
+ function tryParseIncrementCalcMultiplier(cssValue) {
1369
+ const prop = SPACING_CUSTOM_PROPERTY.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1370
+ const re = new RegExp(`^calc\\(var\\(${prop}\\) \\* (-?\\d+(?:\\.\\d+)?)\\)$`);
1371
+ const m = cssValue.match(re);
1372
+ return m ? m[1] : null;
1373
+ }
1374
+ function rootSpacingPreludeCss(incrementPx) {
1375
+ return `:root { ${SPACING_CUSTOM_PROPERTY}: ${incrementPx}px; }`;
1376
+ }
1377
+
1363
1378
  // src/plugin/emit-truss.ts
1364
1379
  var RELATIONSHIP_SHORT = {
1365
1380
  ancestor: "anc",
@@ -1413,18 +1428,22 @@ function cleanValueForClassName(value) {
1413
1428
  }
1414
1429
  return cleaned.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1415
1430
  }
1431
+ function classNameFragmentForResolvedValue(value) {
1432
+ const inc = tryParseIncrementCalcMultiplier(value);
1433
+ return inc !== null ? cleanValueForClassName(inc) : cleanValueForClassName(value);
1434
+ }
1416
1435
  function getPropertyAbbreviation(cssProp) {
1417
1436
  return cssPropertyAbbreviations[cssProp] ?? cssProp;
1418
1437
  }
1419
1438
  function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
1420
1439
  const abbr = seg.abbr;
1421
1440
  if (seg.argResolved !== void 0) {
1422
- const valuePart = cleanValueForClassName(seg.argResolved);
1441
+ const valuePart = classNameFragmentForResolvedValue(seg.argResolved);
1423
1442
  if (isMultiProp) {
1424
1443
  const lookup = getLonghandLookup(mapping);
1425
1444
  const canonical = lookup.get(`${cssProp}\0${cssValue}`);
1426
1445
  if (canonical) return canonical;
1427
- return `${getPropertyAbbreviation(cssProp)}_${cleanValueForClassName(cssValue)}`;
1446
+ return `${getPropertyAbbreviation(cssProp)}_${classNameFragmentForResolvedValue(cssValue)}`;
1428
1447
  }
1429
1448
  return `${abbr}_${valuePart}`;
1430
1449
  }
@@ -1432,7 +1451,7 @@ function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
1432
1451
  const lookup = getLonghandLookup(mapping);
1433
1452
  const canonical = lookup.get(`${cssProp}\0${cssValue}`);
1434
1453
  if (canonical) return canonical;
1435
- return `${getPropertyAbbreviation(cssProp)}_${cleanValueForClassName(cssValue)}`;
1454
+ return `${getPropertyAbbreviation(cssProp)}_${classNameFragmentForResolvedValue(cssValue)}`;
1436
1455
  }
1437
1456
  return abbr;
1438
1457
  }
@@ -1687,16 +1706,17 @@ function toCssVariableName(className, baseKey, cssProp) {
1687
1706
  const cp = className.endsWith(baseClassName) ? className.slice(0, -baseClassName.length) : "";
1688
1707
  return `--${cp}${cssProp}`;
1689
1708
  }
1690
- function buildMaybeIncDeclaration(helperName, increment) {
1709
+ function buildMaybeIncDeclaration(helperName) {
1691
1710
  const incParam = t3.identifier("inc");
1711
+ const calcPrefix = `calc(var(${SPACING_CUSTOM_PROPERTY}) * `;
1692
1712
  const body = t3.blockStatement([
1693
1713
  t3.returnStatement(
1694
1714
  t3.conditionalExpression(
1695
1715
  t3.binaryExpression("===", t3.unaryExpression("typeof", incParam), t3.stringLiteral("string")),
1696
1716
  incParam,
1697
1717
  t3.templateLiteral(
1698
- [t3.templateElement({ raw: "", cooked: "" }, false), t3.templateElement({ raw: "px", cooked: "px" }, true)],
1699
- [t3.binaryExpression("*", incParam, t3.numericLiteral(increment))]
1718
+ [t3.templateElement({ raw: calcPrefix, cooked: calcPrefix }, false), t3.templateElement({ raw: ")", cooked: ")" }, true)],
1719
+ [incParam]
1700
1720
  )
1701
1721
  )
1702
1722
  )
@@ -1725,6 +1745,9 @@ function buildRuntimeLookupDeclaration(lookupName, segmentsByName, mapping) {
1725
1745
  // src/plugin/transform-css.ts
1726
1746
  import * as t5 from "@babel/types";
1727
1747
 
1748
+ // src/plugin/resolve-chain.ts
1749
+ import { pascalCase } from "change-case";
1750
+
1728
1751
  // src/media-query.ts
1729
1752
  function invertMediaQuery(query) {
1730
1753
  const screenPrefix = "@media screen and ";
@@ -2149,6 +2172,18 @@ function resolveChain(ctx, chain) {
2149
2172
  segments.push(seg);
2150
2173
  continue;
2151
2174
  }
2175
+ if (abbr === "setVar") {
2176
+ const segs = resolveSetVarCall(
2177
+ node,
2178
+ mapping,
2179
+ context.mediaQuery,
2180
+ context.pseudoClass,
2181
+ context.pseudoElement,
2182
+ context.whenPseudo
2183
+ );
2184
+ segments.push(...segs);
2185
+ continue;
2186
+ }
2152
2187
  if (abbr === "typography") {
2153
2188
  const resolved = resolveTypographyCall(
2154
2189
  node,
@@ -2305,7 +2340,7 @@ function resolveVariableCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
2305
2340
  if (node.args.length !== 1) {
2306
2341
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
2307
2342
  }
2308
- const literalValue = tryEvaluateLiteral(node.args[0], entry.incremented, mapping.increment);
2343
+ const literalValue = tryEvaluateLiteral(node.args[0], entry.incremented);
2309
2344
  return buildParameterizedSegment({
2310
2345
  abbr,
2311
2346
  props: entry.props,
@@ -2588,10 +2623,10 @@ function isDefaultMarkerNode(node) {
2588
2623
  function isLegacyDefaultMarkerExpression(node) {
2589
2624
  return node.type === "CallExpression" && node.arguments.length === 0 && node.callee.type === "MemberExpression" && !node.callee.computed && node.callee.property.type === "Identifier" && node.callee.property.name === "defaultMarker";
2590
2625
  }
2591
- function tryEvaluateLiteral(node, incremented, increment) {
2626
+ function tryEvaluateLiteral(node, incremented) {
2592
2627
  if (node.type === "NumericLiteral") {
2593
2628
  if (incremented) {
2594
- return `${node.value * increment}px`;
2629
+ return incrementCssValue(node.value);
2595
2630
  }
2596
2631
  return String(node.value);
2597
2632
  }
@@ -2601,7 +2636,7 @@ function tryEvaluateLiteral(node, incremented, increment) {
2601
2636
  if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2602
2637
  const val = -node.argument.value;
2603
2638
  if (incremented) {
2604
- return `${val * increment}px`;
2639
+ return incrementCssValue(val);
2605
2640
  }
2606
2641
  return String(val);
2607
2642
  }
@@ -2697,6 +2732,245 @@ function stringLiteralValue(node, errorMessage) {
2697
2732
  }
2698
2733
  throw new UnsupportedPatternError(errorMessage);
2699
2734
  }
2735
+ function mediaQueryForBreakpointName(mapping, bpName) {
2736
+ if (!mapping.breakpoints) return null;
2737
+ const key = `if${pascalCase(bpName)}`;
2738
+ return mapping.breakpoints[key] ?? null;
2739
+ }
2740
+ function setVarClassBaseFromCssVarName(cssVarName) {
2741
+ const body = (cssVarName.startsWith("--") ? cssVarName.slice(2) : cssVarName).replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
2742
+ return `__${body}`;
2743
+ }
2744
+ function containerQueryStringFromBounds(name, gt, lt) {
2745
+ const parts = [];
2746
+ if (gt !== void 0) {
2747
+ parts.push(`(min-width: ${gt + 1}px)`);
2748
+ }
2749
+ if (lt !== void 0) {
2750
+ parts.push(`(max-width: ${lt}px)`);
2751
+ }
2752
+ const query = parts.join(" and ");
2753
+ const namePrefix = name ? `${name} ` : "";
2754
+ return `@container ${namePrefix}${query}`;
2755
+ }
2756
+ function resolveSetVarPropertyKey(prop, mapping) {
2757
+ const key = prop.key;
2758
+ if (!prop.computed) {
2759
+ if (key.type === "StringLiteral") {
2760
+ if (key.value.startsWith("--")) {
2761
+ return key.value;
2762
+ }
2763
+ throw new UnsupportedPatternError(
2764
+ `setVar() string keys must be CSS variables starting with "--" - got ${JSON.stringify(key.value)}`
2765
+ );
2766
+ }
2767
+ if (key.type === "Identifier") {
2768
+ throw new UnsupportedPatternError(
2769
+ `setVar() requires computed keys like [Tokens.Name] or string keys "--my-var", not bare property names`
2770
+ );
2771
+ }
2772
+ throw new UnsupportedPatternError(`setVar() property keys must be string literals or [Tokens.*] members`);
2773
+ }
2774
+ if (key.type === "MemberExpression") {
2775
+ const mem = key;
2776
+ const memberName = !mem.computed && mem.property.type === "Identifier" ? mem.property.name : mem.computed && mem.property.type === "StringLiteral" ? mem.property.value : null;
2777
+ if (memberName == null) {
2778
+ throw new UnsupportedPatternError(
2779
+ `setVar() [Tokens.name] keys must use a plain .member or ["string"] member access`
2780
+ );
2781
+ }
2782
+ const tokenMap = mapping.tokens;
2783
+ if (!tokenMap || !(memberName in tokenMap)) {
2784
+ throw new UnsupportedPatternError(
2785
+ tokenMap ? `Unknown token "${memberName}" - add it to config.tokens or use a "--" string literal key` : `setVar() [Tokens.*] requires config.tokens; use "--" string literal keys only`
2786
+ );
2787
+ }
2788
+ return tokenMap[memberName];
2789
+ }
2790
+ throw new UnsupportedPatternError(`setVar() computed keys must be Tokens.*-style members`);
2791
+ }
2792
+ function expandSetVarValueToLeaves(valueNode, mapping, baseCtx) {
2793
+ const unwrapped = unwrapExpression(valueNode);
2794
+ const scalar = tryEvaluateAddLiteral(unwrapped);
2795
+ if (scalar !== null) {
2796
+ return [{ literal: scalar, ctx: cloneConditionContext(baseCtx) }];
2797
+ }
2798
+ if (unwrapped.type !== "ObjectExpression") {
2799
+ throw new UnsupportedPatternError(
2800
+ `setVar() values must be string/number literals or a { default?, media?, container? } object`
2801
+ );
2802
+ }
2803
+ let defaultLit;
2804
+ let mediaObj;
2805
+ let containerArr;
2806
+ for (const prop of unwrapped.properties) {
2807
+ if (prop.type === "SpreadElement") {
2808
+ throw new UnsupportedPatternError(`setVar() responsive object does not support spread properties`);
2809
+ }
2810
+ if (prop.type !== "ObjectProperty" || prop.computed) {
2811
+ throw new UnsupportedPatternError(`setVar() responsive object only supports plain data properties`);
2812
+ }
2813
+ const name = objectPropertyName(prop.key);
2814
+ if (!name) {
2815
+ throw new UnsupportedPatternError(`setVar() responsive object: invalid property key`);
2816
+ }
2817
+ if (name === "default") {
2818
+ const v = tryEvaluateAddLiteral(unwrapExpression(prop.value));
2819
+ if (v === null) {
2820
+ throw new UnsupportedPatternError(`setVar().default must be a string or number literal`);
2821
+ }
2822
+ defaultLit = v;
2823
+ continue;
2824
+ }
2825
+ if (name === "media") {
2826
+ if (prop.value.type !== "ObjectExpression") {
2827
+ throw new UnsupportedPatternError(`setVar().media must be an object literal`);
2828
+ }
2829
+ mediaObj = prop.value;
2830
+ continue;
2831
+ }
2832
+ if (name === "container") {
2833
+ if (prop.value.type !== "ArrayExpression") {
2834
+ throw new UnsupportedPatternError(`setVar().container must be an array literal`);
2835
+ }
2836
+ containerArr = prop.value;
2837
+ continue;
2838
+ }
2839
+ throw new UnsupportedPatternError(`setVar() responsive object does not support property "${name}"`);
2840
+ }
2841
+ const leaves = [];
2842
+ if (defaultLit !== void 0) {
2843
+ leaves.push({ literal: defaultLit, ctx: cloneConditionContext(baseCtx) });
2844
+ }
2845
+ if (mediaObj) {
2846
+ for (const mprop of mediaObj.properties) {
2847
+ if (mprop.type === "SpreadElement") {
2848
+ throw new UnsupportedPatternError(`setVar().media does not support spread properties`);
2849
+ }
2850
+ if (mprop.type !== "ObjectProperty" || mprop.computed) {
2851
+ throw new UnsupportedPatternError(`setVar().media only supports plain identifier or string keys`);
2852
+ }
2853
+ const bpName = objectPropertyName(mprop.key);
2854
+ if (!bpName) {
2855
+ throw new UnsupportedPatternError(`setVar().media: invalid breakpoint key`);
2856
+ }
2857
+ const mq = mediaQueryForBreakpointName(mapping, bpName);
2858
+ if (!mq) {
2859
+ throw new UnsupportedPatternError(
2860
+ `Unknown breakpoint "${bpName}" in setVar().media - use a Breakpoint name from truss-config`
2861
+ );
2862
+ }
2863
+ const v = tryEvaluateAddLiteral(unwrapExpression(mprop.value));
2864
+ if (v === null) {
2865
+ throw new UnsupportedPatternError(`setVar().media[${bpName}] must be a string or number literal`);
2866
+ }
2867
+ const ctx = cloneConditionContext(baseCtx);
2868
+ ctx.mediaQuery = mq;
2869
+ leaves.push({ literal: v, ctx });
2870
+ }
2871
+ }
2872
+ if (containerArr) {
2873
+ for (const elt of containerArr.elements) {
2874
+ if (elt === null) {
2875
+ continue;
2876
+ }
2877
+ if (elt.type !== "ObjectExpression") {
2878
+ throw new UnsupportedPatternError(`setVar().container entries must be object literals`);
2879
+ }
2880
+ let rowValue;
2881
+ let cname;
2882
+ let lt;
2883
+ let gt;
2884
+ for (const rprop of elt.properties) {
2885
+ if (rprop.type === "SpreadElement") {
2886
+ throw new UnsupportedPatternError(`setVar().container row does not support spread`);
2887
+ }
2888
+ if (rprop.type !== "ObjectProperty" || rprop.computed) {
2889
+ throw new UnsupportedPatternError(`setVar().container row: use plain properties only`);
2890
+ }
2891
+ const rk = objectPropertyName(rprop.key);
2892
+ if (!rk) {
2893
+ continue;
2894
+ }
2895
+ const rv = rprop.value;
2896
+ if (rk === "value") {
2897
+ const lit = tryEvaluateAddLiteral(unwrapExpression(rv));
2898
+ if (lit === null) {
2899
+ throw new UnsupportedPatternError(`setVar().container row "value" must be a string or number literal`);
2900
+ }
2901
+ rowValue = lit;
2902
+ } else if (rk === "name") {
2903
+ cname = stringLiteralValue(rv, `setVar().container name must be a string literal`);
2904
+ } else if (rk === "lt") {
2905
+ lt = numericLiteralValue(rv, `setVar().container lt must be a numeric literal`);
2906
+ } else if (rk === "gt") {
2907
+ gt = numericLiteralValue(rv, `setVar().container gt must be a numeric literal`);
2908
+ } else {
2909
+ throw new UnsupportedPatternError(`setVar().container row does not support property "${rk}"`);
2910
+ }
2911
+ }
2912
+ if (rowValue === void 0) {
2913
+ throw new UnsupportedPatternError(`setVar().container row requires a "value" property`);
2914
+ }
2915
+ if (lt === void 0 && gt === void 0) {
2916
+ throw new UnsupportedPatternError(`setVar().container row requires at least one of gt or lt`);
2917
+ }
2918
+ const containerMq = containerQueryStringFromBounds(cname, gt, lt);
2919
+ const ctx = cloneConditionContext(baseCtx);
2920
+ ctx.mediaQuery = containerMq;
2921
+ leaves.push({ literal: rowValue, ctx });
2922
+ }
2923
+ }
2924
+ if (leaves.length === 0) {
2925
+ throw new UnsupportedPatternError(
2926
+ `setVar() responsive object must include at least one of default, media entries, or container entries`
2927
+ );
2928
+ }
2929
+ return leaves;
2930
+ }
2931
+ function resolveSetVarCall(node, mapping, mediaQuery, pseudoClass, pseudoElement, whenPseudo) {
2932
+ if (node.args.length !== 1) {
2933
+ throw new UnsupportedPatternError(`setVar() requires exactly 1 argument (an object literal)`);
2934
+ }
2935
+ const arg = node.args[0];
2936
+ if (arg.type === "SpreadElement") {
2937
+ throw new UnsupportedPatternError(`setVar() does not support spread arguments`);
2938
+ }
2939
+ if (arg.type !== "ObjectExpression") {
2940
+ throw new UnsupportedPatternError(`setVar() requires an object literal argument`);
2941
+ }
2942
+ const baseContext = {
2943
+ mediaQuery,
2944
+ pseudoClass,
2945
+ pseudoElement,
2946
+ whenPseudo
2947
+ };
2948
+ const segments = [];
2949
+ for (const prop of arg.properties) {
2950
+ if (prop.type === "SpreadElement") {
2951
+ throw new UnsupportedPatternError(`setVar() does not support spread properties`);
2952
+ }
2953
+ if (prop.type !== "ObjectProperty") {
2954
+ throw new UnsupportedPatternError(`setVar() only supports object properties`);
2955
+ }
2956
+ const cssVarName = resolveSetVarPropertyKey(prop, mapping);
2957
+ const leaves = expandSetVarValueToLeaves(prop.value, mapping, baseContext);
2958
+ for (const leaf of leaves) {
2959
+ const abbr = setVarClassBaseFromCssVarName(cssVarName);
2960
+ segments.push(
2961
+ segmentWithConditionContext(
2962
+ {
2963
+ abbr,
2964
+ defs: { [cssVarName]: leaf.literal },
2965
+ argResolved: leaf.literal
2966
+ },
2967
+ leaf.ctx
2968
+ )
2969
+ );
2970
+ }
2971
+ }
2972
+ return segments;
2973
+ }
2700
2974
  var UnsupportedPatternError = class extends Error {
2701
2975
  constructor(message) {
2702
2976
  super(`[truss] Unsupported pattern: ${message}`);
@@ -3475,7 +3749,7 @@ function transformTruss(code, filename, mapping, options = {}) {
3475
3749
  }
3476
3750
  const declarationsToInsert = [];
3477
3751
  if (maybeIncHelperName) {
3478
- declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
3752
+ declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName));
3479
3753
  }
3480
3754
  for (const [lookupKey, lookup] of runtimeLookups) {
3481
3755
  const lookupName = runtimeLookupNames.get(lookupKey);
@@ -3775,13 +4049,16 @@ function createTrussTransformSession(options) {
3775
4049
  return result;
3776
4050
  }
3777
4051
  function collectCss() {
4052
+ const mapping2 = ensureMapping();
3778
4053
  const appCssParts = [generateCssText(cssRegistry)];
3779
4054
  const allArbitrary = Array.from(arbitraryCssRegistry.values()).join("\n\n");
3780
4055
  appCssParts.push(annotateArbitraryCssBlock(allArbitrary));
3781
4056
  const appCss = appCssParts.filter((part) => part.length > 0).join("\n");
3782
4057
  const libs = loadLibraries();
3783
- if (libs.length === 0) return appCss;
3784
- return mergeTrussCss([...libs, parseTrussCss(appCss)]);
4058
+ const body = libs.length === 0 ? appCss : mergeTrussCss([...libs, parseTrussCss(appCss)]);
4059
+ if (body.length === 0) return "";
4060
+ return `${rootSpacingPreludeCss(mapping2.increment)}
4061
+ ${body}`;
3785
4062
  }
3786
4063
  function hasCss() {
3787
4064
  return cssRegistry.size > 0 || arbitraryCssRegistry.size > 0 || libraryPaths.length > 0;