@homebound/truss 2.0.0-next.1 → 2.0.0-next.11

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.
@@ -142,6 +142,17 @@ function resolveChain(chain, mapping) {
142
142
  segments.push(seg);
143
143
  continue;
144
144
  }
145
+ if (abbr === "typography") {
146
+ const resolved = resolveTypographyCall(
147
+ node,
148
+ mapping,
149
+ currentMediaQuery,
150
+ currentPseudoClass,
151
+ currentPseudoElement
152
+ );
153
+ segments.push(...resolved);
154
+ continue;
155
+ }
145
156
  if (abbr === "element") {
146
157
  if (node.args.length !== 1 || node.args[0].type !== "StringLiteral") {
147
158
  throw new UnsupportedPatternError(
@@ -215,6 +226,52 @@ function conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, breakpoints)
215
226
  if (pseudoClass) parts.push(pseudoName(pseudoClass));
216
227
  return parts.join("_");
217
228
  }
229
+ function resolveTypographyCall(node, mapping, mediaQuery, pseudoClass, pseudoElement) {
230
+ if (node.args.length !== 1) {
231
+ throw new UnsupportedPatternError(`typography() expects exactly 1 argument, got ${node.args.length}`);
232
+ }
233
+ const argAst = node.args[0];
234
+ if (argAst.type === "StringLiteral") {
235
+ return resolveTypographyEntry(argAst.value, mapping, mediaQuery, pseudoClass, pseudoElement);
236
+ }
237
+ const typography = mapping.typography ?? [];
238
+ if (typography.length === 0) {
239
+ throw new UnsupportedPatternError(`typography() is unavailable because no typography abbreviations were generated`);
240
+ }
241
+ const suffix = conditionKeySuffix(mediaQuery, pseudoClass, pseudoElement, mapping.breakpoints);
242
+ const lookupKey = suffix ? `typography__${suffix}` : "typography";
243
+ const segmentsByName = {};
244
+ for (const name of typography) {
245
+ segmentsByName[name] = resolveTypographyEntry(name, mapping, mediaQuery, pseudoClass, pseudoElement);
246
+ }
247
+ return [
248
+ {
249
+ key: lookupKey,
250
+ defs: {},
251
+ typographyLookup: {
252
+ lookupKey,
253
+ argNode: argAst,
254
+ segmentsByName
255
+ }
256
+ }
257
+ ];
258
+ }
259
+ function resolveTypographyEntry(name, mapping, mediaQuery, pseudoClass, pseudoElement) {
260
+ if (!(mapping.typography ?? []).includes(name)) {
261
+ throw new UnsupportedPatternError(`Unknown typography abbreviation "${name}"`);
262
+ }
263
+ const entry = mapping.abbreviations[name];
264
+ if (!entry) {
265
+ throw new UnsupportedPatternError(`Unknown typography abbreviation "${name}"`);
266
+ }
267
+ const resolved = resolveEntry(name, entry, mapping, mediaQuery, pseudoClass, pseudoElement, null);
268
+ for (const segment of resolved) {
269
+ if (segment.dynamicProps || segment.whenPseudo) {
270
+ throw new UnsupportedPatternError(`Typography abbreviation "${name}" cannot require runtime arguments`);
271
+ }
272
+ }
273
+ return resolved;
274
+ }
218
275
  function wrapDefsWithConditions(defs, mediaQuery, pseudoClass) {
219
276
  if (!mediaQuery && !pseudoClass) return defs;
220
277
  const result = {};
@@ -869,12 +926,17 @@ function extractChain(node, cssBinding) {
869
926
  import * as t2 from "@babel/types";
870
927
  function collectCreateData(chains) {
871
928
  const createEntries = /* @__PURE__ */ new Map();
929
+ const runtimeLookups = /* @__PURE__ */ new Map();
872
930
  let needsMaybeInc = false;
873
931
  for (const chain of chains) {
874
932
  for (const part of chain.parts) {
875
933
  const segs = part.type === "unconditional" ? part.segments : [...part.thenSegments, ...part.elseSegments];
876
934
  for (const seg of segs) {
877
935
  if (seg.error) continue;
936
+ if (seg.typographyLookup) {
937
+ collectTypographyLookup(createEntries, runtimeLookups, seg);
938
+ continue;
939
+ }
878
940
  if (seg.dynamicProps) {
879
941
  if (!createEntries.has(seg.key)) {
880
942
  createEntries.set(seg.key, {
@@ -903,7 +965,36 @@ function collectCreateData(chains) {
903
965
  }
904
966
  }
905
967
  }
906
- return { createEntries, needsMaybeInc };
968
+ return { createEntries, runtimeLookups, needsMaybeInc };
969
+ }
970
+ function collectTypographyLookup(createEntries, runtimeLookups, seg) {
971
+ const lookup = seg.typographyLookup;
972
+ if (!lookup) return;
973
+ if (!runtimeLookups.has(lookup.lookupKey)) {
974
+ runtimeLookups.set(lookup.lookupKey, {
975
+ lookupKey: lookup.lookupKey,
976
+ refsByName: Object.fromEntries(
977
+ Object.entries(lookup.segmentsByName).map(function([name, segments]) {
978
+ return [
979
+ name,
980
+ segments.map(function(segment) {
981
+ return segment.key;
982
+ })
983
+ ];
984
+ })
985
+ )
986
+ });
987
+ }
988
+ for (const segments of Object.values(lookup.segmentsByName)) {
989
+ for (const segment of segments) {
990
+ if (createEntries.has(segment.key)) continue;
991
+ createEntries.set(segment.key, {
992
+ key: segment.key,
993
+ defs: segment.defs,
994
+ whenPseudo: segment.whenPseudo
995
+ });
996
+ }
997
+ }
907
998
  }
908
999
  function buildCreateProperties(createEntries, stylexNamespaceName) {
909
1000
  const createProperties = [];
@@ -1039,12 +1130,62 @@ function buildMaybeIncDeclaration(helperName, increment) {
1039
1130
  t2.variableDeclarator(t2.identifier(helperName), t2.arrowFunctionExpression([incParam], body))
1040
1131
  ]);
1041
1132
  }
1133
+ function buildMergePropsDeclaration(helperName, stylexNamespaceName) {
1134
+ const explicitClassNameParam = t2.identifier("explicitClassName");
1135
+ const stylesRestParam = t2.restElement(t2.identifier("styles"));
1136
+ const sxId = t2.identifier("sx");
1137
+ return t2.functionDeclaration(
1138
+ t2.identifier(helperName),
1139
+ [explicitClassNameParam, stylesRestParam],
1140
+ t2.blockStatement([
1141
+ t2.variableDeclaration("const", [
1142
+ t2.variableDeclarator(
1143
+ sxId,
1144
+ t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("props")), [
1145
+ t2.spreadElement(t2.identifier("styles"))
1146
+ ])
1147
+ )
1148
+ ]),
1149
+ t2.returnStatement(
1150
+ t2.objectExpression([
1151
+ t2.spreadElement(sxId),
1152
+ t2.objectProperty(
1153
+ t2.identifier("className"),
1154
+ t2.callExpression(
1155
+ t2.memberExpression(
1156
+ t2.binaryExpression(
1157
+ "+",
1158
+ t2.binaryExpression("+", explicitClassNameParam, t2.stringLiteral(" ")),
1159
+ t2.logicalExpression("||", t2.memberExpression(sxId, t2.identifier("className")), t2.stringLiteral(""))
1160
+ ),
1161
+ t2.identifier("trim")
1162
+ ),
1163
+ []
1164
+ )
1165
+ )
1166
+ ])
1167
+ )
1168
+ ])
1169
+ );
1170
+ }
1042
1171
  function buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties) {
1043
1172
  const createCall = t2.callExpression(t2.memberExpression(t2.identifier(stylexNamespaceName), t2.identifier("create")), [
1044
1173
  t2.objectExpression(createProperties)
1045
1174
  ]);
1046
1175
  return t2.variableDeclaration("const", [t2.variableDeclarator(t2.identifier(createVarName), createCall)]);
1047
1176
  }
1177
+ function buildRuntimeLookupDeclaration(lookupName, createVarName, lookup) {
1178
+ const properties = [];
1179
+ for (const [name, refs] of Object.entries(lookup.refsByName)) {
1180
+ const values = refs.map(function(refKey) {
1181
+ return t2.memberExpression(t2.identifier(createVarName), t2.identifier(refKey));
1182
+ });
1183
+ properties.push(t2.objectProperty(toPropertyKey(name), t2.arrayExpression(values)));
1184
+ }
1185
+ return t2.variableDeclaration("const", [
1186
+ t2.variableDeclarator(t2.identifier(lookupName), t2.objectExpression(properties))
1187
+ ]);
1188
+ }
1048
1189
  function defsToAst(defs) {
1049
1190
  const properties = [];
1050
1191
  for (const [key, value] of Object.entries(defs)) {
@@ -1088,53 +1229,22 @@ function rewriteExpressionSites(options) {
1088
1229
  t3.memberExpression(t3.identifier(options.stylexNamespaceName), t3.identifier("props")),
1089
1230
  propsArgs
1090
1231
  );
1091
- const openingElement = cssAttrPath.parentPath;
1092
- let existingClassNameExpr = null;
1093
- if (openingElement && openingElement.isJSXOpeningElement()) {
1094
- const attrs = openingElement.node.attributes;
1095
- for (let i = 0; i < attrs.length; i++) {
1096
- const attr = attrs[i];
1097
- if (t3.isJSXAttribute(attr) && t3.isJSXIdentifier(attr.name, { name: "className" })) {
1098
- if (t3.isStringLiteral(attr.value)) {
1099
- existingClassNameExpr = attr.value;
1100
- } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1101
- existingClassNameExpr = attr.value.expression;
1102
- }
1103
- attrs.splice(i, 1);
1104
- break;
1105
- }
1106
- }
1107
- }
1108
- let spreadExpr;
1109
- if (existingClassNameExpr) {
1110
- const rId = t3.identifier("__r");
1111
- const mergedClassName = t3.callExpression(
1112
- t3.memberExpression(
1113
- t3.binaryExpression(
1114
- "+",
1115
- t3.binaryExpression("+", existingClassNameExpr, t3.stringLiteral(" ")),
1116
- t3.logicalExpression("||", t3.memberExpression(rId, t3.identifier("className")), t3.stringLiteral(""))
1117
- ),
1118
- t3.identifier("trim")
1119
- ),
1120
- []
1121
- );
1122
- spreadExpr = t3.callExpression(
1123
- t3.arrowFunctionExpression(
1124
- [rId],
1125
- t3.objectExpression([t3.spreadElement(rId), t3.objectProperty(t3.identifier("className"), mergedClassName)])
1126
- ),
1127
- [propsCall]
1128
- );
1129
- } else {
1130
- spreadExpr = propsCall;
1131
- }
1132
- cssAttrPath.replaceWith(t3.jsxSpreadAttribute(spreadExpr));
1232
+ cssAttrPath.replaceWith(
1233
+ t3.jsxSpreadAttribute(
1234
+ buildCssSpreadExpression(cssAttrPath, propsCall, options.mergePropsHelperName, options.needsMergePropsHelper)
1235
+ )
1236
+ );
1133
1237
  continue;
1134
1238
  }
1135
1239
  site.path.replaceWith(t3.arrayExpression(propsArgs));
1136
1240
  }
1137
- rewriteCssArrayExpressions(options.ast, options.stylexNamespaceName);
1241
+ rewriteStyleObjectExpressions(options.ast);
1242
+ rewriteCssAttributeExpressions(
1243
+ options.ast,
1244
+ options.stylexNamespaceName,
1245
+ options.mergePropsHelperName,
1246
+ options.needsMergePropsHelper
1247
+ );
1138
1248
  }
1139
1249
  function getCssAttributePath(path) {
1140
1250
  const parentPath = path.parentPath;
@@ -1165,7 +1275,7 @@ function buildPropsArgsFromChain(chain, options) {
1165
1275
  }
1166
1276
  const thenArgs = buildPropsArgs(part.thenSegments, options);
1167
1277
  const elseArgs = buildPropsArgs(part.elseSegments, options);
1168
- if (thenArgs.length === 1 && elseArgs.length === 1) {
1278
+ if (thenArgs.length === 1 && elseArgs.length === 1 && !t3.isSpreadElement(thenArgs[0]) && !t3.isSpreadElement(elseArgs[0])) {
1169
1279
  args.push(t3.conditionalExpression(part.conditionNode, thenArgs[0], elseArgs[0]));
1170
1280
  } else if (thenArgs.length > 0 || elseArgs.length > 0) {
1171
1281
  args.push(
@@ -1181,6 +1291,19 @@ function buildPropsArgs(segments, options) {
1181
1291
  const args = [];
1182
1292
  for (const seg of segments) {
1183
1293
  if (seg.error) continue;
1294
+ if (seg.typographyLookup) {
1295
+ const lookupName = options.runtimeLookupNames.get(seg.typographyLookup.lookupKey);
1296
+ if (!lookupName) {
1297
+ continue;
1298
+ }
1299
+ const lookupAccess = t3.memberExpression(
1300
+ t3.identifier(lookupName),
1301
+ seg.typographyLookup.argNode,
1302
+ true
1303
+ );
1304
+ args.push(t3.spreadElement(t3.logicalExpression("??", lookupAccess, t3.arrayExpression([]))));
1305
+ continue;
1306
+ }
1184
1307
  const ref = t3.memberExpression(t3.identifier(options.createVarName), t3.identifier(seg.key));
1185
1308
  if (seg.dynamicProps && seg.argNode) {
1186
1309
  let argExpr;
@@ -1198,42 +1321,271 @@ function buildPropsArgs(segments, options) {
1198
1321
  }
1199
1322
  return args;
1200
1323
  }
1201
- function rewriteCssArrayExpressions(ast, stylexNamespaceName) {
1324
+ function rewriteCssAttributeExpressions(ast, stylexNamespaceName, mergePropsHelperName, needsMergePropsHelper) {
1202
1325
  traverse(ast, {
1203
1326
  JSXAttribute(path) {
1204
1327
  if (!t3.isJSXIdentifier(path.node.name, { name: "css" })) return;
1205
1328
  const value = path.node.value;
1206
1329
  if (!t3.isJSXExpressionContainer(value)) return;
1207
- const expr = value.expression;
1208
- if (!t3.isArrayExpression(expr)) return;
1209
- const propsArgs = [];
1210
- for (const el of expr.elements) {
1211
- if (t3.isSpreadElement(el)) {
1212
- const arg = el.argument;
1213
- if (t3.isArrayExpression(arg)) {
1214
- for (const inner of arg.elements) {
1215
- if (!inner) continue;
1216
- if (t3.isSpreadElement(inner)) {
1217
- propsArgs.push(t3.spreadElement(inner.argument));
1218
- } else {
1219
- propsArgs.push(inner);
1220
- }
1221
- }
1222
- } else {
1223
- propsArgs.push(t3.spreadElement(arg));
1224
- }
1225
- } else if (el) {
1226
- propsArgs.push(el);
1227
- }
1228
- }
1330
+ if (!t3.isExpression(value.expression)) return;
1331
+ const propsArgs = buildStyleObjectPropsArgs(value.expression, path) ?? buildStyleArrayLikePropsArgsFromExpression(value.expression, path);
1332
+ if (!propsArgs) return;
1229
1333
  const propsCall = t3.callExpression(
1230
1334
  t3.memberExpression(t3.identifier(stylexNamespaceName), t3.identifier("props")),
1231
1335
  propsArgs
1232
1336
  );
1233
- path.replaceWith(t3.jsxSpreadAttribute(propsCall));
1337
+ path.replaceWith(
1338
+ t3.jsxSpreadAttribute(buildCssSpreadExpression(path, propsCall, mergePropsHelperName, needsMergePropsHelper))
1339
+ );
1234
1340
  }
1235
1341
  });
1236
1342
  }
1343
+ function buildCssSpreadExpression(path, propsCall, mergePropsHelperName, needsMergePropsHelper) {
1344
+ const existingClassNameExpr = removeExistingClassNameAttribute(path);
1345
+ if (!existingClassNameExpr) return propsCall;
1346
+ needsMergePropsHelper.current = true;
1347
+ return t3.callExpression(t3.identifier(mergePropsHelperName), [existingClassNameExpr, ...propsCall.arguments]);
1348
+ }
1349
+ function removeExistingClassNameAttribute(path) {
1350
+ const openingElement = path.parentPath;
1351
+ if (!openingElement || !openingElement.isJSXOpeningElement()) return null;
1352
+ const attrs = openingElement.node.attributes;
1353
+ for (let i = 0; i < attrs.length; i++) {
1354
+ const attr = attrs[i];
1355
+ if (!t3.isJSXAttribute(attr) || !t3.isJSXIdentifier(attr.name, { name: "className" })) continue;
1356
+ let classNameExpr = null;
1357
+ if (t3.isStringLiteral(attr.value)) {
1358
+ classNameExpr = attr.value;
1359
+ } else if (t3.isJSXExpressionContainer(attr.value) && t3.isExpression(attr.value.expression)) {
1360
+ classNameExpr = attr.value.expression;
1361
+ }
1362
+ attrs.splice(i, 1);
1363
+ return classNameExpr;
1364
+ }
1365
+ return null;
1366
+ }
1367
+ function buildStyleObjectPropsArgs(expr, path) {
1368
+ if (!t3.isObjectExpression(expr) || expr.properties.length === 0) return null;
1369
+ let sawStyleArray = false;
1370
+ const propsArgs = [];
1371
+ for (const prop of expr.properties) {
1372
+ if (!t3.isSpreadElement(prop)) return null;
1373
+ const normalizedArg = normalizeStyleArrayLikeExpression(prop.argument, path, /* @__PURE__ */ new Set());
1374
+ if (!normalizedArg) {
1375
+ propsArgs.push(t3.spreadElement(prop.argument));
1376
+ continue;
1377
+ }
1378
+ if (isStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1379
+ sawStyleArray = true;
1380
+ }
1381
+ const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path, /* @__PURE__ */ new Set());
1382
+ if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1383
+ propsArgs.push(...nestedArgs);
1384
+ } else {
1385
+ propsArgs.push(t3.spreadElement(normalizedArg));
1386
+ }
1387
+ }
1388
+ return sawStyleArray ? propsArgs : null;
1389
+ }
1390
+ function buildStyleArrayLikePropsArgsFromExpression(expr, path) {
1391
+ const normalizedExpr = normalizeStyleArrayLikeExpression(expr, path, /* @__PURE__ */ new Set());
1392
+ if (!normalizedExpr) return null;
1393
+ return buildStyleArrayLikePropsArgs(normalizedExpr, path, /* @__PURE__ */ new Set());
1394
+ }
1395
+ function buildStyleArrayLikePropsArgs(expr, path, seen) {
1396
+ if (seen.has(expr)) return null;
1397
+ seen.add(expr);
1398
+ if (t3.isArrayExpression(expr)) {
1399
+ const propsArgs = [];
1400
+ for (const el of expr.elements) {
1401
+ if (!el) continue;
1402
+ if (t3.isSpreadElement(el)) {
1403
+ const normalizedArg = normalizeStyleArrayLikeExpression(el.argument, path, /* @__PURE__ */ new Set());
1404
+ if (!normalizedArg) {
1405
+ propsArgs.push(t3.spreadElement(el.argument));
1406
+ continue;
1407
+ }
1408
+ const nestedArgs = buildStyleArrayLikePropsArgs(normalizedArg, path, seen);
1409
+ if (nestedArgs && t3.isArrayExpression(normalizedArg)) {
1410
+ propsArgs.push(...nestedArgs);
1411
+ } else {
1412
+ propsArgs.push(t3.spreadElement(normalizedArg));
1413
+ }
1414
+ continue;
1415
+ }
1416
+ propsArgs.push(el);
1417
+ }
1418
+ return propsArgs;
1419
+ }
1420
+ if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isConditionalExpression(expr) || t3.isLogicalExpression(expr) || t3.isCallExpression(expr)) {
1421
+ return [t3.spreadElement(expr)];
1422
+ }
1423
+ return null;
1424
+ }
1425
+ function rewriteStyleObjectExpressions(ast) {
1426
+ traverse(ast, {
1427
+ ObjectExpression(path) {
1428
+ const rewritten = tryBuildStyleArrayFromObject(path);
1429
+ if (!rewritten) return;
1430
+ path.replaceWith(rewritten);
1431
+ }
1432
+ });
1433
+ }
1434
+ function tryBuildStyleArrayFromObject(path) {
1435
+ if (path.node.properties.length === 0) return null;
1436
+ let sawStyleArray = false;
1437
+ const elements = [];
1438
+ for (const prop of path.node.properties) {
1439
+ if (!t3.isSpreadElement(prop)) {
1440
+ return null;
1441
+ }
1442
+ const normalizedArg = normalizeStyleArrayLikeExpression(
1443
+ prop.argument,
1444
+ path,
1445
+ /* @__PURE__ */ new Set()
1446
+ // I.e. `...Css.df.$`, `...(cond ? Css.df.$ : {})`, or `...styles.wrapper`
1447
+ );
1448
+ if (!normalizedArg) {
1449
+ return null;
1450
+ }
1451
+ if (isStyleArrayLike(normalizedArg, path, /* @__PURE__ */ new Set())) {
1452
+ sawStyleArray = true;
1453
+ }
1454
+ if (t3.isArrayExpression(normalizedArg)) {
1455
+ elements.push(...normalizedArg.elements);
1456
+ continue;
1457
+ }
1458
+ elements.push(t3.spreadElement(normalizedArg));
1459
+ }
1460
+ if (!sawStyleArray) return null;
1461
+ return t3.arrayExpression(elements);
1462
+ }
1463
+ function normalizeStyleArrayLikeExpression(expr, path, seen) {
1464
+ if (seen.has(expr)) return null;
1465
+ seen.add(expr);
1466
+ if (t3.isArrayExpression(expr)) return expr;
1467
+ if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1468
+ const consequent = normalizeStyleArrayLikeExpression(expr.right, path, seen);
1469
+ if (!consequent) return null;
1470
+ return t3.conditionalExpression(expr.left, consequent, t3.arrayExpression([]));
1471
+ }
1472
+ if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1473
+ const left = normalizeStyleArrayLikeExpression(expr.left, path, seen);
1474
+ const right = normalizeStyleArrayLikeBranch(expr.right, path, seen);
1475
+ if (!left || !right) return null;
1476
+ return t3.logicalExpression(expr.operator, left, right);
1477
+ }
1478
+ if (t3.isConditionalExpression(expr)) {
1479
+ const consequent = normalizeStyleArrayLikeBranch(expr.consequent, path, seen);
1480
+ const alternate = normalizeStyleArrayLikeBranch(expr.alternate, path, seen);
1481
+ if (!consequent || !alternate) return null;
1482
+ return t3.conditionalExpression(expr.test, consequent, alternate);
1483
+ }
1484
+ if (t3.isIdentifier(expr) || t3.isMemberExpression(expr) || t3.isCallExpression(expr)) {
1485
+ const nestedSeen = new Set(seen);
1486
+ nestedSeen.delete(expr);
1487
+ if (isStyleArrayLike(expr, path, nestedSeen)) return expr;
1488
+ }
1489
+ return null;
1490
+ }
1491
+ function normalizeStyleArrayLikeBranch(expr, path, seen) {
1492
+ if (isEmptyObjectExpression(expr)) {
1493
+ return t3.arrayExpression([]);
1494
+ }
1495
+ return normalizeStyleArrayLikeExpression(expr, path, seen);
1496
+ }
1497
+ function isStyleArrayLike(expr, path, seen) {
1498
+ if (seen.has(expr)) return false;
1499
+ seen.add(expr);
1500
+ if (t3.isArrayExpression(expr)) return true;
1501
+ if (t3.isLogicalExpression(expr) && expr.operator === "&&") {
1502
+ return isStyleArrayLike(expr.right, path, seen);
1503
+ }
1504
+ if (t3.isLogicalExpression(expr) && (expr.operator === "||" || expr.operator === "??")) {
1505
+ return isStyleArrayLike(expr.left, path, seen) && isStyleArrayLikeBranch(expr.right, path, seen);
1506
+ }
1507
+ if (t3.isConditionalExpression(expr)) {
1508
+ return isStyleArrayLikeBranch(expr.consequent, path, seen) && isStyleArrayLikeBranch(expr.alternate, path, seen);
1509
+ }
1510
+ if (t3.isIdentifier(expr)) {
1511
+ const binding = path.scope.getBinding(expr.name);
1512
+ const bindingPath = binding?.path;
1513
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1514
+ const init = bindingPath.node.init;
1515
+ return !!(init && isStyleArrayLike(init, bindingPath, seen));
1516
+ }
1517
+ if (t3.isCallExpression(expr)) {
1518
+ const returnExpr = getLocalFunctionReturnExpression(expr, path);
1519
+ return !!(returnExpr && isStyleArrayLike(returnExpr, path, seen));
1520
+ }
1521
+ if (t3.isMemberExpression(expr)) {
1522
+ const object = expr.object;
1523
+ if (!t3.isIdentifier(object)) return false;
1524
+ const binding = path.scope.getBinding(object.name);
1525
+ const bindingPath = binding?.path;
1526
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return false;
1527
+ const init = bindingPath.node.init;
1528
+ if (!init || !t3.isObjectExpression(init)) return false;
1529
+ const propertyName = getStaticMemberPropertyName(expr, path);
1530
+ if (!propertyName) return false;
1531
+ for (const prop of init.properties) {
1532
+ if (!t3.isObjectProperty(prop) || prop.computed) continue;
1533
+ if (!isMatchingPropertyName(prop.key, propertyName)) continue;
1534
+ const value = prop.value;
1535
+ return t3.isExpression(value) && isStyleArrayLike(value, bindingPath, seen);
1536
+ }
1537
+ }
1538
+ return false;
1539
+ }
1540
+ function isStyleArrayLikeBranch(expr, path, seen) {
1541
+ return isEmptyObjectExpression(expr) || isStyleArrayLike(expr, path, seen);
1542
+ }
1543
+ function isMatchingPropertyName(key, name) {
1544
+ return t3.isIdentifier(key) && key.name === name || t3.isStringLiteral(key) && key.value === name;
1545
+ }
1546
+ function isEmptyObjectExpression(expr) {
1547
+ return t3.isObjectExpression(expr) && expr.properties.length === 0;
1548
+ }
1549
+ function getStaticMemberPropertyName(expr, path) {
1550
+ if (!expr.computed && t3.isIdentifier(expr.property)) {
1551
+ return expr.property.name;
1552
+ }
1553
+ if (t3.isStringLiteral(expr.property)) {
1554
+ return expr.property.value;
1555
+ }
1556
+ if (t3.isIdentifier(expr.property)) {
1557
+ const binding = path.scope.getBinding(expr.property.name);
1558
+ const bindingPath = binding?.path;
1559
+ if (!bindingPath || !bindingPath.isVariableDeclarator()) return null;
1560
+ const init = bindingPath.node.init;
1561
+ return t3.isStringLiteral(init) ? init.value : null;
1562
+ }
1563
+ return null;
1564
+ }
1565
+ function getLocalFunctionReturnExpression(expr, path) {
1566
+ if (!t3.isIdentifier(expr.callee)) return null;
1567
+ const binding = path.scope.getBinding(expr.callee.name);
1568
+ const bindingPath = binding?.path;
1569
+ if (!bindingPath) return null;
1570
+ if (bindingPath.isFunctionDeclaration()) {
1571
+ return getFunctionLikeReturnExpression(bindingPath.node);
1572
+ }
1573
+ if (bindingPath.isVariableDeclarator()) {
1574
+ const init = bindingPath.node.init;
1575
+ if (init && (t3.isArrowFunctionExpression(init) || t3.isFunctionExpression(init))) {
1576
+ return getFunctionLikeReturnExpression(init);
1577
+ }
1578
+ }
1579
+ return null;
1580
+ }
1581
+ function getFunctionLikeReturnExpression(fn) {
1582
+ if (t3.isExpression(fn.body)) {
1583
+ return fn.body;
1584
+ }
1585
+ if (fn.body.body.length !== 1) return null;
1586
+ const stmt = fn.body.body[0];
1587
+ return t3.isReturnStatement(stmt) && stmt.argument && t3.isExpression(stmt.argument) ? stmt.argument : null;
1588
+ }
1237
1589
 
1238
1590
  // src/plugin/transform.ts
1239
1591
  var traverse2 = _traverse2.default ?? _traverse2;
@@ -1268,19 +1620,28 @@ function transformTruss(code, filename, mapping) {
1268
1620
  }
1269
1621
  });
1270
1622
  if (sites.length === 0) return null;
1271
- const { createEntries, needsMaybeInc } = collectCreateData(sites.map((s) => s.resolvedChain));
1623
+ const { createEntries, runtimeLookups, needsMaybeInc } = collectCreateData(sites.map((s) => s.resolvedChain));
1272
1624
  const usedTopLevelNames = collectTopLevelBindings(ast);
1273
1625
  const existingStylexNamespace = findStylexNamespaceImport(ast);
1274
1626
  const stylexNamespaceName = existingStylexNamespace ?? reservePreferredName(usedTopLevelNames, "stylex");
1275
1627
  const createVarName = reservePreferredName(usedTopLevelNames, "css", "css_");
1276
1628
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
1629
+ const mergePropsHelperName = reservePreferredName(usedTopLevelNames, "__mergeProps");
1630
+ const needsMergePropsHelper = { current: false };
1631
+ const runtimeLookupNames = /* @__PURE__ */ new Map();
1632
+ for (const [lookupKey] of runtimeLookups) {
1633
+ runtimeLookupNames.set(lookupKey, reservePreferredName(usedTopLevelNames, `__${lookupKey}`));
1634
+ }
1277
1635
  const createProperties = buildCreateProperties(createEntries, stylexNamespaceName);
1278
1636
  rewriteExpressionSites({
1279
1637
  ast,
1280
1638
  sites,
1281
1639
  createVarName,
1282
1640
  stylexNamespaceName,
1283
- maybeIncHelperName
1641
+ maybeIncHelperName,
1642
+ mergePropsHelperName,
1643
+ needsMergePropsHelper,
1644
+ runtimeLookupNames
1284
1645
  });
1285
1646
  removeCssImport(ast, cssBindingName);
1286
1647
  if (!findStylexNamespaceImport(ast)) {
@@ -1292,9 +1653,17 @@ function transformTruss(code, filename, mapping) {
1292
1653
  if (maybeIncHelperName) {
1293
1654
  declarationsToInsert.push(buildMaybeIncDeclaration(maybeIncHelperName, mapping.increment));
1294
1655
  }
1656
+ if (needsMergePropsHelper.current) {
1657
+ declarationsToInsert.push(buildMergePropsDeclaration(mergePropsHelperName, stylexNamespaceName));
1658
+ }
1295
1659
  declarationsToInsert.push(...hoistedMarkerDecls);
1296
1660
  if (createProperties.length > 0) {
1297
1661
  declarationsToInsert.push(buildCreateDeclaration(createVarName, stylexNamespaceName, createProperties));
1662
+ for (const [lookupKey, lookup] of runtimeLookups) {
1663
+ const lookupName = runtimeLookupNames.get(lookupKey);
1664
+ if (!lookupName) continue;
1665
+ declarationsToInsert.push(buildRuntimeLookupDeclaration(lookupName, createVarName, lookup));
1666
+ }
1298
1667
  }
1299
1668
  for (const { message, line } of errorMessages) {
1300
1669
  const location = line !== null ? `${filename}:${line}` : filename;
@@ -1358,7 +1727,72 @@ function hoistMarkerDeclarations(ast, names) {
1358
1727
 
1359
1728
  // src/plugin/transform-css.ts
1360
1729
  import { parse as parse2 } from "@babel/parser";
1730
+ import * as t6 from "@babel/types";
1731
+
1732
+ // src/plugin/css-ts-utils.ts
1361
1733
  import * as t5 from "@babel/types";
1734
+ function collectStaticStringBindings(ast) {
1735
+ const bindings = /* @__PURE__ */ new Map();
1736
+ let changed = true;
1737
+ while (changed) {
1738
+ changed = false;
1739
+ for (const node of ast.program.body) {
1740
+ const declaration = getTopLevelVariableDeclaration(node);
1741
+ if (!declaration) continue;
1742
+ for (const declarator of declaration.declarations) {
1743
+ if (!t5.isIdentifier(declarator.id) || !declarator.init) continue;
1744
+ if (bindings.has(declarator.id.name)) continue;
1745
+ const value = resolveStaticString(declarator.init, bindings);
1746
+ if (value === null) continue;
1747
+ bindings.set(declarator.id.name, value);
1748
+ changed = true;
1749
+ }
1750
+ }
1751
+ }
1752
+ return bindings;
1753
+ }
1754
+ function resolveStaticString(node, bindings) {
1755
+ if (!node) return null;
1756
+ if (t5.isStringLiteral(node)) return node.value;
1757
+ if (t5.isTemplateLiteral(node)) {
1758
+ let value = "";
1759
+ for (let i = 0; i < node.quasis.length; i++) {
1760
+ value += node.quasis[i].value.cooked ?? "";
1761
+ if (i >= node.expressions.length) continue;
1762
+ const expressionValue = resolveStaticString(node.expressions[i], bindings);
1763
+ if (expressionValue === null) return null;
1764
+ value += expressionValue;
1765
+ }
1766
+ return value;
1767
+ }
1768
+ if (t5.isIdentifier(node)) {
1769
+ return bindings.get(node.name) ?? null;
1770
+ }
1771
+ if (t5.isTSAsExpression(node) || t5.isTSSatisfiesExpression(node) || t5.isTSNonNullExpression(node)) {
1772
+ return resolveStaticString(node.expression, bindings);
1773
+ }
1774
+ if (t5.isParenthesizedExpression(node)) {
1775
+ return resolveStaticString(node.expression, bindings);
1776
+ }
1777
+ if (t5.isBinaryExpression(node, { operator: "+" })) {
1778
+ const left = resolveStaticString(node.left, bindings);
1779
+ const right = resolveStaticString(node.right, bindings);
1780
+ if (left === null || right === null) return null;
1781
+ return left + right;
1782
+ }
1783
+ return null;
1784
+ }
1785
+ function getTopLevelVariableDeclaration(node) {
1786
+ if (t5.isVariableDeclaration(node)) {
1787
+ return node;
1788
+ }
1789
+ if (t5.isExportNamedDeclaration(node) && node.declaration && t5.isVariableDeclaration(node.declaration)) {
1790
+ return node.declaration;
1791
+ }
1792
+ return null;
1793
+ }
1794
+
1795
+ // src/plugin/transform-css.ts
1362
1796
  function transformCssTs(code, filename, mapping) {
1363
1797
  const ast = parse2(code, {
1364
1798
  sourceType: "module",
@@ -1370,28 +1804,29 @@ function transformCssTs(code, filename, mapping) {
1370
1804
  return `/* [truss] ${filename}: no Css import found */
1371
1805
  `;
1372
1806
  }
1373
- const defaultExport = findDefaultExportObject(ast);
1374
- if (!defaultExport) {
1375
- return `/* [truss] ${filename}: expected \`export default { ... }\` with an object literal */
1807
+ const cssExport = findNamedCssExportObject(ast);
1808
+ if (!cssExport) {
1809
+ return `/* [truss] ${filename}: expected \`export const css = { ... }\` with an object literal */
1376
1810
  `;
1377
1811
  }
1378
1812
  const rules = [];
1379
- for (const prop of defaultExport.properties) {
1380
- if (t5.isSpreadElement(prop)) {
1813
+ const stringBindings = collectStaticStringBindings(ast);
1814
+ for (const prop of cssExport.properties) {
1815
+ if (t6.isSpreadElement(prop)) {
1381
1816
  rules.push(`/* [truss] unsupported: spread elements in css.ts export */`);
1382
1817
  continue;
1383
1818
  }
1384
- if (!t5.isObjectProperty(prop)) {
1819
+ if (!t6.isObjectProperty(prop)) {
1385
1820
  rules.push(`/* [truss] unsupported: non-property in css.ts export */`);
1386
1821
  continue;
1387
1822
  }
1388
- const selector = objectPropertyStringKey(prop);
1823
+ const selector = objectPropertyStringKey(prop, stringBindings);
1389
1824
  if (selector === null) {
1390
1825
  rules.push(`/* [truss] unsupported: non-string-literal key in css.ts export */`);
1391
1826
  continue;
1392
1827
  }
1393
1828
  const valueNode = prop.value;
1394
- if (!t5.isExpression(valueNode)) {
1829
+ if (!t6.isExpression(valueNode)) {
1395
1830
  rules.push(`/* [truss] unsupported: "${selector}" value is not an expression */`);
1396
1831
  continue;
1397
1832
  }
@@ -1404,23 +1839,32 @@ function transformCssTs(code, filename, mapping) {
1404
1839
  }
1405
1840
  return rules.join("\n\n") + "\n";
1406
1841
  }
1407
- function findDefaultExportObject(ast) {
1842
+ function findNamedCssExportObject(ast) {
1408
1843
  for (const node of ast.program.body) {
1409
- if (!t5.isExportDefaultDeclaration(node)) continue;
1410
- const decl = node.declaration;
1411
- if (t5.isObjectExpression(decl)) return decl;
1412
- if (t5.isTSAsExpression(decl) && t5.isObjectExpression(decl.expression)) return decl.expression;
1413
- if (t5.isTSSatisfiesExpression(decl) && t5.isObjectExpression(decl.expression)) return decl.expression;
1844
+ if (!t6.isExportNamedDeclaration(node) || !node.declaration) continue;
1845
+ if (!t6.isVariableDeclaration(node.declaration)) continue;
1846
+ for (const declarator of node.declaration.declarations) {
1847
+ if (!t6.isIdentifier(declarator.id, { name: "css" })) continue;
1848
+ const value = unwrapObjectExpression(declarator.init);
1849
+ if (value) return value;
1850
+ }
1414
1851
  }
1415
1852
  return null;
1416
1853
  }
1417
- function objectPropertyStringKey(prop) {
1418
- if (t5.isStringLiteral(prop.key)) return prop.key.value;
1419
- if (t5.isIdentifier(prop.key) && !prop.computed) return prop.key.name;
1854
+ function unwrapObjectExpression(node) {
1855
+ if (!node) return null;
1856
+ if (t6.isObjectExpression(node)) return node;
1857
+ if (t6.isTSAsExpression(node) || t6.isTSSatisfiesExpression(node)) return unwrapObjectExpression(node.expression);
1858
+ return null;
1859
+ }
1860
+ function objectPropertyStringKey(prop, stringBindings) {
1861
+ if (t6.isStringLiteral(prop.key)) return prop.key.value;
1862
+ if (t6.isIdentifier(prop.key) && !prop.computed) return prop.key.name;
1863
+ if (prop.computed) return resolveStaticString(prop.key, stringBindings);
1420
1864
  return null;
1421
1865
  }
1422
1866
  function resolveCssExpression(node, cssBindingName, mapping, filename) {
1423
- if (!t5.isMemberExpression(node) || node.computed || !t5.isIdentifier(node.property, { name: "$" })) {
1867
+ if (!t6.isMemberExpression(node) || node.computed || !t6.isIdentifier(node.property, { name: "$" })) {
1424
1868
  return { error: "value must be a Css.*.$ expression" };
1425
1869
  }
1426
1870
  const chain = extractChain(node.object, cssBindingName);
@@ -1450,6 +1894,9 @@ function resolveCssExpression(node, cssBindingName, mapping, filename) {
1450
1894
  if (seg.dynamicProps && !seg.argResolved) {
1451
1895
  return { error: `dynamic value with variable argument is not supported in .css.ts files` };
1452
1896
  }
1897
+ if (seg.typographyLookup) {
1898
+ return { error: `typography() with a runtime key is not supported in .css.ts files` };
1899
+ }
1453
1900
  if (seg.mediaQuery) {
1454
1901
  return { error: `media query modifiers (ifSm, ifMd, etc.) are not supported in .css.ts files` };
1455
1902
  }
@@ -1486,8 +1933,61 @@ ${body}
1486
1933
  }`;
1487
1934
  }
1488
1935
 
1936
+ // src/plugin/rewrite-css-ts-imports.ts
1937
+ import { parse as parse3 } from "@babel/parser";
1938
+ import _generate2 from "@babel/generator";
1939
+ import * as t7 from "@babel/types";
1940
+ var generate2 = _generate2.default ?? _generate2;
1941
+ function rewriteCssTsImports(code, filename) {
1942
+ if (!code.includes(".css.ts")) {
1943
+ return { code, changed: false };
1944
+ }
1945
+ const ast = parse3(code, {
1946
+ sourceType: "module",
1947
+ plugins: ["typescript", "jsx"],
1948
+ sourceFilename: filename
1949
+ });
1950
+ const existingCssSideEffects = /* @__PURE__ */ new Set();
1951
+ const neededCssSideEffects = /* @__PURE__ */ new Set();
1952
+ let changed = false;
1953
+ for (const node of ast.program.body) {
1954
+ if (!t7.isImportDeclaration(node)) continue;
1955
+ if (typeof node.source.value !== "string") continue;
1956
+ if (!node.source.value.endsWith(".css.ts")) continue;
1957
+ if (node.specifiers.length === 0) {
1958
+ node.source = t7.stringLiteral(toVirtualCssSpecifier(node.source.value));
1959
+ existingCssSideEffects.add(node.source.value);
1960
+ changed = true;
1961
+ continue;
1962
+ }
1963
+ neededCssSideEffects.add(toVirtualCssSpecifier(node.source.value));
1964
+ }
1965
+ const sideEffectImports = [];
1966
+ for (const source of neededCssSideEffects) {
1967
+ if (existingCssSideEffects.has(source)) continue;
1968
+ sideEffectImports.push(t7.importDeclaration([], t7.stringLiteral(source)));
1969
+ changed = true;
1970
+ }
1971
+ if (!changed) {
1972
+ return { code, changed: false };
1973
+ }
1974
+ if (sideEffectImports.length > 0) {
1975
+ const insertIndex = findLastImportIndex(ast) + 1;
1976
+ ast.program.body.splice(insertIndex, 0, ...sideEffectImports);
1977
+ }
1978
+ const output = generate2(ast, {
1979
+ sourceFileName: filename,
1980
+ retainLines: false
1981
+ });
1982
+ return { code: output.code, changed: true };
1983
+ }
1984
+ function toVirtualCssSpecifier(source) {
1985
+ return `${source}?truss-css`;
1986
+ }
1987
+
1489
1988
  // src/plugin/index.ts
1490
1989
  var VIRTUAL_CSS_PREFIX = "\0truss-css:";
1990
+ var CSS_TS_QUERY = "?truss-css";
1491
1991
  function trussPlugin(opts) {
1492
1992
  let mapping = null;
1493
1993
  let projectRoot;
@@ -1511,15 +2011,8 @@ function trussPlugin(opts) {
1511
2011
  ensureMapping();
1512
2012
  },
1513
2013
  resolveId(source, importer) {
1514
- if (!source.endsWith(".css.ts")) return null;
1515
- let absolutePath;
1516
- if (isAbsolute(source)) {
1517
- absolutePath = source;
1518
- } else if (importer) {
1519
- absolutePath = resolve(dirname(importer), source);
1520
- } else {
1521
- absolutePath = resolve(projectRoot || process.cwd(), source);
1522
- }
2014
+ if (!source.endsWith(CSS_TS_QUERY)) return null;
2015
+ const absolutePath = resolveImportPath(source.slice(0, -CSS_TS_QUERY.length), importer, projectRoot);
1523
2016
  if (!existsSync(absolutePath)) return null;
1524
2017
  return VIRTUAL_CSS_PREFIX + absolutePath.slice(0, -3);
1525
2018
  },
@@ -1531,17 +2024,38 @@ function trussPlugin(opts) {
1531
2024
  },
1532
2025
  transform(code, id) {
1533
2026
  if (!/\.[cm]?[jt]sx?(\?|$)/.test(id)) return null;
1534
- if (!code.includes("Css")) return null;
2027
+ const rewrittenImports = rewriteCssTsImports(code, id);
2028
+ const rewrittenCode = rewrittenImports.code;
2029
+ const hasCssDsl = rewrittenCode.includes("Css");
2030
+ if (!hasCssDsl && !rewrittenImports.changed) return null;
1535
2031
  const fileId = stripQueryAndHash(id);
1536
2032
  if (isNodeModulesFile(fileId) && !isWhitelistedExternalPackageFile(fileId, externalPackages)) {
1537
2033
  return null;
1538
2034
  }
1539
- const result = transformTruss(code, id, ensureMapping());
1540
- if (!result) return null;
2035
+ if (fileId.endsWith(".css.ts")) {
2036
+ return rewrittenImports.changed ? { code: rewrittenCode, map: null } : null;
2037
+ }
2038
+ if (!hasCssDsl) {
2039
+ return { code: rewrittenCode, map: null };
2040
+ }
2041
+ const result = transformTruss(rewrittenCode, id, ensureMapping());
2042
+ if (!result) {
2043
+ if (!rewrittenImports.changed) return null;
2044
+ return { code: rewrittenCode, map: null };
2045
+ }
1541
2046
  return { code: result.code, map: result.map };
1542
2047
  }
1543
2048
  };
1544
2049
  }
2050
+ function resolveImportPath(source, importer, projectRoot) {
2051
+ if (isAbsolute(source)) {
2052
+ return source;
2053
+ }
2054
+ if (importer) {
2055
+ return resolve(dirname(importer), source);
2056
+ }
2057
+ return resolve(projectRoot || process.cwd(), source);
2058
+ }
1545
2059
  function stripQueryAndHash(id) {
1546
2060
  const queryIndex = id.indexOf("?");
1547
2061
  const hashIndex = id.indexOf("#");