@homebound/truss 2.23.0 → 2.25.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,34 @@ function normalizePseudoIdentifier(pseudo) {
1360
1360
  return `${prefix}${name}`;
1361
1361
  }
1362
1362
 
1363
+ // src/css-custom-property.ts
1364
+ function maybeCssVar(value) {
1365
+ if (typeof value !== "string") return value;
1366
+ if (value.startsWith("--")) return `var(${value})`;
1367
+ return value;
1368
+ }
1369
+ function variableValueNeedsMaybeCssVar(opts) {
1370
+ return !opts.appendPx;
1371
+ }
1372
+ function isCustomPropertyName(value) {
1373
+ return value.startsWith("--");
1374
+ }
1375
+
1376
+ // src/spacing-css-var.ts
1377
+ var SPACING_CUSTOM_PROPERTY = "--t-spacing";
1378
+ function incrementCssValue(multiplier) {
1379
+ return `calc(var(${SPACING_CUSTOM_PROPERTY}) * ${multiplier})`;
1380
+ }
1381
+ function tryParseIncrementCalcMultiplier(cssValue) {
1382
+ const prop = SPACING_CUSTOM_PROPERTY.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1383
+ const re = new RegExp(`^calc\\(var\\(${prop}\\) \\* (-?\\d+(?:\\.\\d+)?)\\)$`);
1384
+ const m = cssValue.match(re);
1385
+ return m ? m[1] : null;
1386
+ }
1387
+ function rootSpacingPreludeCss(incrementPx) {
1388
+ return `:root { ${SPACING_CUSTOM_PROPERTY}: ${incrementPx}px; }`;
1389
+ }
1390
+
1363
1391
  // src/plugin/emit-truss.ts
1364
1392
  var RELATIONSHIP_SHORT = {
1365
1393
  ancestor: "anc",
@@ -1413,18 +1441,22 @@ function cleanValueForClassName(value) {
1413
1441
  }
1414
1442
  return cleaned.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1415
1443
  }
1444
+ function classNameFragmentForResolvedValue(value) {
1445
+ const inc = tryParseIncrementCalcMultiplier(value);
1446
+ return inc !== null ? cleanValueForClassName(inc) : cleanValueForClassName(value);
1447
+ }
1416
1448
  function getPropertyAbbreviation(cssProp) {
1417
1449
  return cssPropertyAbbreviations[cssProp] ?? cssProp;
1418
1450
  }
1419
1451
  function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
1420
1452
  const abbr = seg.abbr;
1421
1453
  if (seg.argResolved !== void 0) {
1422
- const valuePart = cleanValueForClassName(seg.argResolved);
1454
+ const valuePart = classNameFragmentForResolvedValue(seg.argResolved);
1423
1455
  if (isMultiProp) {
1424
1456
  const lookup = getLonghandLookup(mapping);
1425
1457
  const canonical = lookup.get(`${cssProp}\0${cssValue}`);
1426
1458
  if (canonical) return canonical;
1427
- return `${getPropertyAbbreviation(cssProp)}_${cleanValueForClassName(cssValue)}`;
1459
+ return `${getPropertyAbbreviation(cssProp)}_${classNameFragmentForResolvedValue(cssValue)}`;
1428
1460
  }
1429
1461
  return `${abbr}_${valuePart}`;
1430
1462
  }
@@ -1432,13 +1464,14 @@ function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
1432
1464
  const lookup = getLonghandLookup(mapping);
1433
1465
  const canonical = lookup.get(`${cssProp}\0${cssValue}`);
1434
1466
  if (canonical) return canonical;
1435
- return `${getPropertyAbbreviation(cssProp)}_${cleanValueForClassName(cssValue)}`;
1467
+ return `${getPropertyAbbreviation(cssProp)}_${classNameFragmentForResolvedValue(cssValue)}`;
1436
1468
  }
1437
1469
  return abbr;
1438
1470
  }
1439
1471
  function collectAtomicRules(chains, mapping) {
1440
1472
  const rules = /* @__PURE__ */ new Map();
1441
1473
  let needsMaybeInc = false;
1474
+ let needsMaybeCssVar = false;
1442
1475
  function collectSegment(seg) {
1443
1476
  if (seg.error || seg.styleArrayArg || seg.classNameArg || seg.styleArg) return;
1444
1477
  if (seg.typographyLookup) {
@@ -1450,6 +1483,9 @@ function collectAtomicRules(chains, mapping) {
1450
1483
  return;
1451
1484
  }
1452
1485
  if (seg.incremented) needsMaybeInc = true;
1486
+ if (seg.variableProps && seg.argResolved === void 0 && variableValueNeedsMaybeCssVar(seg)) {
1487
+ needsMaybeCssVar = true;
1488
+ }
1453
1489
  collectSegmentRules(rules, seg, mapping);
1454
1490
  }
1455
1491
  for (const chain of chains) {
@@ -1460,7 +1496,7 @@ function collectAtomicRules(chains, mapping) {
1460
1496
  }
1461
1497
  }
1462
1498
  }
1463
- return { rules, needsMaybeInc };
1499
+ return { rules, needsMaybeInc, needsMaybeCssVar };
1464
1500
  }
1465
1501
  function segmentContext(seg, mapping) {
1466
1502
  const prefix = `${conditionPrefix(seg.pseudoClass, seg.mediaQuery, seg.pseudoElement, mapping.breakpoints)}${seg.whenPseudo ? whenPrefix(seg.whenPseudo) : ""}`;
@@ -1546,6 +1582,7 @@ function variableStyleEntries(seg, mapping, prefix, isConditional) {
1546
1582
  cssValue: `var(${varName})`,
1547
1583
  varName,
1548
1584
  argNode: seg.argNode,
1585
+ argResolved: seg.argResolved,
1549
1586
  incremented: seg.incremented,
1550
1587
  appendPx: seg.appendPx
1551
1588
  });
@@ -1635,7 +1672,7 @@ function formatNestedRuleBlock(wrapper, selector, rule) {
1635
1672
  }).join(" ");
1636
1673
  return `${wrapper} { ${selector} { ${body} } }`;
1637
1674
  }
1638
- function buildStyleHashProperties(segments, mapping, maybeIncHelperName) {
1675
+ function buildStyleHashProperties(segments, mapping, maybeIncHelperName, maybeCssVarHelperName) {
1639
1676
  const propGroups = /* @__PURE__ */ new Map();
1640
1677
  function pushEntry(entry) {
1641
1678
  const cssProp = entry.cssProp;
@@ -1663,14 +1700,22 @@ function buildStyleHashProperties(segments, mapping, maybeIncHelperName) {
1663
1700
  if (variableEntries.length > 0) {
1664
1701
  const varsProps = [];
1665
1702
  for (const dyn of variableEntries) {
1666
- let valueExpr = dyn.argNode;
1667
- if (dyn.incremented) {
1668
- valueExpr = t3.callExpression(t3.identifier(maybeIncHelperName ?? "__maybeInc"), [valueExpr]);
1669
- } else if (dyn.appendPx) {
1670
- valueExpr = t3.templateLiteral(
1671
- [t3.templateElement({ raw: "", cooked: "" }, false), t3.templateElement({ raw: "px", cooked: "px" }, true)],
1672
- [valueExpr]
1673
- );
1703
+ let valueExpr;
1704
+ if (dyn.argResolved !== void 0) {
1705
+ valueExpr = t3.stringLiteral(dyn.argResolved);
1706
+ } else {
1707
+ valueExpr = dyn.argNode;
1708
+ if (dyn.incremented) {
1709
+ valueExpr = t3.callExpression(t3.identifier(maybeIncHelperName ?? "__maybeInc"), [valueExpr]);
1710
+ } else if (dyn.appendPx) {
1711
+ valueExpr = t3.templateLiteral(
1712
+ [t3.templateElement({ raw: "", cooked: "" }, false), t3.templateElement({ raw: "px", cooked: "px" }, true)],
1713
+ [valueExpr]
1714
+ );
1715
+ }
1716
+ if (maybeCssVarHelperName && variableValueNeedsMaybeCssVar(dyn)) {
1717
+ valueExpr = t3.callExpression(t3.identifier(maybeCssVarHelperName), [valueExpr]);
1718
+ }
1674
1719
  }
1675
1720
  varsProps.push(t3.objectProperty(t3.stringLiteral(dyn.varName), valueExpr));
1676
1721
  }
@@ -1687,16 +1732,17 @@ function toCssVariableName(className, baseKey, cssProp) {
1687
1732
  const cp = className.endsWith(baseClassName) ? className.slice(0, -baseClassName.length) : "";
1688
1733
  return `--${cp}${cssProp}`;
1689
1734
  }
1690
- function buildMaybeIncDeclaration(helperName, increment) {
1735
+ function buildMaybeIncDeclaration(helperName) {
1691
1736
  const incParam = t3.identifier("inc");
1737
+ const calcPrefix = `calc(var(${SPACING_CUSTOM_PROPERTY}) * `;
1692
1738
  const body = t3.blockStatement([
1693
1739
  t3.returnStatement(
1694
1740
  t3.conditionalExpression(
1695
1741
  t3.binaryExpression("===", t3.unaryExpression("typeof", incParam), t3.stringLiteral("string")),
1696
1742
  incParam,
1697
1743
  t3.templateLiteral(
1698
- [t3.templateElement({ raw: "", cooked: "" }, false), t3.templateElement({ raw: "px", cooked: "px" }, true)],
1699
- [t3.binaryExpression("*", incParam, t3.numericLiteral(increment))]
1744
+ [t3.templateElement({ raw: calcPrefix, cooked: calcPrefix }, false), t3.templateElement({ raw: ")", cooked: ")" }, true)],
1745
+ [incParam]
1700
1746
  )
1701
1747
  )
1702
1748
  )
@@ -2320,7 +2366,7 @@ function resolveVariableCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
2320
2366
  if (node.args.length !== 1) {
2321
2367
  throw new UnsupportedPatternError(`${abbr}() expects exactly 1 argument, got ${node.args.length}`);
2322
2368
  }
2323
- const literalValue = tryEvaluateLiteral(node.args[0], entry.incremented, mapping.increment);
2369
+ const literalValue = tryEvaluatePropertyLiteral(node.args[0], mapping, entry.incremented);
2324
2370
  return buildParameterizedSegment({
2325
2371
  abbr,
2326
2372
  props: entry.props,
@@ -2328,6 +2374,7 @@ function resolveVariableCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
2328
2374
  extraDefs: entry.extraDefs,
2329
2375
  argAst: node.args[0],
2330
2376
  literalValue,
2377
+ mapping,
2331
2378
  mediaQuery,
2332
2379
  pseudoClass,
2333
2380
  pseudoElement,
@@ -2351,6 +2398,7 @@ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
2351
2398
  extraDefs: targetEntry.extraDefs,
2352
2399
  argAst: node.args[0],
2353
2400
  literalValue,
2401
+ mapping,
2354
2402
  mediaQuery,
2355
2403
  pseudoClass,
2356
2404
  pseudoElement,
@@ -2358,14 +2406,44 @@ function resolveDelegateCall(abbr, entry, node, mapping, mediaQuery, pseudoClass
2358
2406
  });
2359
2407
  }
2360
2408
  function buildParameterizedSegment(params) {
2361
- const { abbr, props, incremented, appendPx, extraDefs, argAst, literalValue, whenPseudo } = params;
2409
+ const { abbr, props, incremented, appendPx, extraDefs, argAst, literalValue, mapping, whenPseudo } = params;
2362
2410
  const context = {
2363
2411
  mediaQuery: params.mediaQuery,
2364
2412
  pseudoClass: params.pseudoClass,
2365
2413
  pseudoElement: params.pseudoElement,
2366
2414
  whenPseudo
2367
2415
  };
2416
+ return resolveLiteralOrVariableSegment({
2417
+ abbr,
2418
+ props,
2419
+ incremented,
2420
+ appendPx,
2421
+ extraDefs,
2422
+ argAst,
2423
+ literalValue,
2424
+ mapping,
2425
+ context
2426
+ });
2427
+ }
2428
+ function resolveLiteralOrVariableSegment(params) {
2429
+ const { abbr, props, incremented, appendPx, extraDefs, argAst, literalValue, mapping, context } = params;
2368
2430
  if (literalValue !== null) {
2431
+ const raw = tryResolveValueLiteral(argAst, mapping);
2432
+ if (raw !== null && isCustomPropertyName(raw)) {
2433
+ const base2 = segmentWithConditionContext(
2434
+ {
2435
+ abbr,
2436
+ defs: {},
2437
+ variableProps: props,
2438
+ incremented,
2439
+ variableExtraDefs: extraDefs,
2440
+ argResolved: literalValue
2441
+ },
2442
+ context
2443
+ );
2444
+ if (appendPx) base2.appendPx = true;
2445
+ return base2;
2446
+ }
2369
2447
  const defs = {};
2370
2448
  for (const prop of props) {
2371
2449
  defs[prop] = literalValue;
@@ -2478,23 +2556,18 @@ function resolveAddCall(node, mapping, mediaQuery, pseudoClass, pseudoElement, w
2478
2556
  }
2479
2557
  const propName = propArg.value;
2480
2558
  const valueArg = node.args[1];
2481
- const literalValue = tryEvaluateAddLiteral(valueArg);
2482
- if (literalValue !== null) {
2483
- return [segmentWithConditionContext(
2484
- { abbr: propName, defs: { [propName]: literalValue }, argResolved: literalValue },
2485
- context
2486
- )];
2487
- }
2488
- return [segmentWithConditionContext(
2489
- {
2559
+ const literalValue = tryEvaluatePropertyLiteral(valueArg, mapping, false);
2560
+ return [
2561
+ resolveLiteralOrVariableSegment({
2490
2562
  abbr: propName,
2491
- defs: {},
2492
- variableProps: [propName],
2563
+ props: [propName],
2493
2564
  incremented: false,
2494
- argNode: valueArg
2495
- },
2496
- context
2497
- )];
2565
+ argAst: valueArg,
2566
+ literalValue,
2567
+ mapping,
2568
+ context
2569
+ })
2570
+ ];
2498
2571
  }
2499
2572
  function resolveAddObjectLiteral(obj, mapping, context) {
2500
2573
  const segments = [];
@@ -2514,21 +2587,31 @@ function resolveAddObjectLiteral(obj, mapping, context) {
2514
2587
  throw new UnsupportedPatternError(`add({...}) property keys must be identifiers or string literals`);
2515
2588
  }
2516
2589
  const valueNode = property.value;
2517
- const literalValue = tryEvaluateAddLiteral(valueNode);
2590
+ const literalValue = tryEvaluatePropertyLiteral(valueNode, mapping, false);
2518
2591
  if (literalValue !== null) {
2519
- const canonicalAbbr = findCanonicalAbbreviation(mapping, propName, literalValue);
2520
- if (canonicalAbbr) {
2521
- const entry = mapping.abbreviations[canonicalAbbr];
2522
- segments.push(segmentWithConditionContext(
2523
- { abbr: canonicalAbbr, defs: entry.defs },
2524
- context
2525
- ));
2526
- } else {
2527
- segments.push(segmentWithConditionContext(
2528
- { abbr: propName, defs: { [propName]: literalValue }, argResolved: literalValue },
2529
- context
2530
- ));
2592
+ const raw = tryResolveValueLiteral(valueNode, mapping);
2593
+ if (raw === null || !isCustomPropertyName(raw)) {
2594
+ const canonicalAbbr = findCanonicalAbbreviation(mapping, propName, literalValue);
2595
+ if (canonicalAbbr) {
2596
+ const entry = mapping.abbreviations[canonicalAbbr];
2597
+ segments.push(segmentWithConditionContext(
2598
+ { abbr: canonicalAbbr, defs: entry.defs },
2599
+ context
2600
+ ));
2601
+ continue;
2602
+ }
2531
2603
  }
2604
+ segments.push(
2605
+ resolveLiteralOrVariableSegment({
2606
+ abbr: propName,
2607
+ props: [propName],
2608
+ incremented: false,
2609
+ argAst: valueNode,
2610
+ literalValue,
2611
+ mapping,
2612
+ context
2613
+ })
2614
+ );
2532
2615
  } else {
2533
2616
  segments.push(segmentWithConditionContext(
2534
2617
  { abbr: propName, defs: {}, variableProps: [propName], incremented: false, argNode: valueNode },
@@ -2541,7 +2624,11 @@ function resolveAddObjectLiteral(obj, mapping, context) {
2541
2624
  function findCanonicalAbbreviation(mapping, prop, value) {
2542
2625
  return getLonghandLookup(mapping).get(`${prop}\0${value}`) ?? null;
2543
2626
  }
2544
- function tryEvaluateAddLiteral(node) {
2627
+ function tryResolveValueLiteral(node, mapping) {
2628
+ if (mapping) {
2629
+ const token = tryResolveTokensMember(node, mapping);
2630
+ if (token !== null) return token;
2631
+ }
2545
2632
  if (node.type === "StringLiteral") {
2546
2633
  return node.value;
2547
2634
  }
@@ -2553,6 +2640,20 @@ function tryEvaluateAddLiteral(node) {
2553
2640
  }
2554
2641
  return null;
2555
2642
  }
2643
+ function tryResolveTokensMember(node, mapping) {
2644
+ if (node.type !== "MemberExpression") return null;
2645
+ if (node.object.type !== "Identifier" || node.object.name !== "Tokens") return null;
2646
+ const memberName = !node.computed && node.property.type === "Identifier" ? node.property.name : node.computed && node.property.type === "StringLiteral" ? node.property.value : null;
2647
+ if (memberName == null) return null;
2648
+ const tokenMap = mapping.tokens;
2649
+ if (!tokenMap) {
2650
+ throw new UnsupportedPatternError(`Tokens.* requires config.tokens`);
2651
+ }
2652
+ if (!(memberName in tokenMap)) {
2653
+ throw new UnsupportedPatternError(`Unknown token "${memberName}" - add it to config.tokens`);
2654
+ }
2655
+ return tokenMap[memberName];
2656
+ }
2556
2657
  var WHEN_RELATIONSHIPS = /* @__PURE__ */ new Set(["ancestor", "descendant", "anySibling", "siblingBefore", "siblingAfter"]);
2557
2658
  function resolveWhenCall(node) {
2558
2659
  if (node.args.length !== 1 && node.args.length !== 3) {
@@ -2603,23 +2704,24 @@ function isDefaultMarkerNode(node) {
2603
2704
  function isLegacyDefaultMarkerExpression(node) {
2604
2705
  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";
2605
2706
  }
2606
- function tryEvaluateLiteral(node, incremented, increment) {
2707
+ function tryEvaluatePropertyLiteral(node, mapping, incremented) {
2607
2708
  if (node.type === "NumericLiteral") {
2608
2709
  if (incremented) {
2609
- return `${node.value * increment}px`;
2710
+ return incrementCssValue(node.value);
2610
2711
  }
2611
2712
  return String(node.value);
2612
2713
  }
2613
- if (node.type === "StringLiteral") {
2614
- return node.value;
2615
- }
2616
2714
  if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2617
2715
  const val = -node.argument.value;
2618
2716
  if (incremented) {
2619
- return `${val * increment}px`;
2717
+ return incrementCssValue(val);
2620
2718
  }
2621
2719
  return String(val);
2622
2720
  }
2721
+ const raw = tryResolveValueLiteral(node, mapping);
2722
+ if (raw !== null) {
2723
+ return maybeCssVar(raw);
2724
+ }
2623
2725
  return null;
2624
2726
  }
2625
2727
  function tryEvaluatePxLiteral(node) {
@@ -2771,7 +2873,7 @@ function resolveSetVarPropertyKey(prop, mapping) {
2771
2873
  }
2772
2874
  function expandSetVarValueToLeaves(valueNode, mapping, baseCtx) {
2773
2875
  const unwrapped = unwrapExpression(valueNode);
2774
- const scalar = tryEvaluateAddLiteral(unwrapped);
2876
+ const scalar = tryResolveValueLiteral(unwrapped);
2775
2877
  if (scalar !== null) {
2776
2878
  return [{ literal: scalar, ctx: cloneConditionContext(baseCtx) }];
2777
2879
  }
@@ -2795,7 +2897,7 @@ function expandSetVarValueToLeaves(valueNode, mapping, baseCtx) {
2795
2897
  throw new UnsupportedPatternError(`setVar() responsive object: invalid property key`);
2796
2898
  }
2797
2899
  if (name === "default") {
2798
- const v = tryEvaluateAddLiteral(unwrapExpression(prop.value));
2900
+ const v = tryResolveValueLiteral(unwrapExpression(prop.value));
2799
2901
  if (v === null) {
2800
2902
  throw new UnsupportedPatternError(`setVar().default must be a string or number literal`);
2801
2903
  }
@@ -2840,7 +2942,7 @@ function expandSetVarValueToLeaves(valueNode, mapping, baseCtx) {
2840
2942
  `Unknown breakpoint "${bpName}" in setVar().media - use a Breakpoint name from truss-config`
2841
2943
  );
2842
2944
  }
2843
- const v = tryEvaluateAddLiteral(unwrapExpression(mprop.value));
2945
+ const v = tryResolveValueLiteral(unwrapExpression(mprop.value));
2844
2946
  if (v === null) {
2845
2947
  throw new UnsupportedPatternError(`setVar().media[${bpName}] must be a string or number literal`);
2846
2948
  }
@@ -2874,7 +2976,7 @@ function expandSetVarValueToLeaves(valueNode, mapping, baseCtx) {
2874
2976
  }
2875
2977
  const rv = rprop.value;
2876
2978
  if (rk === "value") {
2877
- const lit = tryEvaluateAddLiteral(unwrapExpression(rv));
2979
+ const lit = tryResolveValueLiteral(unwrapExpression(rv));
2878
2980
  if (lit === null) {
2879
2981
  throw new UnsupportedPatternError(`setVar().container row "value" must be a string or number literal`);
2880
2982
  }
@@ -3158,6 +3260,12 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
3158
3260
  if (seg.whenPseudo) {
3159
3261
  return { error: `when() modifiers are not supported in .css.ts files` };
3160
3262
  }
3263
+ if (seg.variableProps && seg.argResolved) {
3264
+ for (const prop of seg.variableProps) {
3265
+ declarations.push({ property: camelToKebab(prop), value: seg.argResolved });
3266
+ }
3267
+ continue;
3268
+ }
3161
3269
  for (const [prop, value] of Object.entries(seg.defs)) {
3162
3270
  if (typeof value === "string" || typeof value === "number") {
3163
3271
  declarations.push({ property: camelToKebab(prop), value: String(value) });
@@ -3284,7 +3392,14 @@ function buildStyleHashMembers(segments, options) {
3284
3392
  const styleKeyCounts = /* @__PURE__ */ new Map();
3285
3393
  function flushNormal() {
3286
3394
  if (normalSegs.length > 0) {
3287
- members.push(...buildStyleHashProperties(normalSegs, options.mapping, options.maybeIncHelperName));
3395
+ members.push(
3396
+ ...buildStyleHashProperties(
3397
+ normalSegs,
3398
+ options.mapping,
3399
+ options.maybeIncHelperName,
3400
+ options.maybeCssVarHelperName
3401
+ )
3402
+ );
3288
3403
  normalSegs.length = 0;
3289
3404
  }
3290
3405
  }
@@ -3670,10 +3785,12 @@ function transformTruss(code, filename, mapping, options = {}) {
3670
3785
  });
3671
3786
  if (sites.length === 0 && !hasCssPropsCall && !hasBuildtimeJsxCssAttribute) return null;
3672
3787
  const chains = sites.map((s) => s.resolvedChain);
3673
- const { rules, needsMaybeInc } = collectAtomicRules(chains, mapping);
3788
+ const { rules, needsMaybeInc, needsMaybeCssVar } = collectAtomicRules(chains, mapping);
3674
3789
  const cssText = generateCssText(rules);
3675
3790
  const usedTopLevelNames = collectTopLevelBindings(ast);
3676
3791
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
3792
+ const existingMaybeCssVarHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "maybeCssVar");
3793
+ const maybeCssVarHelperName = needsMaybeCssVar ? existingMaybeCssVarHelperName ?? reservePreferredName(usedTopLevelNames, "maybeCssVar") : null;
3677
3794
  const existingMergePropsHelperName = findNamedImportBinding(ast, "@homebound/truss/runtime", "mergeProps");
3678
3795
  const mergePropsHelperName = existingMergePropsHelperName ?? reservePreferredName(usedTopLevelNames, "mergeProps");
3679
3796
  const needsMergePropsHelper = { current: false };
@@ -3696,6 +3813,7 @@ function transformTruss(code, filename, mapping, options = {}) {
3696
3813
  debug: options.debug ?? false,
3697
3814
  mapping,
3698
3815
  maybeIncHelperName,
3816
+ maybeCssVarHelperName,
3699
3817
  mergePropsHelperName,
3700
3818
  needsMergePropsHelper,
3701
3819
  trussPropsHelperName,
@@ -3714,6 +3832,9 @@ function transformTruss(code, filename, mapping, options = {}) {
3714
3832
  if (needsTrussDebugInfo.current && !existingTrussDebugInfoName) {
3715
3833
  runtimeImports.push({ importedName: "TrussDebugInfo", localName: trussDebugInfoName });
3716
3834
  }
3835
+ if (needsMaybeCssVar && !existingMaybeCssVarHelperName && maybeCssVarHelperName) {
3836
+ runtimeImports.push({ importedName: "maybeCssVar", localName: maybeCssVarHelperName });
3837
+ }
3717
3838
  if (options.injectCss) {
3718
3839
  runtimeImports.push({ importedName: "__injectTrussCSS", localName: "__injectTrussCSS" });
3719
3840
  }
@@ -3729,7 +3850,7 @@ function transformTruss(code, filename, mapping, options = {}) {
3729
3850
  }
3730
3851
  const declarationsToInsert = [];
3731
3852
  if (maybeIncHelperName) {
3732
- declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
3853
+ declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName));
3733
3854
  }
3734
3855
  for (const [lookupKey, lookup] of runtimeLookups) {
3735
3856
  const lookupName = runtimeLookupNames.get(lookupKey);
@@ -4029,13 +4150,16 @@ function createTrussTransformSession(options) {
4029
4150
  return result;
4030
4151
  }
4031
4152
  function collectCss() {
4153
+ const mapping2 = ensureMapping();
4032
4154
  const appCssParts = [generateCssText(cssRegistry)];
4033
4155
  const allArbitrary = Array.from(arbitraryCssRegistry.values()).join("\n\n");
4034
4156
  appCssParts.push(annotateArbitraryCssBlock(allArbitrary));
4035
4157
  const appCss = appCssParts.filter((part) => part.length > 0).join("\n");
4036
4158
  const libs = loadLibraries();
4037
- if (libs.length === 0) return appCss;
4038
- return mergeTrussCss([...libs, parseTrussCss(appCss)]);
4159
+ const body = libs.length === 0 ? appCss : mergeTrussCss([...libs, parseTrussCss(appCss)]);
4160
+ if (body.length === 0) return "";
4161
+ return `${rootSpacingPreludeCss(mapping2.increment)}
4162
+ ${body}`;
4039
4163
  }
4040
4164
  function hasCss() {
4041
4165
  return cssRegistry.size > 0 || arbitraryCssRegistry.size > 0 || libraryPaths.length > 0;