@csszyx/compiler 0.3.1 → 0.4.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.
package/dist/index.cjs CHANGED
@@ -30,12 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BOOLEAN_SHORTHANDS: () => BOOLEAN_SHORTHANDS,
33
34
  COLOR_PROPERTIES: () => COLOR_PROPERTIES,
34
35
  CsszyxCompiler: () => CsszyxCompiler,
35
36
  DEFAULT_COMPILER_OPTIONS: () => DEFAULT_COMPILER_OPTIONS,
37
+ KNOWN_VARIANTS: () => KNOWN_VARIANTS,
36
38
  ManifestBuilder: () => ManifestBuilder,
37
39
  PROPERTY_CATEGORY_MAP: () => PROPERTY_CATEGORY_MAP,
40
+ PROPERTY_MAP: () => PROPERTY_MAP,
38
41
  PropertyCategory: () => PropertyCategory,
42
+ SUGGESTION_MAP: () => SUGGESTION_MAP,
39
43
  VERSION: () => VERSION,
40
44
  buildParentMap: () => buildParentMap,
41
45
  createRecoveryToken: () => createRecoveryToken,
@@ -230,7 +234,8 @@ var PROPERTY_CATEGORY_MAP = {
230
234
  skewY: 4 /* ANGLE */,
231
235
  // ---- DURATION ----
232
236
  duration: 5 /* DURATION */,
233
- delay: 5 /* DURATION */
237
+ delay: 5 /* DURATION */,
238
+ animationDelay: 5 /* DURATION */
234
239
  };
235
240
  function getPropertyCategory(property) {
236
241
  return PROPERTY_CATEGORY_MAP[property] ?? 7 /* PASSTHROUGH */;
@@ -274,6 +279,9 @@ function isValidColorString(value) {
274
279
  if (/^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*-\d+$/.test(value)) {
275
280
  return true;
276
281
  }
282
+ if (/^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z][a-zA-Z0-9]*)*$/.test(value)) {
283
+ return true;
284
+ }
277
285
  return false;
278
286
  }
279
287
  function hasSlashOpacity(value) {
@@ -376,11 +384,13 @@ var PROPERTY_MAP = {
376
384
  outlineColor: "outline",
377
385
  outlineOffset: "outline-offset",
378
386
  outlineStyle: "outline",
379
- // Ring (v3 keep for future v3 support)
387
+ // Ring (v4: outset ring + inset ring)
380
388
  ring: "ring",
381
389
  ringColor: "ring",
382
390
  ringOffset: "ring-offset",
383
391
  ringOffsetColor: "ring-offset",
392
+ insetRing: "inset-ring",
393
+ insetRingColor: "inset-ring",
384
394
  // Spacing (canonical shorthand only)
385
395
  p: "p",
386
396
  pt: "pt",
@@ -560,6 +570,7 @@ var PROPERTY_MAP = {
560
570
  rotateX: "rotate-x",
561
571
  rotateY: "rotate-y",
562
572
  rotateZ: "rotate-z",
573
+ translate: "translate",
563
574
  translateX: "translate-x",
564
575
  translateY: "translate-y",
565
576
  translateZ: "translate-z",
@@ -578,6 +589,8 @@ var PROPERTY_MAP = {
578
589
  ease: "ease",
579
590
  delay: "delay",
580
591
  animate: "animate",
592
+ animationDelay: "animation-delay",
593
+ // animation-delay — distinct from transition delay
581
594
  // Masks
582
595
  mask: "mask",
583
596
  maskSize: "mask-size",
@@ -984,13 +997,7 @@ var BOOLEAN_SHORTHANDS = /* @__PURE__ */ new Set([
984
997
  // Ring (v3 future)
985
998
  "ring",
986
999
  // Outline
987
- "outline",
988
- // Transforms
989
- "scale3d",
990
- "rotate3d",
991
- "translate3d",
992
- "transformGpu",
993
- "transformCpu"
1000
+ "outline"
994
1001
  ]);
995
1002
  var BOOLEAN_TO_CLASS = {
996
1003
  inlineBlock: "inline-block",
@@ -1023,9 +1030,6 @@ var BOOLEAN_TO_CLASS = {
1023
1030
  diagonalFractions: "diagonal-fractions",
1024
1031
  stackedFractions: "stacked-fractions",
1025
1032
  // Transforms
1026
- scale3d: "scale-3d",
1027
- rotate3d: "rotate-3d",
1028
- translate3d: "translate-3d",
1029
1033
  transformGpu: "transform-gpu",
1030
1034
  transformCpu: "transform-cpu",
1031
1035
  // Misc
@@ -1092,6 +1096,7 @@ var NEGATIVE_ALLOWED = /* @__PURE__ */ new Set([
1092
1096
  "scale-z",
1093
1097
  "skew-x",
1094
1098
  "skew-y",
1099
+ "translate",
1095
1100
  "translate-x",
1096
1101
  "translate-y",
1097
1102
  "translate-z",
@@ -1118,7 +1123,10 @@ function handleImportant(value) {
1118
1123
  }
1119
1124
  function formatOpacity(op) {
1120
1125
  if (typeof op === "number") {
1121
- return String(op);
1126
+ if (Number.isInteger(op * 2)) {
1127
+ return String(op);
1128
+ }
1129
+ return `[${op}]`;
1122
1130
  }
1123
1131
  if (typeof op === "string") {
1124
1132
  if (op.startsWith("--")) {
@@ -1135,7 +1143,8 @@ function normalizeArbitraryVariant(key) {
1135
1143
  return key.replace(/\s+/g, "");
1136
1144
  }
1137
1145
  function normalizeArbitraryValue(value) {
1138
- return value.trim().replace(/\s+/g, "_");
1146
+ const stripped = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
1147
+ return stripped.trim().replace(/\s+/g, "_");
1139
1148
  }
1140
1149
  var FRACTION_SUPPORTED_PROPS = /* @__PURE__ */ new Set([
1141
1150
  // Sizing (both rawKey and resolved key forms)
@@ -1173,6 +1182,7 @@ var FRACTION_SUPPORTED_PROPS = /* @__PURE__ */ new Set([
1173
1182
  "start",
1174
1183
  "end",
1175
1184
  // Translate
1185
+ "translate",
1176
1186
  "translate-x",
1177
1187
  "translateX",
1178
1188
  "translate-y",
@@ -1181,21 +1191,22 @@ var FRACTION_SUPPORTED_PROPS = /* @__PURE__ */ new Set([
1181
1191
  "aspect"
1182
1192
  ]);
1183
1193
  function needsArbitraryBrackets(value) {
1184
- return /^\d+(\.\d+)?(px|rem|em|%|vh|vw|ch|dvh|dvw|svh|svw|lvh|lvw|cqw|cqh|deg|rad|turn|grad|ms|s|fr)$/.test(value) || // Positive units
1185
- /^-\d+(\.\d+)?(px|rem|em|%|vh|vw|ch|dvh|dvw|svh|svw|lvh|lvw|cqw|cqh|deg|rad|turn|grad|ms|s|fr)$/.test(value) || // Negative units like -1px, -2rem
1186
- /^\.\d+(px|rem|em|%|vh|vw|ch)?$/.test(value) || // Values starting with . like .25em
1187
- /^-\.\d+(px|rem|em|%|vh|vw|ch)?$/.test(value) || // Negative values starting with -. like -.25em
1188
- value.startsWith("#") || // Hex colors
1189
- value.startsWith("rgb") || // RGB colors
1190
- value.startsWith("hsl") || // HSL colors
1191
- value.includes("calc(") || // Calculations
1192
- value.includes("var(") || // CSS variables (old syntax)
1193
- value.includes("attr(") || // attr() function
1194
- value.includes("url(") || // URLs
1195
- value.includes("clamp(") || // Clamp
1196
- value.includes("min(") || // Min
1197
- value.includes("max(") || // Max
1198
- value.includes(" ");
1194
+ const v = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
1195
+ return /^\d+(\.\d+)?(px|rem|em|%|vh|vw|ch|dvh|dvw|svh|svw|lvh|lvw|cqw|cqh|deg|rad|turn|grad|ms|s|fr)$/.test(v) || // Positive units
1196
+ /^-\d+(\.\d+)?(px|rem|em|%|vh|vw|ch|dvh|dvw|svh|svw|lvh|lvw|cqw|cqh|deg|rad|turn|grad|ms|s|fr)$/.test(v) || // Negative units like -1px, -2rem
1197
+ /^\.\d+(px|rem|em|%|vh|vw|ch)?$/.test(v) || // Values starting with . like .25em
1198
+ /^-\.\d+(px|rem|em|%|vh|vw|ch)?$/.test(v) || // Negative values starting with -. like -.25em
1199
+ v.startsWith("#") || // Hex colors
1200
+ v.startsWith("rgb") || // RGB colors
1201
+ v.startsWith("hsl") || // HSL colors
1202
+ v.includes("calc(") || // Calculations
1203
+ v.includes("var(") || // CSS variables (old syntax)
1204
+ v.includes("attr(") || // attr() function
1205
+ v.includes("url(") || // URLs
1206
+ v.includes("clamp(") || // Clamp
1207
+ v.includes("min(") || // Min
1208
+ v.includes("max(") || // Max
1209
+ v.includes(" ");
1199
1210
  }
1200
1211
  var LIST_STYLE_STANDARD = /* @__PURE__ */ new Set(["none", "disc", "decimal"]);
1201
1212
  var FONT_STRETCH_KEYWORDS = /* @__PURE__ */ new Set([
@@ -1209,11 +1220,12 @@ var FONT_STRETCH_KEYWORDS = /* @__PURE__ */ new Set([
1209
1220
  "extra-expanded",
1210
1221
  "ultra-expanded"
1211
1222
  ]);
1212
- var NEEDS_ARBITRARY_PROPERTY = /* @__PURE__ */ new Set([
1213
- "mask-type",
1214
- "mask-composite",
1215
- "mask-mode"
1216
- ]);
1223
+ function camelToKebab(prop) {
1224
+ if (prop.startsWith("--")) {
1225
+ return prop;
1226
+ }
1227
+ return prop.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1228
+ }
1217
1229
  function getVariantPrefix(key) {
1218
1230
  if (VARIANT_MAP[key]) {
1219
1231
  return VARIANT_MAP[key];
@@ -1239,6 +1251,32 @@ function handleGroupPeer(type, nestedObj, prefix) {
1239
1251
  }
1240
1252
  continue;
1241
1253
  }
1254
+ if (nestedKey === "data" && typeof nestedValue === "object") {
1255
+ for (const [attr, attrValue] of Object.entries(nestedValue)) {
1256
+ if (attrValue === null || attrValue === void 0 || attrValue === false) {
1257
+ continue;
1258
+ }
1259
+ const variantPrefix = `${prefix}${type}-data-[${attr}]:`;
1260
+ const result = transform(attrValue, variantPrefix);
1261
+ if (result.className) {
1262
+ classes.push(result.className);
1263
+ }
1264
+ }
1265
+ continue;
1266
+ }
1267
+ if (nestedKey === "aria" && typeof nestedValue === "object") {
1268
+ for (const [attr, attrValue] of Object.entries(nestedValue)) {
1269
+ if (attrValue === null || attrValue === void 0 || attrValue === false) {
1270
+ continue;
1271
+ }
1272
+ const variantPrefix = ARIA_STATES.has(attr) ? `${prefix}${type}-aria-${attr}:` : `${prefix}${type}-aria-[${attr}]:`;
1273
+ const result = transform(attrValue, variantPrefix);
1274
+ if (result.className) {
1275
+ classes.push(result.className);
1276
+ }
1277
+ }
1278
+ continue;
1279
+ }
1242
1280
  const isVariant = KNOWN_VARIANTS.has(nestedKey) || KNOWN_VARIANTS.has(getVariantPrefix(nestedKey));
1243
1281
  const isArbitrary = nestedKey.startsWith(".") || nestedKey.startsWith("#") || nestedKey.startsWith("[") || nestedKey.startsWith(":");
1244
1282
  if (isArbitrary) {
@@ -1259,6 +1297,33 @@ function handleGroupPeer(type, nestedObj, prefix) {
1259
1297
  if (stateValue === null || stateValue === void 0 || stateValue === false) {
1260
1298
  continue;
1261
1299
  }
1300
+ if (state === "data" && typeof stateValue === "object") {
1301
+ for (const [attr, attrValue] of Object.entries(stateValue)) {
1302
+ if (attrValue === null || attrValue === void 0 || attrValue === false) {
1303
+ continue;
1304
+ }
1305
+ const variantPrefix2 = `${prefix}${type}-data-[${attr}]/${nestedKey}:`;
1306
+ const result2 = transform(attrValue, variantPrefix2);
1307
+ if (result2.className) {
1308
+ classes.push(result2.className);
1309
+ }
1310
+ }
1311
+ continue;
1312
+ }
1313
+ if (state === "aria" && typeof stateValue === "object") {
1314
+ for (const [attr, attrValue] of Object.entries(stateValue)) {
1315
+ if (attrValue === null || attrValue === void 0 || attrValue === false) {
1316
+ continue;
1317
+ }
1318
+ const ariaSegment = ARIA_STATES.has(attr) ? `aria-${attr}` : `aria-[${attr}]`;
1319
+ const variantPrefix2 = `${prefix}${type}-${ariaSegment}/${nestedKey}:`;
1320
+ const result2 = transform(attrValue, variantPrefix2);
1321
+ if (result2.className) {
1322
+ classes.push(result2.className);
1323
+ }
1324
+ }
1325
+ continue;
1326
+ }
1262
1327
  const mappedState = getVariantPrefix(state);
1263
1328
  const variantPrefix = `${prefix}${type}-${mappedState}/${nestedKey}:`;
1264
1329
  const result = transform(stateValue, variantPrefix);
@@ -1380,6 +1445,18 @@ function transform(szProp, prefix = "", mangleMap) {
1380
1445
  if (value === null || value === void 0) {
1381
1446
  continue;
1382
1447
  }
1448
+ if (rawKey === "css") {
1449
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1450
+ for (const [cssProp, cssVal] of Object.entries(value)) {
1451
+ if (cssVal === null || cssVal === void 0) {
1452
+ continue;
1453
+ }
1454
+ const kebab = camelToKebab(cssProp);
1455
+ classes.push(`${prefix}[${kebab}:${normalizeArbitraryValue(String(cssVal))}]`);
1456
+ }
1457
+ }
1458
+ continue;
1459
+ }
1383
1460
  if (rawKey.startsWith("@") && typeof value === "string") {
1384
1461
  const mappedKey = VARIANT_MAP[rawKey] || rawKey;
1385
1462
  classes.push(`${prefix}${mappedKey}/${value}`);
@@ -1450,10 +1527,11 @@ function transform(szProp, prefix = "", mangleMap) {
1450
1527
  classes.push(`${prefix}${rawKey}/${value}`);
1451
1528
  continue;
1452
1529
  }
1453
- if (typeof value === "object" && value !== null && !Array.isArray(value) && "color" in value) {
1530
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && rawKey in PROPERTY_MAP && "color" in value) {
1454
1531
  const colorObj = value;
1455
1532
  const twPrefix = PROPERTY_MAP[rawKey] || rawKey.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1456
- const colorBase = String(colorObj.color);
1533
+ const rawColorBase = String(colorObj.color);
1534
+ const colorBase = rawColorBase.startsWith("--") ? `(${rawColorBase})` : needsArbitraryBrackets(rawColorBase) ? `[${normalizeArbitraryValue(rawColorBase)}]` : normalizeArbitraryValue(rawColorBase);
1457
1535
  if (colorObj.op !== void 0) {
1458
1536
  const opStr = formatOpacity(colorObj.op);
1459
1537
  classes.push(`${prefix}${twPrefix}-${colorBase}/${opStr}`);
@@ -1812,7 +1890,7 @@ function transform(szProp, prefix = "", mangleMap) {
1812
1890
  }
1813
1891
  if (rawKey === "decorationThickness" || rawKey === "textDecorationThickness") {
1814
1892
  if (needsArbitraryBrackets(value)) {
1815
- className += `decoration-[${value}]`;
1893
+ className += `decoration-[${normalizeArbitraryValue(value)}]`;
1816
1894
  } else if (value.startsWith("--")) {
1817
1895
  className += `decoration-(${value})`;
1818
1896
  } else {
@@ -1863,7 +1941,9 @@ function transform(szProp, prefix = "", mangleMap) {
1863
1941
  if (rawKey === "brightness" || rawKey === "contrast" || rawKey === "saturate" || rawKey === "scale" || rawKey === "backdropBrightness" || rawKey === "backdropContrast" || rawKey === "backdropSaturate") {
1864
1942
  const prop = rawKey.startsWith("backdrop") ? `backdrop-${rawKey.slice(8).toLowerCase()}` : rawKey;
1865
1943
  const sValue = String(value);
1866
- if (sValue.startsWith("--")) {
1944
+ if (sValue === "3d" && rawKey === "scale") {
1945
+ classes.push(`${prefix}scale-3d`);
1946
+ } else if (sValue.startsWith("--")) {
1867
1947
  classes.push(`${prop}-(${sValue})`);
1868
1948
  } else {
1869
1949
  classes.push(`${prop}-[${sValue}]`);
@@ -1903,38 +1983,53 @@ function transform(szProp, prefix = "", mangleMap) {
1903
1983
  if (rawKey === "bgImg") {
1904
1984
  const v = String(value).trim();
1905
1985
  if (v === "none") {
1906
- classes.push("bg-none");
1986
+ classes.push(`${prefix}bg-none`);
1907
1987
  continue;
1908
1988
  }
1909
1989
  const vNorm = v.startsWith("-") ? v.slice(1) : v;
1990
+ if (vNorm.startsWith("repeating-")) {
1991
+ classes.push(`${prefix}bg-[${normalizeArbitraryValue(v)}]`);
1992
+ continue;
1993
+ }
1910
1994
  if (vNorm.startsWith("linear-") || vNorm.startsWith("radial") || vNorm.startsWith("conic") || vNorm.startsWith("gradient-to-")) {
1911
1995
  const vMapped = vNorm.startsWith("gradient-to-") ? vNorm.replace("gradient-to-", "linear-to-") : vNorm;
1912
1996
  if (v.startsWith("-")) {
1913
- classes.push(`-bg-${vMapped}`);
1997
+ classes.push(`${prefix}-bg-${vMapped}`);
1914
1998
  } else {
1915
- classes.push(`bg-${vMapped}`);
1999
+ classes.push(`${prefix}bg-${vMapped}`);
1916
2000
  }
1917
2001
  continue;
1918
2002
  }
1919
2003
  if (v.startsWith("--")) {
1920
- classes.push(`bg-(image:${v})`);
2004
+ classes.push(`${prefix}bg-(image:${v})`);
1921
2005
  continue;
1922
2006
  }
1923
2007
  if (v.startsWith("url(")) {
1924
- classes.push(`bg-[${v}]`);
2008
+ classes.push(`${prefix}bg-[${v}]`);
1925
2009
  continue;
1926
2010
  }
1927
- classes.push(`bg-[url(${v})]`);
2011
+ classes.push(`${prefix}bg-[url(${v})]`);
1928
2012
  continue;
1929
2013
  }
1930
2014
  if (rawKey === "bgPos") {
1931
2015
  const sVal = String(value);
1932
2016
  if (sVal.startsWith("--")) {
1933
- classes.push(`bg-(${sVal})`);
2017
+ classes.push(`${prefix}bg-(${sVal})`);
1934
2018
  } else if (sVal.includes("_") || needsArbitraryBrackets(sVal)) {
1935
- classes.push(`bg-[${normalizeArbitraryValue(sVal)}]`);
2019
+ classes.push(`${prefix}bg-[${normalizeArbitraryValue(sVal)}]`);
2020
+ } else {
2021
+ classes.push(`${prefix}bg-${sVal}`);
2022
+ }
2023
+ continue;
2024
+ }
2025
+ if (rawKey === "bgSize") {
2026
+ const sVal = String(value);
2027
+ if (sVal === "auto" || sVal === "cover" || sVal === "contain") {
2028
+ classes.push(`${prefix}bg-${sVal}`);
2029
+ } else if (sVal.startsWith("--")) {
2030
+ classes.push(`${prefix}bg-size-(${sVal})`);
1936
2031
  } else {
1937
- classes.push(`bg-${sVal}`);
2032
+ classes.push(`${prefix}bg-size-[${normalizeArbitraryValue(sVal)}]`);
1938
2033
  }
1939
2034
  continue;
1940
2035
  }
@@ -1960,7 +2055,8 @@ function transform(szProp, prefix = "", mangleMap) {
1960
2055
  } else if (value === "no-repeat") {
1961
2056
  className += "bg-no-repeat";
1962
2057
  } else {
1963
- className += `bg-repeat-${value}`;
2058
+ const suffix = value.startsWith("repeat-") ? value.slice(7) : value;
2059
+ className += `bg-repeat-${suffix}`;
1964
2060
  }
1965
2061
  classes.push(className);
1966
2062
  continue;
@@ -2001,7 +2097,8 @@ function transform(szProp, prefix = "", mangleMap) {
2001
2097
  } else if (value.startsWith("--")) {
2002
2098
  className += `content-(${value})`;
2003
2099
  } else {
2004
- className += `content-[${value}]`;
2100
+ const inner = value.startsWith('"') && value.endsWith('"') && value.length >= 2 ? `'${value.slice(1, -1)}'` : value;
2101
+ className += `content-[${inner}]`;
2005
2102
  }
2006
2103
  classes.push(className);
2007
2104
  continue;
@@ -2054,16 +2151,12 @@ function transform(szProp, prefix = "", mangleMap) {
2054
2151
  const sVal = String(value);
2055
2152
  const prop = PROPERTY_MAP[rawKey] || rawKey;
2056
2153
  if (needsArbitraryBrackets(sVal) || sVal.includes("(") || sVal.includes("_") || sVal.includes("%")) {
2057
- classes.push(`${prop}-[${normalizeArbitraryValue(sVal)}]`);
2154
+ classes.push(`${className}${prop}-[${normalizeArbitraryValue(sVal)}]`);
2058
2155
  continue;
2059
2156
  }
2060
2157
  }
2061
2158
  if (rawKey === "transformStyle") {
2062
- if (value === "preserve-3d" || value === "3d") {
2063
- className += "transform-3d";
2064
- } else {
2065
- className += `transform-${value}`;
2066
- }
2159
+ className += `transform-${value}`;
2067
2160
  classes.push(className);
2068
2161
  continue;
2069
2162
  }
@@ -2074,7 +2167,7 @@ function transform(szProp, prefix = "", mangleMap) {
2074
2167
  } else if (value.startsWith("--")) {
2075
2168
  className += `perspective-(${value})`;
2076
2169
  } else if (needsArbitraryBrackets(value)) {
2077
- className += `perspective-[${value}]`;
2170
+ className += `perspective-[${normalizeArbitraryValue(value)}]`;
2078
2171
  } else {
2079
2172
  className += `perspective-${value}`;
2080
2173
  }
@@ -2098,7 +2191,7 @@ function transform(szProp, prefix = "", mangleMap) {
2098
2191
  }
2099
2192
  }
2100
2193
  if (process.env.NODE_ENV !== "production" && typeof window === "undefined") {
2101
- const isKnown = PROPERTY_MAP[rawKey] || BOOLEAN_SHORTHANDS.has(rawKey) || SNAP_DIRECT_MAP[rawKey] || NEEDS_ARBITRARY_PROPERTY.has(key) || rawKey === "fromPos" || rawKey === "viaPos" || rawKey === "toPos" || rawKey.startsWith("--") || rawKey.startsWith("[") || rawKey.startsWith("@") || // Variants that fell through (e.g. empty object)
2194
+ const isKnown = PROPERTY_MAP[rawKey] || BOOLEAN_SHORTHANDS.has(rawKey) || SNAP_DIRECT_MAP[rawKey] || rawKey === "fromPos" || rawKey === "viaPos" || rawKey === "toPos" || rawKey.startsWith("--") || rawKey.startsWith("[") || rawKey.startsWith("@") || // Variants that fell through (e.g. empty object)
2102
2195
  KNOWN_VARIANTS.has(rawKey) || // Groups/Peers
2103
2196
  rawKey === "group" || rawKey === "peer" || // Special variant objects
2104
2197
  rawKey === "has" || rawKey === "not" || rawKey === "data" || rawKey === "aria" || rawKey === "supports" || rawKey === "min" || rawKey === "max";
@@ -2125,6 +2218,11 @@ function transform(szProp, prefix = "", mangleMap) {
2125
2218
  classes.push(className);
2126
2219
  continue;
2127
2220
  }
2221
+ if (rawKey === "animationDelay") {
2222
+ const ms = typeof value === "number" ? `${value}ms` : String(value);
2223
+ classes.push(`${className}[animation-delay:${ms}]`);
2224
+ continue;
2225
+ }
2128
2226
  if (typeof value === "number") {
2129
2227
  if (value < 0 && NEGATIVE_ALLOWED.has(key)) {
2130
2228
  className += `-${key}-${Math.abs(value)}`;
@@ -2137,15 +2235,6 @@ function transform(szProp, prefix = "", mangleMap) {
2137
2235
  if (typeof value === "string") {
2138
2236
  const { value: cleanValue, important } = handleImportant(value);
2139
2237
  let finalValue = cleanValue;
2140
- if (NEEDS_ARBITRARY_PROPERTY.has(key)) {
2141
- const arbitraryProp = `[${key}:${finalValue}]`;
2142
- className += arbitraryProp;
2143
- if (important) {
2144
- className += "!";
2145
- }
2146
- classes.push(className);
2147
- continue;
2148
- }
2149
2238
  if (finalValue.startsWith("--")) {
2150
2239
  const typeHint = CSS_VAR_TYPE_HINTS[rawKey];
2151
2240
  if (typeHint) {
@@ -2234,11 +2323,14 @@ function normalizeClassName(className) {
2234
2323
  // src/transform.ts
2235
2324
  function transformSourceCode(source) {
2236
2325
  let usesRuntime = false;
2326
+ let usesMerge = false;
2237
2327
  let usesColorVar = false;
2238
2328
  let transformed = false;
2239
2329
  const collectedClasses = /* @__PURE__ */ new Set();
2330
+ const rawClassNames = /* @__PURE__ */ new Set();
2331
+ const diagnostics = [];
2240
2332
  if (!source.includes("sz")) {
2241
- return { code: source, transformed: false, usesRuntime: false, usesColorVar: false, classes: collectedClasses };
2333
+ return { code: source, transformed: false, usesRuntime: false, usesMerge: false, usesColorVar: false, classes: collectedClasses, rawClassNames, diagnostics };
2242
2334
  }
2243
2335
  try {
2244
2336
  const result = babel.transformSync(source, {
@@ -2262,7 +2354,7 @@ function transformSourceCode(source) {
2262
2354
  if (t.isStringLiteral(val)) {
2263
2355
  for (const c of val.value.split(/\s+/)) {
2264
2356
  if (c) {
2265
- collectedClasses.add(c);
2357
+ rawClassNames.add(c);
2266
2358
  }
2267
2359
  }
2268
2360
  }
@@ -2272,6 +2364,99 @@ function transformSourceCode(source) {
2272
2364
  return;
2273
2365
  }
2274
2366
  const value = path.node.value;
2367
+ let existingClassNameNode = null;
2368
+ let existingClassExpr = null;
2369
+ let existingStyleNode = null;
2370
+ let existingStyleExpr = null;
2371
+ if (path.parentPath?.isJSXOpeningElement()) {
2372
+ for (const attr of path.parentPath.node.attributes) {
2373
+ if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
2374
+ const aName = attr.name;
2375
+ if (aName.name === "className" || aName.name === "class") {
2376
+ existingClassNameNode = attr;
2377
+ const aVal = attr.value;
2378
+ if (t.isStringLiteral(aVal)) {
2379
+ existingClassExpr = aVal;
2380
+ } else if (t.isJSXExpressionContainer(aVal)) {
2381
+ if (t.isExpression(aVal.expression)) {
2382
+ existingClassExpr = aVal.expression;
2383
+ }
2384
+ }
2385
+ } else if (aName.name === "style") {
2386
+ existingStyleNode = attr;
2387
+ const aVal = attr.value;
2388
+ if (t.isJSXExpressionContainer(aVal)) {
2389
+ if (t.isExpression(aVal.expression)) {
2390
+ existingStyleExpr = aVal.expression;
2391
+ }
2392
+ } else if (t.isStringLiteral(aVal)) {
2393
+ existingStyleExpr = aVal;
2394
+ }
2395
+ }
2396
+ }
2397
+ }
2398
+ }
2399
+ const createMergedClassNameValue = (szExpr) => {
2400
+ if (!existingClassExpr) {
2401
+ return t.isStringLiteral(szExpr) ? szExpr : t.jsxExpressionContainer(szExpr);
2402
+ }
2403
+ if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
2404
+ path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
2405
+ (a) => a !== existingClassNameNode
2406
+ );
2407
+ existingClassNameNode = null;
2408
+ }
2409
+ if (t.isStringLiteral(existingClassExpr) && t.isStringLiteral(szExpr)) {
2410
+ const merged = `${existingClassExpr.value} ${szExpr.value}`.trim();
2411
+ return t.stringLiteral(merged);
2412
+ }
2413
+ usesRuntime = true;
2414
+ usesMerge = true;
2415
+ return t.jsxExpressionContainer(
2416
+ t.callExpression(t.identifier("_szMerge"), [existingClassExpr, szExpr])
2417
+ );
2418
+ };
2419
+ const mergeAndInjectStyle = (newStyleProps) => {
2420
+ if (newStyleProps.length === 0) {
2421
+ return;
2422
+ }
2423
+ if (!path.parentPath?.isJSXOpeningElement()) {
2424
+ return;
2425
+ }
2426
+ if (existingStyleNode && existingStyleExpr) {
2427
+ path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
2428
+ (a) => a !== existingStyleNode
2429
+ );
2430
+ existingStyleNode = null;
2431
+ if (t.isObjectExpression(existingStyleExpr)) {
2432
+ existingStyleExpr.properties.push(...newStyleProps);
2433
+ path.parentPath.node.attributes.push(
2434
+ t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(existingStyleExpr))
2435
+ );
2436
+ } else if (t.isStringLiteral(existingStyleExpr)) {
2437
+ const parsedOldProps = parseStyleStringToObjectExpr(existingStyleExpr.value).properties;
2438
+ path.parentPath.node.attributes.push(
2439
+ t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(
2440
+ t.objectExpression([...parsedOldProps, ...newStyleProps])
2441
+ ))
2442
+ );
2443
+ } else {
2444
+ const mergedStyle = t.objectExpression([
2445
+ t.spreadElement(existingStyleExpr),
2446
+ ...newStyleProps
2447
+ ]);
2448
+ path.parentPath.node.attributes.push(
2449
+ t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(mergedStyle))
2450
+ );
2451
+ }
2452
+ } else {
2453
+ path.parentPath.node.attributes.push(
2454
+ t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(t.objectExpression(newStyleProps)))
2455
+ );
2456
+ existingStyleExpr = t.objectExpression(newStyleProps);
2457
+ existingStyleNode = path.parentPath.node.attributes[path.parentPath.node.attributes.length - 1];
2458
+ }
2459
+ };
2275
2460
  if (t.isStringLiteral(value)) {
2276
2461
  path.node.name.name = "className";
2277
2462
  for (const c of value.value.split(/\s+/)) {
@@ -2279,13 +2464,16 @@ function transformSourceCode(source) {
2279
2464
  collectedClasses.add(c);
2280
2465
  }
2281
2466
  }
2467
+ path.node.value = createMergedClassNameValue(value);
2282
2468
  transformed = true;
2283
2469
  return;
2284
2470
  }
2285
2471
  if (t.isJSXExpressionContainer(value)) {
2286
2472
  const expression = value.expression;
2287
2473
  if (t.isObjectExpression(expression)) {
2288
- const staticObject = evaluateStaticObject(expression);
2474
+ const getBinding = (name) => path.scope.getBinding(name);
2475
+ const flatExpression = resolveObjectSpreads(expression, getBinding) ?? expression;
2476
+ const staticObject = evaluateStaticObject(flatExpression);
2289
2477
  if (staticObject !== null) {
2290
2478
  const { className, attributes } = transform(staticObject);
2291
2479
  for (const c of className.split(/\s+/)) {
@@ -2294,22 +2482,35 @@ function transformSourceCode(source) {
2294
2482
  }
2295
2483
  }
2296
2484
  path.node.name.name = "className";
2297
- path.node.value = t.stringLiteral(className);
2485
+ path.node.value = createMergedClassNameValue(t.stringLiteral(className));
2298
2486
  Object.entries(attributes).forEach(([key, val]) => {
2299
2487
  if (path.parentPath?.isJSXOpeningElement()) {
2300
- path.parentPath.node.attributes.push(
2301
- t.jsxAttribute(
2302
- t.jsxIdentifier(key),
2303
- t.stringLiteral(val)
2304
- )
2305
- );
2488
+ if (key === "style") {
2489
+ const newProps = parseStyleStringToObjectExpr(val).properties;
2490
+ mergeAndInjectStyle(newProps);
2491
+ } else {
2492
+ path.parentPath.node.attributes.push(
2493
+ t.jsxAttribute(
2494
+ t.jsxIdentifier(key),
2495
+ t.stringLiteral(val)
2496
+ )
2497
+ );
2498
+ }
2306
2499
  }
2307
2500
  });
2308
2501
  transformed = true;
2309
2502
  return;
2310
2503
  }
2311
- const partial = evaluatePartialObject(expression);
2312
- if (partial !== null && !partial.hasSpread && partial.dynamicProps.size > 0) {
2504
+ const hoisted = tryHoistConditionalSpread(expression, getBinding);
2505
+ if (hoisted !== null) {
2506
+ path.node.name.name = "className";
2507
+ path.node.value = createMergedClassNameValue(hoisted);
2508
+ collectFromExpr(hoisted, collectedClasses);
2509
+ transformed = true;
2510
+ return;
2511
+ }
2512
+ const partial = evaluatePartialObject(flatExpression);
2513
+ if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
2313
2514
  const staticClasses = [];
2314
2515
  if (Object.keys(partial.staticProps).length > 0) {
2315
2516
  const { className: sc } = transform(partial.staticProps);
@@ -2330,23 +2531,28 @@ function transformSourceCode(source) {
2330
2531
  )
2331
2532
  );
2332
2533
  }
2333
- const allClasses = [...staticClasses, ...partial.rawClasses, ...cssVarClasses].join(" ");
2334
- for (const c of allClasses.split(/\s+/)) {
2534
+ const baseClasses = [...staticClasses, ...partial.rawClasses, ...cssVarClasses].join(" ");
2535
+ for (const c of baseClasses.split(/\s+/)) {
2335
2536
  if (c) {
2336
2537
  collectedClasses.add(c);
2337
2538
  }
2338
2539
  }
2339
- path.node.name.name = "className";
2340
- path.node.value = t.stringLiteral(allClasses);
2341
- if (styleProps.length > 0 && path.parentPath?.isJSXOpeningElement()) {
2342
- const styleAttr = t.jsxAttribute(
2343
- t.jsxIdentifier("style"),
2344
- t.jsxExpressionContainer(
2345
- t.objectExpression(styleProps)
2346
- )
2347
- );
2348
- path.parentPath.node.attributes.push(styleAttr);
2540
+ for (const cc of partial.conditionalClasses) {
2541
+ for (const c of cc.consequent.split(/\s+/)) {
2542
+ if (c) {
2543
+ collectedClasses.add(c);
2544
+ }
2545
+ }
2546
+ for (const c of cc.alternate.split(/\s+/)) {
2547
+ if (c) {
2548
+ collectedClasses.add(c);
2549
+ }
2550
+ }
2349
2551
  }
2552
+ const classExpr = partial.conditionalClasses.length > 0 ? buildConditionalClassExpr(baseClasses, partial.conditionalClasses) : t.stringLiteral(baseClasses);
2553
+ path.node.name.name = "className";
2554
+ path.node.value = createMergedClassNameValue(classExpr);
2555
+ mergeAndInjectStyle(styleProps);
2350
2556
  if (partial.usesColorVar) {
2351
2557
  usesColorVar = true;
2352
2558
  }
@@ -2359,18 +2565,19 @@ function transformSourceCode(source) {
2359
2565
  if (binding && binding.path.isVariableDeclarator()) {
2360
2566
  const init2 = binding.path.node.init;
2361
2567
  if (init2) {
2362
- const resolved = tryStaticTransformNode(init2);
2568
+ const gbIdent = (name) => path.scope.getBinding(name);
2569
+ const resolved = tryStaticTransformNode(init2, gbIdent);
2363
2570
  if (resolved !== null) {
2364
2571
  path.node.name.name = "className";
2365
2572
  if (t.isStringLiteral(resolved)) {
2366
- path.node.value = resolved;
2573
+ path.node.value = createMergedClassNameValue(resolved);
2367
2574
  for (const c of resolved.value.split(/\s+/)) {
2368
2575
  if (c) {
2369
2576
  collectedClasses.add(c);
2370
2577
  }
2371
2578
  }
2372
2579
  } else {
2373
- value.expression = resolved;
2580
+ path.node.value = createMergedClassNameValue(resolved);
2374
2581
  collectFromExpr(resolved, collectedClasses);
2375
2582
  }
2376
2583
  transformed = true;
@@ -2380,33 +2587,267 @@ function transformSourceCode(source) {
2380
2587
  }
2381
2588
  }
2382
2589
  if (t.isConditionalExpression(expression)) {
2383
- const resolved = tryStaticTransformNode(expression);
2590
+ const gbCond = (name) => path.scope.getBinding(name);
2591
+ const resolved = tryStaticTransformNode(expression, gbCond);
2384
2592
  if (resolved !== null) {
2385
2593
  path.node.name.name = "className";
2386
2594
  if (t.isStringLiteral(resolved)) {
2387
- path.node.value = resolved;
2595
+ path.node.value = createMergedClassNameValue(resolved);
2388
2596
  for (const c of resolved.value.split(/\s+/)) {
2389
2597
  if (c) {
2390
2598
  collectedClasses.add(c);
2391
2599
  }
2392
2600
  }
2393
2601
  } else {
2394
- value.expression = resolved;
2602
+ path.node.value = createMergedClassNameValue(resolved);
2395
2603
  collectFromExpr(resolved, collectedClasses);
2396
2604
  }
2397
2605
  transformed = true;
2398
2606
  return;
2399
2607
  }
2400
2608
  }
2609
+ if (t.isArrayExpression(expression)) {
2610
+ const parts = [];
2611
+ let hasRuntime = false;
2612
+ const getBindingForArray = (name) => path.scope.getBinding(name);
2613
+ for (const element of expression.elements) {
2614
+ if (element === null) {
2615
+ continue;
2616
+ }
2617
+ if (t.isBooleanLiteral(element) && !element.value) {
2618
+ continue;
2619
+ }
2620
+ if (t.isNullLiteral(element)) {
2621
+ continue;
2622
+ }
2623
+ if (t.isIdentifier(element) && element.name === "undefined") {
2624
+ continue;
2625
+ }
2626
+ if (t.isLogicalExpression(element) && element.operator === "&&") {
2627
+ const resolved2 = tryStaticTransformNode(element.right, getBindingForArray);
2628
+ if (resolved2 !== null && t.isStringLiteral(resolved2)) {
2629
+ if (resolved2.value) {
2630
+ parts.push(t.logicalExpression("&&", element.left, resolved2));
2631
+ for (const c of resolved2.value.split(/\s+/)) {
2632
+ if (c) {
2633
+ collectedClasses.add(c);
2634
+ }
2635
+ }
2636
+ hasRuntime = true;
2637
+ }
2638
+ continue;
2639
+ }
2640
+ parts.push(element);
2641
+ hasRuntime = true;
2642
+ continue;
2643
+ }
2644
+ const resolved = tryStaticTransformNode(element, getBindingForArray);
2645
+ if (resolved !== null) {
2646
+ if (t.isStringLiteral(resolved)) {
2647
+ if (resolved.value) {
2648
+ parts.push(resolved);
2649
+ for (const c of resolved.value.split(/\s+/)) {
2650
+ if (c) {
2651
+ collectedClasses.add(c);
2652
+ }
2653
+ }
2654
+ }
2655
+ } else {
2656
+ parts.push(resolved);
2657
+ collectFromExpr(resolved, collectedClasses);
2658
+ hasRuntime = true;
2659
+ }
2660
+ } else {
2661
+ parts.push(element);
2662
+ hasRuntime = true;
2663
+ }
2664
+ }
2665
+ path.node.name.name = "className";
2666
+ if (parts.length === 0) {
2667
+ path.node.value = createMergedClassNameValue(t.stringLiteral(""));
2668
+ } else if (!hasRuntime) {
2669
+ const merged = parts.map((p) => p.value).filter(Boolean).join(" ");
2670
+ path.node.value = createMergedClassNameValue(t.stringLiteral(merged));
2671
+ } else {
2672
+ if (existingClassExpr) {
2673
+ parts.unshift(existingClassExpr);
2674
+ if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
2675
+ path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
2676
+ (a) => a !== existingClassNameNode
2677
+ );
2678
+ existingClassNameNode = null;
2679
+ }
2680
+ }
2681
+ const szCall2 = t.callExpression(
2682
+ t.identifier("_szMerge"),
2683
+ parts
2684
+ );
2685
+ path.node.value = t.jsxExpressionContainer(szCall2);
2686
+ usesMerge = true;
2687
+ usesRuntime = true;
2688
+ }
2689
+ transformed = true;
2690
+ return;
2691
+ }
2692
+ const loc = expression.loc;
2693
+ const lineCol = loc ? `${loc.start.line}:${loc.start.column + 1}` : "?";
2694
+ let reason, suggestion;
2695
+ if (t.isCallExpression(expression)) {
2696
+ const callee = expression.callee;
2697
+ const name = t.isIdentifier(callee) ? callee.name : t.isMemberExpression(callee) && t.isIdentifier(callee.property) ? callee.property.name : "?";
2698
+ reason = `function call \`${name}()\` result is unknown at build time`;
2699
+ suggestion = "If it returns static variants \u2192 convert to szv(). If it depends on runtime data \u2192 use dynamic().";
2700
+ } else if (t.isIdentifier(expression)) {
2701
+ reason = `identifier \`${expression.name}\` could not be resolved to a static value`;
2702
+ suggestion = "Make sure it's a module-level or function-body const with a literal object value. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
2703
+ } else if (t.isMemberExpression(expression)) {
2704
+ reason = "member expression is not statically resolvable";
2705
+ suggestion = "Extract the value to a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
2706
+ } else {
2707
+ reason = `expression of type \`${expression.type}\` is not statically analyzable`;
2708
+ suggestion = "Use a literal sz object or a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
2709
+ }
2710
+ diagnostics.push(`sz fallback at ${lineCol}: ${reason}.
2711
+ Suggestion: ${suggestion}`);
2401
2712
  path.node.name.name = "className";
2402
2713
  const szCall = t.callExpression(
2403
2714
  t.identifier("_sz"),
2404
2715
  [expression]
2405
2716
  );
2406
- value.expression = szCall;
2717
+ path.node.value = createMergedClassNameValue(szCall);
2407
2718
  usesRuntime = true;
2408
2719
  transformed = true;
2409
2720
  }
2721
+ },
2722
+ // ── szv catalog extraction ────────────────────────────────────────
2723
+ // When the compiler sees `const X = szv({...})` with a static config,
2724
+ // it emits a no-op catalog array so Tailwind JIT can scan all variant
2725
+ // class strings — even when szv is called at runtime with dynamic args.
2726
+ VariableDeclarator(path) {
2727
+ const init2 = path.node.init;
2728
+ if (!t.isCallExpression(init2)) {
2729
+ return;
2730
+ }
2731
+ if (!t.isIdentifier(init2.callee) || init2.callee.name !== "szv") {
2732
+ return;
2733
+ }
2734
+ if (init2.arguments.length === 0) {
2735
+ return;
2736
+ }
2737
+ if (!t.isIdentifier(path.node.id)) {
2738
+ return;
2739
+ }
2740
+ const configArg = init2.arguments[0];
2741
+ if (!t.isObjectExpression(configArg)) {
2742
+ return;
2743
+ }
2744
+ const config = evaluateStaticObject(configArg);
2745
+ if (!config) {
2746
+ return;
2747
+ }
2748
+ const base = config.base ?? {};
2749
+ const variants = config.variants ?? {};
2750
+ const classStrings = [];
2751
+ const baseResult = transform(base);
2752
+ const baseCls = typeof baseResult === "string" ? baseResult : baseResult.className;
2753
+ if (baseCls) {
2754
+ classStrings.push(baseCls);
2755
+ }
2756
+ for (const variantValues of Object.values(variants)) {
2757
+ for (const variantObj of Object.values(variantValues)) {
2758
+ if (!variantObj || typeof variantObj !== "object") {
2759
+ continue;
2760
+ }
2761
+ const merged = { ...base, ...variantObj };
2762
+ const result2 = transform(merged);
2763
+ const cls = typeof result2 === "string" ? result2 : result2.className;
2764
+ if (cls) {
2765
+ classStrings.push(cls);
2766
+ }
2767
+ }
2768
+ }
2769
+ if (classStrings.length === 0) {
2770
+ return;
2771
+ }
2772
+ for (const combined of classStrings) {
2773
+ for (const c of combined.split(/\s+/)) {
2774
+ if (c) {
2775
+ collectedClasses.add(c);
2776
+ }
2777
+ }
2778
+ }
2779
+ const catalogDecl = t.variableDeclaration("const", [
2780
+ t.variableDeclarator(
2781
+ t.identifier(`_szv_catalog_${path.node.id.name}`),
2782
+ t.arrayExpression(classStrings.map((s) => t.stringLiteral(s)))
2783
+ )
2784
+ ]);
2785
+ const parentPath = path.parentPath;
2786
+ if (parentPath && t.isVariableDeclaration(parentPath.node)) {
2787
+ parentPath.insertAfter(catalogDecl);
2788
+ transformed = true;
2789
+ }
2790
+ },
2791
+ // ── dynamic() literal extraction ──────────────────────────────────
2792
+ // Detects `dynamic({...})` and `dynamic(CONST_IDENTIFIER)` calls
2793
+ // with statically-analyzable arguments and adds the resulting
2794
+ // class tokens to collectedClasses so prescanAndWriteClasses()
2795
+ // includes them in csszyx-classes.html for Tailwind to scan.
2796
+ // This means dynamic() with static/const args works in Astro SSR
2797
+ // without needing client:* directives.
2798
+ CallExpression(path) {
2799
+ const callee = path.node.callee;
2800
+ if (!t.isIdentifier(callee) || callee.name !== "dynamic") {
2801
+ return;
2802
+ }
2803
+ if (path.node.arguments.length === 0) {
2804
+ return;
2805
+ }
2806
+ const arg = path.node.arguments[0];
2807
+ if (t.isObjectExpression(arg)) {
2808
+ const staticObj = evaluateStaticObject(arg);
2809
+ if (!staticObj) {
2810
+ return;
2811
+ }
2812
+ const { className } = transform(staticObj);
2813
+ for (const c of className.split(/\s+/)) {
2814
+ if (c) {
2815
+ collectedClasses.add(c);
2816
+ }
2817
+ }
2818
+ return;
2819
+ }
2820
+ let argExpr = arg;
2821
+ while (t.isTSAsExpression(argExpr) || t.isTSSatisfiesExpression(argExpr)) {
2822
+ argExpr = argExpr.expression;
2823
+ }
2824
+ if (t.isIdentifier(argExpr)) {
2825
+ const binding = path.scope.getBinding(argExpr.name);
2826
+ if (!binding) {
2827
+ return;
2828
+ }
2829
+ const declarator = binding.path.node;
2830
+ if (!t.isVariableDeclarator(declarator) || !declarator.init) {
2831
+ return;
2832
+ }
2833
+ let initExpr = declarator.init;
2834
+ while (t.isTSAsExpression(initExpr) || t.isTSSatisfiesExpression(initExpr)) {
2835
+ initExpr = initExpr.expression;
2836
+ }
2837
+ if (!t.isObjectExpression(initExpr)) {
2838
+ return;
2839
+ }
2840
+ const staticObj = evaluateStaticObject(initExpr);
2841
+ if (!staticObj) {
2842
+ return;
2843
+ }
2844
+ const { className } = transform(staticObj);
2845
+ for (const c of className.split(/\s+/)) {
2846
+ if (c) {
2847
+ collectedClasses.add(c);
2848
+ }
2849
+ }
2850
+ }
2410
2851
  }
2411
2852
  }
2412
2853
  };
@@ -2417,29 +2858,72 @@ function transformSourceCode(source) {
2417
2858
  code: result?.code || source,
2418
2859
  transformed,
2419
2860
  usesRuntime,
2861
+ usesMerge,
2420
2862
  usesColorVar,
2421
- classes: collectedClasses
2863
+ classes: collectedClasses,
2864
+ rawClassNames,
2865
+ diagnostics
2422
2866
  };
2423
2867
  } catch (e) {
2424
2868
  console.warn("[csszyx] AST transform failed, falling back to original code:", e);
2425
- return { code: source, transformed: false, usesRuntime: false, usesColorVar: false, classes: collectedClasses };
2869
+ return { code: source, transformed: false, usesRuntime: false, usesMerge: false, usesColorVar: false, classes: collectedClasses, rawClassNames, diagnostics };
2426
2870
  }
2427
2871
  }
2428
- function tryStaticTransformNode(node) {
2872
+ function parseStyleStringToObjectExpr(styleStr) {
2873
+ const props = styleStr.split(";").map((s) => s.trim()).filter(Boolean);
2874
+ const objProps = [];
2875
+ for (const prop of props) {
2876
+ const idx = prop.indexOf(":");
2877
+ if (idx > -1) {
2878
+ const k = prop.slice(0, idx).trim();
2879
+ const v = prop.slice(idx + 1).trim();
2880
+ let keyNode;
2881
+ if (k.startsWith("--")) {
2882
+ keyNode = t.stringLiteral(k);
2883
+ } else {
2884
+ const camel = k.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
2885
+ keyNode = t.identifier(camel);
2886
+ }
2887
+ objProps.push(t.objectProperty(keyNode, t.stringLiteral(v)));
2888
+ }
2889
+ }
2890
+ return t.objectExpression(objProps);
2891
+ }
2892
+ function tryStaticTransformNode(node, getBinding) {
2893
+ if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {
2894
+ return tryStaticTransformNode(node.expression, getBinding);
2895
+ }
2429
2896
  if (t.isObjectExpression(node)) {
2430
- const staticObj = evaluateStaticObject(node);
2897
+ const resolved = getBinding ? resolveObjectSpreads(node, getBinding) ?? node : node;
2898
+ const staticObj = evaluateStaticObject(resolved);
2431
2899
  if (staticObj !== null) {
2432
2900
  const { className } = transform(staticObj);
2433
2901
  return t.stringLiteral(className);
2434
2902
  }
2903
+ if (getBinding) {
2904
+ const hoisted = tryHoistConditionalSpread(node, getBinding);
2905
+ if (hoisted !== null) {
2906
+ return hoisted;
2907
+ }
2908
+ }
2435
2909
  return null;
2436
2910
  }
2437
2911
  if (t.isStringLiteral(node)) {
2438
2912
  return node;
2439
2913
  }
2914
+ if (t.isIdentifier(node) && getBinding) {
2915
+ const binding = getBinding(node.name);
2916
+ if (binding && binding.path.isVariableDeclarator()) {
2917
+ const init2 = binding.path.node.init;
2918
+ if (init2) {
2919
+ return tryStaticTransformNode(init2, getBinding);
2920
+ }
2921
+ }
2922
+ return null;
2923
+ }
2440
2924
  if (t.isConditionalExpression(node)) {
2441
- const consequent = tryStaticTransformNode(node.consequent);
2442
- const alternate = tryStaticTransformNode(node.alternate);
2925
+ const consequent = tryStaticTransformNode(node.consequent, getBinding);
2926
+ const alternate = tryStaticTransformNode(node.alternate, getBinding);
2443
2927
  if (consequent !== null && alternate !== null) {
2444
2928
  return t.conditionalExpression(node.test, consequent, alternate);
2445
2929
  }
@@ -2447,6 +2931,39 @@ function tryStaticTransformNode(node) {
2447
2931
  }
2448
2932
  return null;
2449
2933
  }
2934
+ function tryHoistConditionalSpread(node, getBinding) {
2935
+ let conditionalSpreadIdx = -1;
2936
+ let conditionalExpr = null;
2937
+ for (let i = 0; i < node.properties.length; i++) {
2938
+ const prop = node.properties[i];
2939
+ if (!t.isSpreadElement(prop)) {
2940
+ continue;
2941
+ }
2942
+ if (t.isConditionalExpression(prop.argument)) {
2943
+ if (conditionalSpreadIdx !== -1) {
2944
+ return null;
2945
+ }
2946
+ conditionalSpreadIdx = i;
2947
+ conditionalExpr = prop.argument;
2948
+ } else {
2949
+ return null;
2950
+ }
2951
+ }
2952
+ if (conditionalSpreadIdx === -1 || conditionalExpr === null) {
2953
+ return null;
2954
+ }
2955
+ const otherProps = node.properties.filter((_, i) => i !== conditionalSpreadIdx);
2956
+ const mkObj = (branch) => t.objectExpression([t.spreadElement(branch), ...otherProps]);
2957
+ const resolvedA = tryStaticTransformNode(mkObj(conditionalExpr.consequent), getBinding);
2958
+ const resolvedB = tryStaticTransformNode(mkObj(conditionalExpr.alternate), getBinding);
2959
+ if (!resolvedA || !resolvedB) {
2960
+ return null;
2961
+ }
2962
+ if (!t.isStringLiteral(resolvedA) || !t.isStringLiteral(resolvedB)) {
2963
+ return null;
2964
+ }
2965
+ return t.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
2966
+ }
2450
2967
  function evaluateStaticObject(node) {
2451
2968
  const result = {};
2452
2969
  for (const prop of node.properties) {
@@ -2461,6 +2978,8 @@ function evaluateStaticObject(node) {
2461
2978
  key = prop.key.name;
2462
2979
  } else if (t.isStringLiteral(prop.key)) {
2463
2980
  key = prop.key.value;
2981
+ } else if (t.isNumericLiteral(prop.key)) {
2982
+ key = String(prop.key.value);
2464
2983
  } else {
2465
2984
  return null;
2466
2985
  }
@@ -2485,10 +3004,82 @@ function evaluateStaticObject(node) {
2485
3004
  }
2486
3005
  return result;
2487
3006
  }
3007
+ function resolveObjectSpreads(node, getBinding) {
3008
+ const newProps = [];
3009
+ for (const prop of node.properties) {
3010
+ if (!t.isSpreadElement(prop)) {
3011
+ if (t.isObjectProperty(prop) && t.isObjectExpression(prop.value)) {
3012
+ const resolvedValue = resolveObjectSpreads(prop.value, getBinding);
3013
+ if (resolvedValue === null) {
3014
+ return null;
3015
+ }
3016
+ newProps.push(t.objectProperty(prop.key, resolvedValue, prop.computed, prop.shorthand));
3017
+ } else {
3018
+ newProps.push(prop);
3019
+ }
3020
+ continue;
3021
+ }
3022
+ const arg = prop.argument;
3023
+ if (!t.isIdentifier(arg)) {
3024
+ return null;
3025
+ }
3026
+ const binding = getBinding(arg.name);
3027
+ if (!binding || !binding.path.isVariableDeclarator()) {
3028
+ return null;
3029
+ }
3030
+ let init2 = binding.path.node.init;
3031
+ if (t.isTSAsExpression(init2) || t.isTSSatisfiesExpression(init2)) {
3032
+ init2 = init2.expression;
3033
+ }
3034
+ if (!t.isObjectExpression(init2)) {
3035
+ return null;
3036
+ }
3037
+ const inner = resolveObjectSpreads(init2, getBinding);
3038
+ if (inner === null) {
3039
+ return null;
3040
+ }
3041
+ newProps.push(...inner.properties);
3042
+ }
3043
+ return t.objectExpression(newProps);
3044
+ }
3045
+ function extractStaticLiteralValue(node) {
3046
+ if (t.isStringLiteral(node)) {
3047
+ return node.value;
3048
+ }
3049
+ if (t.isNumericLiteral(node)) {
3050
+ return node.value;
3051
+ }
3052
+ if (t.isBooleanLiteral(node)) {
3053
+ return node.value;
3054
+ }
3055
+ if (t.isUnaryExpression(node) && node.operator === "-" && t.isNumericLiteral(node.argument)) {
3056
+ return -node.argument.value;
3057
+ }
3058
+ return null;
3059
+ }
3060
+ function buildConditionalClassExpr(baseClasses, conditionalClasses) {
3061
+ if (conditionalClasses.length === 0) {
3062
+ return t.stringLiteral(baseClasses);
3063
+ }
3064
+ const makeCondExpr = (cc) => t.conditionalExpression(cc.test, t.stringLiteral(cc.consequent), t.stringLiteral(cc.alternate));
3065
+ if (conditionalClasses.length === 1 && !baseClasses) {
3066
+ return makeCondExpr(conditionalClasses[0]);
3067
+ }
3068
+ const quasis = [];
3069
+ const exprs = [];
3070
+ for (let i = 0; i < conditionalClasses.length; i++) {
3071
+ const prefix = i === 0 ? baseClasses ? baseClasses + " " : "" : " ";
3072
+ quasis.push(t.templateElement({ raw: prefix, cooked: prefix }, false));
3073
+ exprs.push(makeCondExpr(conditionalClasses[i]));
3074
+ }
3075
+ quasis.push(t.templateElement({ raw: "", cooked: "" }, true));
3076
+ return t.templateLiteral(quasis, exprs);
3077
+ }
2488
3078
  function evaluatePartialObject(node, variantChain = "") {
2489
3079
  const staticProps = {};
2490
3080
  const dynamicProps = /* @__PURE__ */ new Map();
2491
3081
  const rawClasses = [];
3082
+ const conditionalClasses = [];
2492
3083
  let usesColorVar = false;
2493
3084
  for (const prop of node.properties) {
2494
3085
  if (t.isSpreadElement(prop)) {
@@ -2505,6 +3096,8 @@ function evaluatePartialObject(node, variantChain = "") {
2505
3096
  key = prop.key.name;
2506
3097
  } else if (t.isStringLiteral(prop.key)) {
2507
3098
  key = prop.key.value;
3099
+ } else if (t.isNumericLiteral(prop.key)) {
3100
+ key = String(prop.key.value);
2508
3101
  } else {
2509
3102
  return null;
2510
3103
  }
@@ -2547,7 +3140,7 @@ function evaluatePartialObject(node, variantChain = "") {
2547
3140
  staticProps[key] = { color: colorStr, op: opVal };
2548
3141
  } else if (t.isExpression(opProp.value)) {
2549
3142
  const variantPfx = variantChain ? `${variantChain}:` : "";
2550
- rawClasses.push(`${variantPfx}${twPrefix}-${colorStr}/[var(${opVarName})]`);
3143
+ rawClasses.push(`${variantPfx}${twPrefix}-${colorStr}/(${opVarName})`);
2551
3144
  dynamicProps.set(uniqueKey, {
2552
3145
  expression: opProp.value,
2553
3146
  category: 3 /* UNITLESS */,
@@ -2586,6 +3179,7 @@ function evaluatePartialObject(node, variantChain = "") {
2586
3179
  dynamicProps.set(k, v);
2587
3180
  }
2588
3181
  rawClasses.push(...nestedResult.rawClasses);
3182
+ conditionalClasses.push(...nestedResult.conditionalClasses);
2589
3183
  if (nestedResult.usesColorVar) {
2590
3184
  usesColorVar = true;
2591
3185
  }
@@ -2594,6 +3188,25 @@ function evaluatePartialObject(node, variantChain = "") {
2594
3188
  }
2595
3189
  }
2596
3190
  }
3191
+ } else if (t.isConditionalExpression(value)) {
3192
+ const consVal = extractStaticLiteralValue(value.consequent);
3193
+ const altVal = extractStaticLiteralValue(value.alternate);
3194
+ if (consVal !== null && altVal !== null) {
3195
+ const { className: classA } = transform({ [key]: consVal });
3196
+ const { className: classB } = transform({ [key]: altVal });
3197
+ const vPfx = variantChain ? getVariantPrefix(variantChain) + ":" : "";
3198
+ const prefixed = (cls) => vPfx ? cls.split(/\s+/).filter(Boolean).map((c) => vPfx + c).join(" ") : cls;
3199
+ conditionalClasses.push({ test: value.test, consequent: prefixed(classA), alternate: prefixed(classB) });
3200
+ } else {
3201
+ const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
3202
+ const category = getPropertyCategory(key);
3203
+ const varName = getCSSVariableName(key, variantChain || void 0);
3204
+ const uniqueKey = variantChain ? `${variantChain}-${key}` : key;
3205
+ if (COLOR_PROPERTIES.has(key)) {
3206
+ usesColorVar = true;
3207
+ }
3208
+ dynamicProps.set(uniqueKey, { expression: value, category, varName, twPrefix, variantChain: variantChain || "" });
3209
+ }
2597
3210
  } else if (t.isExpression(value)) {
2598
3211
  const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2599
3212
  const category = getPropertyCategory(key);
@@ -2613,7 +3226,7 @@ function evaluatePartialObject(node, variantChain = "") {
2613
3226
  return null;
2614
3227
  }
2615
3228
  }
2616
- return { staticProps, dynamicProps, rawClasses, hasSpread: false, usesColorVar };
3229
+ return { staticProps, dynamicProps, rawClasses, conditionalClasses, hasSpread: false, usesColorVar };
2617
3230
  }
2618
3231
  function generateStyleValueExpression(info) {
2619
3232
  const { expression, category } = info;
@@ -2676,7 +3289,7 @@ function collectFromExpr(node, classes) {
2676
3289
  function buildCSSVarClassName(info) {
2677
3290
  const { twPrefix, varName, variantChain } = info;
2678
3291
  const variantPrefix = variantChain ? `${getVariantPrefix(variantChain)}:` : "";
2679
- return `${variantPrefix}${twPrefix}-[var(${varName})]`;
3292
+ return `${variantPrefix}${twPrefix}-(${varName})`;
2680
3293
  }
2681
3294
 
2682
3295
  // src/compiler.ts
@@ -3142,12 +3755,16 @@ function mergeOptions(options = {}) {
3142
3755
  }
3143
3756
  // Annotate the CommonJS export names for ESM import in node:
3144
3757
  0 && (module.exports = {
3758
+ BOOLEAN_SHORTHANDS,
3145
3759
  COLOR_PROPERTIES,
3146
3760
  CsszyxCompiler,
3147
3761
  DEFAULT_COMPILER_OPTIONS,
3762
+ KNOWN_VARIANTS,
3148
3763
  ManifestBuilder,
3149
3764
  PROPERTY_CATEGORY_MAP,
3765
+ PROPERTY_MAP,
3150
3766
  PropertyCategory,
3767
+ SUGGESTION_MAP,
3151
3768
  VERSION,
3152
3769
  buildParentMap,
3153
3770
  createRecoveryToken,