@homebound/truss 2.17.0 → 2.18.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.
@@ -1424,6 +1424,248 @@ import _generate2 from "@babel/generator";
1424
1424
  import * as t4 from "@babel/types";
1425
1425
  import { basename } from "path";
1426
1426
 
1427
+ // src/plugin/ast-utils.ts
1428
+ import * as t2 from "@babel/types";
1429
+ function collectTopLevelBindings(ast) {
1430
+ const used = /* @__PURE__ */ new Set();
1431
+ for (const node of ast.program.body) {
1432
+ if (t2.isImportDeclaration(node)) {
1433
+ for (const spec of node.specifiers) {
1434
+ used.add(spec.local.name);
1435
+ }
1436
+ continue;
1437
+ }
1438
+ if (t2.isVariableDeclaration(node)) {
1439
+ for (const decl of node.declarations) {
1440
+ collectPatternBindings(decl.id, used);
1441
+ }
1442
+ continue;
1443
+ }
1444
+ if (t2.isFunctionDeclaration(node) && node.id) {
1445
+ used.add(node.id.name);
1446
+ continue;
1447
+ }
1448
+ if (t2.isClassDeclaration(node) && node.id) {
1449
+ used.add(node.id.name);
1450
+ continue;
1451
+ }
1452
+ if (t2.isExportNamedDeclaration(node) && node.declaration) {
1453
+ const decl = node.declaration;
1454
+ if (t2.isVariableDeclaration(decl)) {
1455
+ for (const varDecl of decl.declarations) {
1456
+ collectPatternBindings(varDecl.id, used);
1457
+ }
1458
+ } else if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
1459
+ used.add(decl.id.name);
1460
+ }
1461
+ continue;
1462
+ }
1463
+ if (t2.isExportDefaultDeclaration(node)) {
1464
+ const decl = node.declaration;
1465
+ if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
1466
+ used.add(decl.id.name);
1467
+ }
1468
+ }
1469
+ }
1470
+ return used;
1471
+ }
1472
+ function collectPatternBindings(pattern, used) {
1473
+ if (t2.isVoidPattern(pattern)) {
1474
+ return;
1475
+ }
1476
+ if (t2.isIdentifier(pattern)) {
1477
+ used.add(pattern.name);
1478
+ return;
1479
+ }
1480
+ if (t2.isAssignmentPattern(pattern)) {
1481
+ collectPatternBindings(pattern.left, used);
1482
+ return;
1483
+ }
1484
+ if (t2.isRestElement(pattern)) {
1485
+ collectPatternBindings(pattern.argument, used);
1486
+ return;
1487
+ }
1488
+ if (t2.isObjectPattern(pattern)) {
1489
+ for (const prop of pattern.properties) {
1490
+ if (t2.isObjectProperty(prop)) {
1491
+ collectPatternBindings(prop.value, used);
1492
+ } else if (t2.isRestElement(prop)) {
1493
+ collectPatternBindings(prop.argument, used);
1494
+ }
1495
+ }
1496
+ return;
1497
+ }
1498
+ if (t2.isArrayPattern(pattern)) {
1499
+ for (const el of pattern.elements) {
1500
+ if (!el) continue;
1501
+ if (t2.isIdentifier(el) || t2.isAssignmentPattern(el) || t2.isObjectPattern(el) || t2.isArrayPattern(el)) {
1502
+ collectPatternBindings(el, used);
1503
+ } else if (t2.isRestElement(el)) {
1504
+ collectPatternBindings(el.argument, used);
1505
+ }
1506
+ }
1507
+ }
1508
+ }
1509
+ function reservePreferredName(used, preferred, secondary) {
1510
+ if (!used.has(preferred)) {
1511
+ used.add(preferred);
1512
+ return preferred;
1513
+ }
1514
+ if (secondary && !used.has(secondary)) {
1515
+ used.add(secondary);
1516
+ return secondary;
1517
+ }
1518
+ const base = secondary ?? preferred;
1519
+ let i = 1;
1520
+ let candidate = `${base}_${i}`;
1521
+ while (used.has(candidate)) {
1522
+ i++;
1523
+ candidate = `${base}_${i}`;
1524
+ }
1525
+ used.add(candidate);
1526
+ return candidate;
1527
+ }
1528
+ function findCssImportBinding(ast) {
1529
+ for (const node of ast.program.body) {
1530
+ if (!t2.isImportDeclaration(node)) continue;
1531
+ for (const spec of node.specifiers) {
1532
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: "Css" })) {
1533
+ return spec.local.name;
1534
+ }
1535
+ }
1536
+ }
1537
+ return null;
1538
+ }
1539
+ function findCssBuilderBinding(ast) {
1540
+ for (const node of ast.program.body) {
1541
+ if (!t2.isVariableDeclaration(node)) continue;
1542
+ for (const decl of node.declarations) {
1543
+ if (t2.isIdentifier(decl.id) && decl.init && t2.isNewExpression(decl.init) && t2.isIdentifier(decl.init.callee, { name: "CssBuilder" })) {
1544
+ return decl.id.name;
1545
+ }
1546
+ }
1547
+ }
1548
+ return null;
1549
+ }
1550
+ function removeCssImport(ast, cssBinding) {
1551
+ for (let i = 0; i < ast.program.body.length; i++) {
1552
+ const node = ast.program.body[i];
1553
+ if (!t2.isImportDeclaration(node)) continue;
1554
+ const cssSpecIndex = node.specifiers.findIndex((s) => t2.isImportSpecifier(s) && s.local.name === cssBinding);
1555
+ if (cssSpecIndex === -1) continue;
1556
+ if (node.specifiers.length === 1) {
1557
+ ast.program.body.splice(i, 1);
1558
+ } else {
1559
+ node.specifiers.splice(cssSpecIndex, 1);
1560
+ }
1561
+ return;
1562
+ }
1563
+ }
1564
+ function findLastImportIndex(ast) {
1565
+ let lastImportIndex = -1;
1566
+ for (let i = 0; i < ast.program.body.length; i++) {
1567
+ if (t2.isImportDeclaration(ast.program.body[i])) {
1568
+ lastImportIndex = i;
1569
+ }
1570
+ }
1571
+ return lastImportIndex;
1572
+ }
1573
+ function findNamedImportBinding(ast, source, importedName) {
1574
+ for (const node of ast.program.body) {
1575
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
1576
+ for (const spec of node.specifiers) {
1577
+ if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: importedName })) {
1578
+ return spec.local.name;
1579
+ }
1580
+ }
1581
+ }
1582
+ return null;
1583
+ }
1584
+ function findImportDeclaration(ast, source) {
1585
+ for (const node of ast.program.body) {
1586
+ if (t2.isImportDeclaration(node) && node.source.value === source) {
1587
+ return node;
1588
+ }
1589
+ }
1590
+ return null;
1591
+ }
1592
+ function replaceCssImportWithNamedImports(ast, cssBinding, source, imports) {
1593
+ for (const node of ast.program.body) {
1594
+ if (!t2.isImportDeclaration(node)) continue;
1595
+ const cssSpecIndex = node.specifiers.findIndex((spec) => {
1596
+ return t2.isImportSpecifier(spec) && spec.local.name === cssBinding;
1597
+ });
1598
+ if (cssSpecIndex === -1 || node.specifiers.length !== 1) continue;
1599
+ node.source = t2.stringLiteral(source);
1600
+ node.specifiers = imports.map((entry) => {
1601
+ return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
1602
+ });
1603
+ return true;
1604
+ }
1605
+ return false;
1606
+ }
1607
+ function upsertNamedImports(ast, source, imports) {
1608
+ if (imports.length === 0) return;
1609
+ for (const node of ast.program.body) {
1610
+ if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
1611
+ for (const entry of imports) {
1612
+ const exists = node.specifiers.some((spec) => {
1613
+ return t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: entry.importedName });
1614
+ });
1615
+ if (exists) continue;
1616
+ node.specifiers.push(t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName)));
1617
+ }
1618
+ return;
1619
+ }
1620
+ const importDecl = t2.importDeclaration(
1621
+ imports.map((entry) => {
1622
+ return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
1623
+ }),
1624
+ t2.stringLiteral(source)
1625
+ );
1626
+ const idx = findLastImportIndex(ast);
1627
+ ast.program.body.splice(idx + 1, 0, importDecl);
1628
+ }
1629
+ function extractChain(node, cssBinding) {
1630
+ const chain = [];
1631
+ let current = node;
1632
+ while (true) {
1633
+ if (t2.isIdentifier(current, { name: cssBinding })) {
1634
+ chain.reverse();
1635
+ return chain;
1636
+ }
1637
+ if (t2.isMemberExpression(current) && !current.computed && t2.isIdentifier(current.property)) {
1638
+ const name = current.property.name;
1639
+ if (name === "else") {
1640
+ chain.push({ type: "else" });
1641
+ } else {
1642
+ chain.push({ type: "getter", name });
1643
+ }
1644
+ current = current.object;
1645
+ continue;
1646
+ }
1647
+ if (t2.isCallExpression(current) && t2.isMemberExpression(current.callee) && !current.callee.computed && t2.isIdentifier(current.callee.property)) {
1648
+ const name = current.callee.property.name;
1649
+ if (name === "if") {
1650
+ chain.push({
1651
+ type: "if",
1652
+ conditionNode: current.arguments[0]
1653
+ });
1654
+ current = current.callee.object;
1655
+ continue;
1656
+ }
1657
+ chain.push({
1658
+ type: "call",
1659
+ name,
1660
+ args: current.arguments
1661
+ });
1662
+ current = current.callee.object;
1663
+ continue;
1664
+ }
1665
+ return null;
1666
+ }
1667
+ }
1668
+
1427
1669
  // src/plugin/resolve-chain.ts
1428
1670
  function emptyConditionContext() {
1429
1671
  return {
@@ -1497,9 +1739,10 @@ function applyModifierNodeToConditionContext(context, node, mapping) {
1497
1739
  context.pseudoClass = pseudoSelector(node.name);
1498
1740
  }
1499
1741
  }
1500
- function resolveFullChain(chain, mapping) {
1742
+ function resolveFullChain(chain, mapping, cssBindingName, initialContext = emptyConditionContext()) {
1501
1743
  const parts = [];
1502
1744
  const markers = [];
1745
+ const nestedErrors = [];
1503
1746
  const filteredChain = [];
1504
1747
  const scanErrors = [];
1505
1748
  for (let j = 0; j < chain.length; j++) {
@@ -1518,15 +1761,15 @@ function resolveFullChain(chain, mapping) {
1518
1761
  }
1519
1762
  let i = 0;
1520
1763
  let currentNodes = [];
1521
- let currentContext = emptyConditionContext();
1522
- let currentNodesStartContext = emptyConditionContext();
1764
+ let currentContext = cloneConditionContext(initialContext);
1765
+ let currentNodesStartContext = cloneConditionContext(initialContext);
1523
1766
  function flushCurrentNodes() {
1524
1767
  if (currentNodes.length === 0) {
1525
1768
  return;
1526
1769
  }
1527
1770
  parts.push({
1528
1771
  type: "unconditional",
1529
- segments: resolveChain(currentNodes, mapping, currentNodesStartContext)
1772
+ segments: resolveChain(currentNodes, mapping, currentNodesStartContext, cssBindingName)
1530
1773
  });
1531
1774
  currentNodes = [];
1532
1775
  currentNodesStartContext = cloneConditionContext(currentContext);
@@ -1548,13 +1791,22 @@ function resolveFullChain(chain, mapping) {
1548
1791
  const branchContext = cloneConditionContext(currentContext);
1549
1792
  const thenNodes = mediaStart.thenNodes ? [...mediaStart.thenNodes, ...filteredChain.slice(i + 1, elseIndex)] : filteredChain.slice(i, elseIndex);
1550
1793
  const elseNodes = [makeMediaQueryNode(mediaStart.inverseMediaQuery), ...filteredChain.slice(elseIndex + 1)];
1551
- const thenSegs = resolveChain(thenNodes, mapping, branchContext);
1552
- const elseSegs = resolveChain(elseNodes, mapping, branchContext);
1794
+ const thenSegs = resolveChain(thenNodes, mapping, branchContext, cssBindingName);
1795
+ const elseSegs = resolveChain(elseNodes, mapping, branchContext, cssBindingName);
1553
1796
  parts.push({ type: "unconditional", segments: [...thenSegs, ...elseSegs] });
1554
1797
  i = filteredChain.length;
1555
1798
  break;
1556
1799
  }
1557
1800
  }
1801
+ if (isWhenObjectCall(node)) {
1802
+ flushCurrentNodes();
1803
+ const resolved = resolveWhenObjectSelectors(node, mapping, cssBindingName, currentContext);
1804
+ parts.push(...resolved.parts);
1805
+ markers.push(...resolved.markers);
1806
+ nestedErrors.push(...resolved.errors);
1807
+ i++;
1808
+ continue;
1809
+ }
1558
1810
  if (node.type === "if") {
1559
1811
  if (node.conditionNode.type === "StringLiteral") {
1560
1812
  const mediaQuery = node.conditionNode.value;
@@ -1584,8 +1836,8 @@ function resolveFullChain(chain, mapping) {
1584
1836
  }
1585
1837
  i++;
1586
1838
  }
1587
- const thenSegs = resolveChain(thenNodes, mapping, branchContext);
1588
- const elseSegs = resolveChain(elseNodes, mapping, branchContext);
1839
+ const thenSegs = resolveChain(thenNodes, mapping, branchContext, cssBindingName);
1840
+ const elseSegs = resolveChain(elseNodes, mapping, branchContext, cssBindingName);
1589
1841
  parts.push({
1590
1842
  type: "conditional",
1591
1843
  conditionNode: node.conditionNode,
@@ -1607,21 +1859,87 @@ function resolveFullChain(chain, mapping) {
1607
1859
  }
1608
1860
  }
1609
1861
  }
1610
- return { parts, markers, errors: [...scanErrors, ...segmentErrors] };
1862
+ return { parts, markers, errors: [.../* @__PURE__ */ new Set([...scanErrors, ...nestedErrors, ...segmentErrors])] };
1611
1863
  }
1612
- function getMediaConditionalStartNode(node, mapping) {
1613
- if (node.type === "if" && node.conditionNode.type === "StringLiteral") {
1864
+ function isWhenObjectCall(node) {
1865
+ return node.type === "call" && node.name === "when" && node.args.length === 1 && node.args[0].type === "ObjectExpression";
1866
+ }
1867
+ function resolveWhenObjectSelectors(node, mapping, cssBindingName, initialContext) {
1868
+ if (!cssBindingName) {
1614
1869
  return {
1615
- inverseMediaQuery: invertMediaQuery(node.conditionNode.value),
1616
- thenNodes: [makeMediaQueryNode(node.conditionNode.value)]
1870
+ parts: [],
1871
+ markers: [],
1872
+ errors: [new UnsupportedPatternError(`when({ ... }) requires a resolvable Css binding`).message]
1617
1873
  };
1618
1874
  }
1619
- if (node.type === "getter" && mapping.breakpoints && node.name in mapping.breakpoints) {
1620
- return { inverseMediaQuery: invertMediaQuery(mapping.breakpoints[node.name]) };
1875
+ const objectArg = node.args[0];
1876
+ if (objectArg.type !== "ObjectExpression") {
1877
+ throw new UnsupportedPatternError(`when({ ... }) requires an object literal argument`);
1621
1878
  }
1622
- return null;
1623
- }
1624
- function findElseIndex(chain, start) {
1879
+ const parts = [];
1880
+ const markers = [];
1881
+ const errors = [];
1882
+ for (const property of objectArg.properties) {
1883
+ try {
1884
+ if (property.type === "SpreadElement") {
1885
+ throw new UnsupportedPatternError(`when({ ... }) does not support spread properties`);
1886
+ }
1887
+ if (property.type !== "ObjectProperty") {
1888
+ throw new UnsupportedPatternError(`when({ ... }) only supports plain object properties`);
1889
+ }
1890
+ if (property.computed || property.key.type !== "StringLiteral") {
1891
+ throw new UnsupportedPatternError(`when({ ... }) selector keys must be string literals`);
1892
+ }
1893
+ const value = unwrapExpression(property.value);
1894
+ if (value.type !== "MemberExpression" || value.computed || value.property.type !== "Identifier" || value.property.name !== "$") {
1895
+ throw new UnsupportedPatternError(`when({ ... }) values must be Css.*.$ expressions`);
1896
+ }
1897
+ const innerChain = extractChain(value.object, cssBindingName);
1898
+ if (!innerChain) {
1899
+ throw new UnsupportedPatternError(`when({ ... }) values must be Css.*.$ expressions`);
1900
+ }
1901
+ const selectorContext = cloneConditionContext(initialContext);
1902
+ selectorContext.pseudoClass = property.key.value;
1903
+ const resolved = resolveFullChain(innerChain, mapping, cssBindingName, selectorContext);
1904
+ parts.push(...resolved.parts);
1905
+ markers.push(...resolved.markers);
1906
+ errors.push(...resolved.errors);
1907
+ } catch (err) {
1908
+ if (err instanceof UnsupportedPatternError) {
1909
+ errors.push(err.message);
1910
+ } else {
1911
+ throw err;
1912
+ }
1913
+ }
1914
+ }
1915
+ return { parts, markers, errors: [...new Set(errors)] };
1916
+ }
1917
+ function flattenWhenObjectParts(resolved) {
1918
+ const segments = [];
1919
+ for (const part of resolved.parts) {
1920
+ if (part.type !== "unconditional") {
1921
+ throw new UnsupportedPatternError(`when({ ... }) values cannot use if()/else in this context`);
1922
+ }
1923
+ segments.push(...part.segments);
1924
+ }
1925
+ for (const err of resolved.errors) {
1926
+ segments.push({ abbr: "__error", defs: {}, error: err });
1927
+ }
1928
+ return segments;
1929
+ }
1930
+ function getMediaConditionalStartNode(node, mapping) {
1931
+ if (node.type === "if" && node.conditionNode.type === "StringLiteral") {
1932
+ return {
1933
+ inverseMediaQuery: invertMediaQuery(node.conditionNode.value),
1934
+ thenNodes: [makeMediaQueryNode(node.conditionNode.value)]
1935
+ };
1936
+ }
1937
+ if (node.type === "getter" && mapping.breakpoints && node.name in mapping.breakpoints) {
1938
+ return { inverseMediaQuery: invertMediaQuery(mapping.breakpoints[node.name]) };
1939
+ }
1940
+ return null;
1941
+ }
1942
+ function findElseIndex(chain, start) {
1625
1943
  for (let i = start; i < chain.length; i++) {
1626
1944
  if (chain[i].type === "if") {
1627
1945
  return -1;
@@ -1656,7 +1974,7 @@ function invertMediaQuery(query) {
1656
1974
  }
1657
1975
  return query.replace("@media", "@media not");
1658
1976
  }
1659
- function resolveChain(chain, mapping, initialContext = emptyConditionContext()) {
1977
+ function resolveChain(chain, mapping, initialContext = emptyConditionContext(), cssBindingName) {
1660
1978
  const segments = [];
1661
1979
  const context = cloneConditionContext(initialContext);
1662
1980
  for (const node of chain) {
@@ -1751,6 +2069,11 @@ function resolveChain(chain, mapping, initialContext = emptyConditionContext())
1751
2069
  continue;
1752
2070
  }
1753
2071
  if (abbr === "when") {
2072
+ if (isWhenObjectCall(node)) {
2073
+ const resolved2 = resolveWhenObjectSelectors(node, mapping, cssBindingName, context);
2074
+ segments.push(...flattenWhenObjectParts(resolved2));
2075
+ continue;
2076
+ }
1754
2077
  const resolved = resolveWhenCall(node);
1755
2078
  if (resolved.kind === "selector") {
1756
2079
  context.pseudoClass = resolved.pseudo;
@@ -2120,399 +2443,167 @@ function resolveWhenCall(node) {
2120
2443
  if (relationshipArg.type !== "StringLiteral") {
2121
2444
  throw new UnsupportedPatternError(`when() relationship argument must be a string literal`);
2122
2445
  }
2123
- const relationship = relationshipArg.value;
2124
- if (!WHEN_RELATIONSHIPS.has(relationship)) {
2125
- throw new UnsupportedPatternError(
2126
- `when() relationship must be one of: ${[...WHEN_RELATIONSHIPS].join(", ")} -- got "${relationship}"`
2127
- );
2128
- }
2129
- const pseudoArg = node.args[2];
2130
- if (pseudoArg.type !== "StringLiteral") {
2131
- throw new UnsupportedPatternError(`when() pseudo selector (3rd argument) must be a string literal`);
2132
- }
2133
- return { kind: "relationship", pseudo: pseudoArg.value, markerNode, relationship };
2134
- }
2135
- function resolveWhenMarker(node) {
2136
- if (isDefaultMarkerNode(node)) {
2137
- return void 0;
2138
- }
2139
- if (node.type === "Identifier") {
2140
- return node;
2141
- }
2142
- throw new UnsupportedPatternError(`when() marker must be a marker variable or marker`);
2143
- }
2144
- function isDefaultMarkerNode(node) {
2145
- if (node.type === "Identifier" && (node.name === "marker" || node.name === "defaultMarker")) {
2146
- return true;
2147
- }
2148
- return isLegacyDefaultMarkerExpression(node);
2149
- }
2150
- function isLegacyDefaultMarkerExpression(node) {
2151
- 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";
2152
- }
2153
- var PSEUDO_METHODS = {
2154
- onHover: ":hover",
2155
- onFocus: ":focus",
2156
- onFocusVisible: ":focus-visible",
2157
- onFocusWithin: ":focus-within",
2158
- onActive: ":active",
2159
- onDisabled: ":disabled",
2160
- ifFirstOfType: ":first-of-type",
2161
- ifLastOfType: ":last-of-type"
2162
- };
2163
- function isPseudoMethod(name) {
2164
- return name in PSEUDO_METHODS;
2165
- }
2166
- function pseudoSelector(name) {
2167
- return PSEUDO_METHODS[name];
2168
- }
2169
- function tryEvaluateLiteral(node, incremented, increment) {
2170
- if (node.type === "NumericLiteral") {
2171
- if (incremented) {
2172
- return `${node.value * increment}px`;
2173
- }
2174
- return String(node.value);
2175
- }
2176
- if (node.type === "StringLiteral") {
2177
- return node.value;
2178
- }
2179
- if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2180
- const val = -node.argument.value;
2181
- if (incremented) {
2182
- return `${val * increment}px`;
2183
- }
2184
- return String(val);
2185
- }
2186
- return null;
2187
- }
2188
- function tryEvaluatePxLiteral(node) {
2189
- if (node.type === "NumericLiteral") {
2190
- return `${node.value}px`;
2191
- }
2192
- return null;
2193
- }
2194
- function containerSelectorFromCall(node) {
2195
- if (node.args.length !== 1) {
2196
- throw new UnsupportedPatternError(`ifContainer() expects exactly 1 argument, got ${node.args.length}`);
2197
- }
2198
- const arg = node.args[0];
2199
- if (!arg || arg.type !== "ObjectExpression") {
2200
- throw new UnsupportedPatternError("ifContainer() expects an object literal argument");
2201
- }
2202
- let lt;
2203
- let gt;
2204
- let name;
2205
- for (const prop of arg.properties) {
2206
- if (prop.type === "SpreadElement") {
2207
- throw new UnsupportedPatternError("ifContainer() does not support spread properties");
2208
- }
2209
- if (prop.type !== "ObjectProperty" || prop.computed) {
2210
- throw new UnsupportedPatternError("ifContainer() expects plain object properties");
2211
- }
2212
- const key = objectPropertyName(prop.key);
2213
- if (!key) {
2214
- throw new UnsupportedPatternError("ifContainer() only supports identifier/string keys");
2215
- }
2216
- const valueNode = prop.value;
2217
- if (key === "lt") {
2218
- lt = numericLiteralValue(valueNode, "ifContainer().lt must be a numeric literal");
2219
- continue;
2220
- }
2221
- if (key === "gt") {
2222
- gt = numericLiteralValue(valueNode, "ifContainer().gt must be a numeric literal");
2223
- continue;
2224
- }
2225
- if (key === "name") {
2226
- name = stringLiteralValue(valueNode, "ifContainer().name must be a string literal");
2227
- continue;
2228
- }
2229
- throw new UnsupportedPatternError(`ifContainer() does not support property "${key}"`);
2230
- }
2231
- if (lt === void 0 && gt === void 0) {
2232
- throw new UnsupportedPatternError('ifContainer() requires at least one of "lt" or "gt"');
2233
- }
2234
- const parts = [];
2235
- if (gt !== void 0) {
2236
- parts.push(`(min-width: ${gt + 1}px)`);
2237
- }
2238
- if (lt !== void 0) {
2239
- parts.push(`(max-width: ${lt}px)`);
2240
- }
2241
- const query = parts.join(" and ");
2242
- const namePrefix = name ? `${name} ` : "";
2243
- return `@container ${namePrefix}${query}`;
2244
- }
2245
- function objectPropertyName(node) {
2246
- if (node.type === "Identifier") return node.name;
2247
- if (node.type === "StringLiteral") return node.value;
2248
- return null;
2249
- }
2250
- function numericLiteralValue(node, errorMessage) {
2251
- if (node.type === "NumericLiteral") {
2252
- return node.value;
2253
- }
2254
- if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2255
- return -node.argument.value;
2256
- }
2257
- throw new UnsupportedPatternError(errorMessage);
2258
- }
2259
- function stringLiteralValue(node, errorMessage) {
2260
- if (node.type === "StringLiteral") {
2261
- return node.value;
2262
- }
2263
- if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1) {
2264
- return node.quasis[0].value.cooked ?? "";
2265
- }
2266
- throw new UnsupportedPatternError(errorMessage);
2267
- }
2268
- var UnsupportedPatternError = class extends Error {
2269
- constructor(message) {
2270
- super(`[truss] Unsupported pattern: ${message}`);
2271
- this.name = "UnsupportedPatternError";
2272
- }
2273
- };
2274
-
2275
- // src/plugin/ast-utils.ts
2276
- import * as t2 from "@babel/types";
2277
- function collectTopLevelBindings(ast) {
2278
- const used = /* @__PURE__ */ new Set();
2279
- for (const node of ast.program.body) {
2280
- if (t2.isImportDeclaration(node)) {
2281
- for (const spec of node.specifiers) {
2282
- used.add(spec.local.name);
2283
- }
2284
- continue;
2285
- }
2286
- if (t2.isVariableDeclaration(node)) {
2287
- for (const decl of node.declarations) {
2288
- collectPatternBindings(decl.id, used);
2289
- }
2290
- continue;
2291
- }
2292
- if (t2.isFunctionDeclaration(node) && node.id) {
2293
- used.add(node.id.name);
2294
- continue;
2295
- }
2296
- if (t2.isClassDeclaration(node) && node.id) {
2297
- used.add(node.id.name);
2298
- continue;
2299
- }
2300
- if (t2.isExportNamedDeclaration(node) && node.declaration) {
2301
- const decl = node.declaration;
2302
- if (t2.isVariableDeclaration(decl)) {
2303
- for (const varDecl of decl.declarations) {
2304
- collectPatternBindings(varDecl.id, used);
2305
- }
2306
- } else if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
2307
- used.add(decl.id.name);
2308
- }
2309
- continue;
2310
- }
2311
- if (t2.isExportDefaultDeclaration(node)) {
2312
- const decl = node.declaration;
2313
- if ((t2.isFunctionDeclaration(decl) || t2.isClassDeclaration(decl)) && decl.id) {
2314
- used.add(decl.id.name);
2315
- }
2316
- }
2317
- }
2318
- return used;
2319
- }
2320
- function collectPatternBindings(pattern, used) {
2321
- if (t2.isVoidPattern(pattern)) {
2322
- return;
2323
- }
2324
- if (t2.isIdentifier(pattern)) {
2325
- used.add(pattern.name);
2326
- return;
2327
- }
2328
- if (t2.isAssignmentPattern(pattern)) {
2329
- collectPatternBindings(pattern.left, used);
2330
- return;
2331
- }
2332
- if (t2.isRestElement(pattern)) {
2333
- collectPatternBindings(pattern.argument, used);
2334
- return;
2335
- }
2336
- if (t2.isObjectPattern(pattern)) {
2337
- for (const prop of pattern.properties) {
2338
- if (t2.isObjectProperty(prop)) {
2339
- collectPatternBindings(prop.value, used);
2340
- } else if (t2.isRestElement(prop)) {
2341
- collectPatternBindings(prop.argument, used);
2342
- }
2343
- }
2344
- return;
2345
- }
2346
- if (t2.isArrayPattern(pattern)) {
2347
- for (const el of pattern.elements) {
2348
- if (!el) continue;
2349
- if (t2.isIdentifier(el) || t2.isAssignmentPattern(el) || t2.isObjectPattern(el) || t2.isArrayPattern(el)) {
2350
- collectPatternBindings(el, used);
2351
- } else if (t2.isRestElement(el)) {
2352
- collectPatternBindings(el.argument, used);
2353
- }
2354
- }
2355
- }
2356
- }
2357
- function reservePreferredName(used, preferred, secondary) {
2358
- if (!used.has(preferred)) {
2359
- used.add(preferred);
2360
- return preferred;
2361
- }
2362
- if (secondary && !used.has(secondary)) {
2363
- used.add(secondary);
2364
- return secondary;
2365
- }
2366
- const base = secondary ?? preferred;
2367
- let i = 1;
2368
- let candidate = `${base}_${i}`;
2369
- while (used.has(candidate)) {
2370
- i++;
2371
- candidate = `${base}_${i}`;
2372
- }
2373
- used.add(candidate);
2374
- return candidate;
2375
- }
2376
- function findCssImportBinding(ast) {
2377
- for (const node of ast.program.body) {
2378
- if (!t2.isImportDeclaration(node)) continue;
2379
- for (const spec of node.specifiers) {
2380
- if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: "Css" })) {
2381
- return spec.local.name;
2382
- }
2383
- }
2446
+ const relationship = relationshipArg.value;
2447
+ if (!WHEN_RELATIONSHIPS.has(relationship)) {
2448
+ throw new UnsupportedPatternError(
2449
+ `when() relationship must be one of: ${[...WHEN_RELATIONSHIPS].join(", ")} -- got "${relationship}"`
2450
+ );
2384
2451
  }
2385
- return null;
2452
+ const pseudoArg = node.args[2];
2453
+ if (pseudoArg.type !== "StringLiteral") {
2454
+ throw new UnsupportedPatternError(`when() pseudo selector (3rd argument) must be a string literal`);
2455
+ }
2456
+ return { kind: "relationship", pseudo: pseudoArg.value, markerNode, relationship };
2386
2457
  }
2387
- function findCssBuilderBinding(ast) {
2388
- for (const node of ast.program.body) {
2389
- if (!t2.isVariableDeclaration(node)) continue;
2390
- for (const decl of node.declarations) {
2391
- if (t2.isIdentifier(decl.id) && decl.init && t2.isNewExpression(decl.init) && t2.isIdentifier(decl.init.callee, { name: "CssBuilder" })) {
2392
- return decl.id.name;
2393
- }
2394
- }
2458
+ function resolveWhenMarker(node) {
2459
+ if (isDefaultMarkerNode(node)) {
2460
+ return void 0;
2395
2461
  }
2396
- return null;
2462
+ if (node.type === "Identifier") {
2463
+ return node;
2464
+ }
2465
+ throw new UnsupportedPatternError(`when() marker must be a marker variable or marker`);
2397
2466
  }
2398
- function removeCssImport(ast, cssBinding) {
2399
- for (let i = 0; i < ast.program.body.length; i++) {
2400
- const node = ast.program.body[i];
2401
- if (!t2.isImportDeclaration(node)) continue;
2402
- const cssSpecIndex = node.specifiers.findIndex((s) => t2.isImportSpecifier(s) && s.local.name === cssBinding);
2403
- if (cssSpecIndex === -1) continue;
2404
- if (node.specifiers.length === 1) {
2405
- ast.program.body.splice(i, 1);
2406
- } else {
2407
- node.specifiers.splice(cssSpecIndex, 1);
2408
- }
2409
- return;
2467
+ function isDefaultMarkerNode(node) {
2468
+ if (node.type === "Identifier" && (node.name === "marker" || node.name === "defaultMarker")) {
2469
+ return true;
2410
2470
  }
2471
+ return isLegacyDefaultMarkerExpression(node);
2411
2472
  }
2412
- function findLastImportIndex(ast) {
2413
- let lastImportIndex = -1;
2414
- for (let i = 0; i < ast.program.body.length; i++) {
2415
- if (t2.isImportDeclaration(ast.program.body[i])) {
2416
- lastImportIndex = i;
2473
+ function isLegacyDefaultMarkerExpression(node) {
2474
+ 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";
2475
+ }
2476
+ var PSEUDO_METHODS = {
2477
+ onHover: ":hover",
2478
+ onFocus: ":focus",
2479
+ onFocusVisible: ":focus-visible",
2480
+ onFocusWithin: ":focus-within",
2481
+ onActive: ":active",
2482
+ onDisabled: ":disabled",
2483
+ ifFirstOfType: ":first-of-type",
2484
+ ifLastOfType: ":last-of-type"
2485
+ };
2486
+ function isPseudoMethod(name) {
2487
+ return name in PSEUDO_METHODS;
2488
+ }
2489
+ function pseudoSelector(name) {
2490
+ return PSEUDO_METHODS[name];
2491
+ }
2492
+ function tryEvaluateLiteral(node, incremented, increment) {
2493
+ if (node.type === "NumericLiteral") {
2494
+ if (incremented) {
2495
+ return `${node.value * increment}px`;
2417
2496
  }
2497
+ return String(node.value);
2418
2498
  }
2419
- return lastImportIndex;
2420
- }
2421
- function findNamedImportBinding(ast, source, importedName) {
2422
- for (const node of ast.program.body) {
2423
- if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
2424
- for (const spec of node.specifiers) {
2425
- if (t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: importedName })) {
2426
- return spec.local.name;
2427
- }
2499
+ if (node.type === "StringLiteral") {
2500
+ return node.value;
2501
+ }
2502
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2503
+ const val = -node.argument.value;
2504
+ if (incremented) {
2505
+ return `${val * increment}px`;
2428
2506
  }
2507
+ return String(val);
2429
2508
  }
2430
2509
  return null;
2431
2510
  }
2432
- function findImportDeclaration(ast, source) {
2433
- for (const node of ast.program.body) {
2434
- if (t2.isImportDeclaration(node) && node.source.value === source) {
2435
- return node;
2436
- }
2511
+ function tryEvaluatePxLiteral(node) {
2512
+ if (node.type === "NumericLiteral") {
2513
+ return `${node.value}px`;
2437
2514
  }
2438
2515
  return null;
2439
2516
  }
2440
- function replaceCssImportWithNamedImports(ast, cssBinding, source, imports) {
2441
- for (const node of ast.program.body) {
2442
- if (!t2.isImportDeclaration(node)) continue;
2443
- const cssSpecIndex = node.specifiers.findIndex((spec) => {
2444
- return t2.isImportSpecifier(spec) && spec.local.name === cssBinding;
2445
- });
2446
- if (cssSpecIndex === -1 || node.specifiers.length !== 1) continue;
2447
- node.source = t2.stringLiteral(source);
2448
- node.specifiers = imports.map((entry) => {
2449
- return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
2450
- });
2451
- return true;
2517
+ function containerSelectorFromCall(node) {
2518
+ if (node.args.length !== 1) {
2519
+ throw new UnsupportedPatternError(`ifContainer() expects exactly 1 argument, got ${node.args.length}`);
2452
2520
  }
2453
- return false;
2454
- }
2455
- function upsertNamedImports(ast, source, imports) {
2456
- if (imports.length === 0) return;
2457
- for (const node of ast.program.body) {
2458
- if (!t2.isImportDeclaration(node) || node.source.value !== source) continue;
2459
- for (const entry of imports) {
2460
- const exists = node.specifiers.some((spec) => {
2461
- return t2.isImportSpecifier(spec) && t2.isIdentifier(spec.imported, { name: entry.importedName });
2462
- });
2463
- if (exists) continue;
2464
- node.specifiers.push(t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName)));
2521
+ const arg = node.args[0];
2522
+ if (!arg || arg.type !== "ObjectExpression") {
2523
+ throw new UnsupportedPatternError("ifContainer() expects an object literal argument");
2524
+ }
2525
+ let lt;
2526
+ let gt;
2527
+ let name;
2528
+ for (const prop of arg.properties) {
2529
+ if (prop.type === "SpreadElement") {
2530
+ throw new UnsupportedPatternError("ifContainer() does not support spread properties");
2465
2531
  }
2466
- return;
2532
+ if (prop.type !== "ObjectProperty" || prop.computed) {
2533
+ throw new UnsupportedPatternError("ifContainer() expects plain object properties");
2534
+ }
2535
+ const key = objectPropertyName(prop.key);
2536
+ if (!key) {
2537
+ throw new UnsupportedPatternError("ifContainer() only supports identifier/string keys");
2538
+ }
2539
+ const valueNode = prop.value;
2540
+ if (key === "lt") {
2541
+ lt = numericLiteralValue(valueNode, "ifContainer().lt must be a numeric literal");
2542
+ continue;
2543
+ }
2544
+ if (key === "gt") {
2545
+ gt = numericLiteralValue(valueNode, "ifContainer().gt must be a numeric literal");
2546
+ continue;
2547
+ }
2548
+ if (key === "name") {
2549
+ name = stringLiteralValue(valueNode, "ifContainer().name must be a string literal");
2550
+ continue;
2551
+ }
2552
+ throw new UnsupportedPatternError(`ifContainer() does not support property "${key}"`);
2467
2553
  }
2468
- const importDecl = t2.importDeclaration(
2469
- imports.map((entry) => {
2470
- return t2.importSpecifier(t2.identifier(entry.localName), t2.identifier(entry.importedName));
2471
- }),
2472
- t2.stringLiteral(source)
2473
- );
2474
- const idx = findLastImportIndex(ast);
2475
- ast.program.body.splice(idx + 1, 0, importDecl);
2554
+ if (lt === void 0 && gt === void 0) {
2555
+ throw new UnsupportedPatternError('ifContainer() requires at least one of "lt" or "gt"');
2556
+ }
2557
+ const parts = [];
2558
+ if (gt !== void 0) {
2559
+ parts.push(`(min-width: ${gt + 1}px)`);
2560
+ }
2561
+ if (lt !== void 0) {
2562
+ parts.push(`(max-width: ${lt}px)`);
2563
+ }
2564
+ const query = parts.join(" and ");
2565
+ const namePrefix = name ? `${name} ` : "";
2566
+ return `@container ${namePrefix}${query}`;
2476
2567
  }
2477
- function extractChain(node, cssBinding) {
2478
- const chain = [];
2568
+ function objectPropertyName(node) {
2569
+ if (node.type === "Identifier") return node.name;
2570
+ if (node.type === "StringLiteral") return node.value;
2571
+ return null;
2572
+ }
2573
+ function unwrapExpression(node) {
2479
2574
  let current = node;
2480
2575
  while (true) {
2481
- if (t2.isIdentifier(current, { name: cssBinding })) {
2482
- chain.reverse();
2483
- return chain;
2484
- }
2485
- if (t2.isMemberExpression(current) && !current.computed && t2.isIdentifier(current.property)) {
2486
- const name = current.property.name;
2487
- if (name === "else") {
2488
- chain.push({ type: "else" });
2489
- } else {
2490
- chain.push({ type: "getter", name });
2491
- }
2492
- current = current.object;
2493
- continue;
2494
- }
2495
- if (t2.isCallExpression(current) && t2.isMemberExpression(current.callee) && !current.callee.computed && t2.isIdentifier(current.callee.property)) {
2496
- const name = current.callee.property.name;
2497
- if (name === "if") {
2498
- chain.push({
2499
- type: "if",
2500
- conditionNode: current.arguments[0]
2501
- });
2502
- current = current.callee.object;
2503
- continue;
2504
- }
2505
- chain.push({
2506
- type: "call",
2507
- name,
2508
- args: current.arguments
2509
- });
2510
- current = current.callee.object;
2576
+ if (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression") {
2577
+ current = current.expression;
2511
2578
  continue;
2512
2579
  }
2513
- return null;
2580
+ return current;
2581
+ }
2582
+ }
2583
+ function numericLiteralValue(node, errorMessage) {
2584
+ if (node.type === "NumericLiteral") {
2585
+ return node.value;
2586
+ }
2587
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral") {
2588
+ return -node.argument.value;
2589
+ }
2590
+ throw new UnsupportedPatternError(errorMessage);
2591
+ }
2592
+ function stringLiteralValue(node, errorMessage) {
2593
+ if (node.type === "StringLiteral") {
2594
+ return node.value;
2595
+ }
2596
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1) {
2597
+ return node.quasis[0].value.cooked ?? "";
2514
2598
  }
2599
+ throw new UnsupportedPatternError(errorMessage);
2515
2600
  }
2601
+ var UnsupportedPatternError = class extends Error {
2602
+ constructor(message) {
2603
+ super(`[truss] Unsupported pattern: ${message}`);
2604
+ this.name = "UnsupportedPatternError";
2605
+ }
2606
+ };
2516
2607
 
2517
2608
  // src/plugin/rewrite-sites.ts
2518
2609
  import _traverse from "@babel/traverse";
@@ -2552,6 +2643,20 @@ function getCssAttributePath(path) {
2552
2643
  function buildStyleHashFromChain(chain, options) {
2553
2644
  const members = [];
2554
2645
  const previousProperties = /* @__PURE__ */ new Map();
2646
+ const pendingUnconditionalSegments = [];
2647
+ function flushPendingUnconditionalSegments() {
2648
+ if (pendingUnconditionalSegments.length === 0) {
2649
+ return;
2650
+ }
2651
+ const partMembers = buildStyleHashMembers(pendingUnconditionalSegments, options);
2652
+ members.push(...partMembers);
2653
+ for (const member of partMembers) {
2654
+ if (t3.isObjectProperty(member)) {
2655
+ previousProperties.set(propertyName(member.key), member);
2656
+ }
2657
+ }
2658
+ pendingUnconditionalSegments.length = 0;
2659
+ }
2555
2660
  if (chain.markers.length > 0) {
2556
2661
  const markerClasses = chain.markers.map((marker) => {
2557
2662
  return markerClassName(marker.markerNode);
@@ -2560,14 +2665,9 @@ function buildStyleHashFromChain(chain, options) {
2560
2665
  }
2561
2666
  for (const part of chain.parts) {
2562
2667
  if (part.type === "unconditional") {
2563
- const partMembers = buildStyleHashMembers(part.segments, options);
2564
- members.push(...partMembers);
2565
- for (const member of partMembers) {
2566
- if (t3.isObjectProperty(member)) {
2567
- previousProperties.set(propertyName(member.key), member);
2568
- }
2569
- }
2668
+ pendingUnconditionalSegments.push(...part.segments);
2570
2669
  } else {
2670
+ flushPendingUnconditionalSegments();
2571
2671
  const thenMembers = mergeConditionalBranchMembers(
2572
2672
  buildStyleHashMembers(part.thenSegments, options),
2573
2673
  previousProperties,
@@ -2585,6 +2685,7 @@ function buildStyleHashFromChain(chain, options) {
2585
2685
  );
2586
2686
  }
2587
2687
  }
2688
+ flushPendingUnconditionalSegments();
2588
2689
  return t3.objectExpression(members);
2589
2690
  }
2590
2691
  function buildStyleHashMembers(segments, options) {
@@ -2976,11 +3077,14 @@ function transformTruss(code, filename, mapping, options = {}) {
2976
3077
  hasRuntimeStyleCssUsage = true;
2977
3078
  return;
2978
3079
  }
3080
+ if (isInsideWhenObjectValue(path, cssBindingName)) {
3081
+ return;
3082
+ }
2979
3083
  const parentPath = path.parentPath;
2980
3084
  if (parentPath && parentPath.isMemberExpression() && t4.isIdentifier(parentPath.node.property, { name: "$" })) {
2981
3085
  return;
2982
3086
  }
2983
- const resolvedChain = resolveFullChain(chain, mapping);
3087
+ const resolvedChain = resolveFullChain(chain, mapping, cssBindingName);
2984
3088
  sites.push({ path, resolvedChain });
2985
3089
  const line = path.node.loc?.start.line ?? null;
2986
3090
  for (const err of resolvedChain.errors) {
@@ -3124,6 +3228,19 @@ function isRuntimeStyleCssAttribute2(path) {
3124
3228
  if (!openingElementPath || !openingElementPath.isJSXOpeningElement()) return false;
3125
3229
  return t4.isJSXIdentifier(openingElementPath.node.name, { name: "RuntimeStyle" });
3126
3230
  }
3231
+ function isInsideWhenObjectValue(path, cssBindingName) {
3232
+ let current = path.parentPath;
3233
+ while (current) {
3234
+ if (current.isObjectExpression()) {
3235
+ const parent = current.parentPath;
3236
+ if (parent?.isCallExpression() && parent.node.arguments[0] === current.node && t4.isMemberExpression(parent.node.callee) && !parent.node.callee.computed && t4.isIdentifier(parent.node.callee.property, { name: "when" }) && extractChain(parent.node.callee.object, cssBindingName)) {
3237
+ return true;
3238
+ }
3239
+ }
3240
+ current = current.parentPath;
3241
+ }
3242
+ return false;
3243
+ }
3127
3244
  function collectRuntimeLookups(chains) {
3128
3245
  const lookups = /* @__PURE__ */ new Map();
3129
3246
  for (const chain of chains) {
@@ -3331,7 +3448,7 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
3331
3448
  if (n.type === "if") return { error: "if() conditionals are not supported in .css.ts files" };
3332
3449
  if (n.type === "else") return { error: "else is not supported in .css.ts files" };
3333
3450
  }
3334
- const resolved = resolveFullChain(chain, mapping);
3451
+ const resolved = resolveFullChain(chain, mapping, cssBindingName);
3335
3452
  if (resolved.errors.length > 0) {
3336
3453
  return { error: resolved.errors[0] };
3337
3454
  }