@csszyx/compiler 0.10.9 → 0.10.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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('@csszyx/core');
4
- const transformCore = require('./shared/compiler.mibv6qPF.cjs');
4
+ const transformCore = require('./shared/compiler.dkTeNO_S.cjs');
5
5
  const oxcParser = require('oxc-parser');
6
6
  const t = require('@babel/types');
7
7
  const node_crypto = require('node:crypto');
@@ -81,6 +81,7 @@ function transformSourceCode(source, filename, options) {
81
81
  let usesColorVar = false;
82
82
  let transformed = false;
83
83
  const collectedClasses = /* @__PURE__ */ new Set();
84
+ const szsPendingClasses = [];
84
85
  const rawClassNames = /* @__PURE__ */ new Set();
85
86
  const diagnostics = [];
86
87
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -200,6 +201,70 @@ function transformSourceCode(source, filename, options) {
200
201
  transformed = true;
201
202
  return;
202
203
  }
204
+ if (attrName === "szs") {
205
+ const openingEl = path.parentPath?.isJSXOpeningElement() ? path.parentPath.node : null;
206
+ if (openingEl && isHostElementName(openingEl.name)) {
207
+ diagnostics.push(
208
+ `[csszyx] szs at ${filename ?? "<anonymous>"}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
209
+ );
210
+ return;
211
+ }
212
+ const container = path.node.value;
213
+ if (!t__namespace.isJSXExpressionContainer(container) || !t__namespace.isObjectExpression(container.expression)) {
214
+ diagnostics.push(szsUnsupportedMessage$1(filename));
215
+ return;
216
+ }
217
+ const slotMap = container.expression;
218
+ if (!isValidSzsSlotMap$1(slotMap)) {
219
+ diagnostics.push(szsUnsupportedMessage$1(filename));
220
+ return;
221
+ }
222
+ transformCore.setSzWarnLocation(
223
+ transformCore.formatSzWarnLocation(
224
+ filename ?? "file.tsx",
225
+ path.node.loc?.start.line,
226
+ options?.rootDir
227
+ )
228
+ );
229
+ const compiledSlots = [];
230
+ for (const prop of slotMap.properties) {
231
+ const slot = prop;
232
+ if (t__namespace.isStringLiteral(slot.value)) {
233
+ compiledSlots.push({
234
+ slot,
235
+ classes: slot.value.value,
236
+ rewrite: false
237
+ });
238
+ continue;
239
+ }
240
+ const compiled = tryStaticTransformNode(slot.value);
241
+ if (!compiled || !t__namespace.isStringLiteral(compiled)) {
242
+ diagnostics.push(szsUnsupportedMessage$1(filename));
243
+ return;
244
+ }
245
+ compiledSlots.push({
246
+ slot,
247
+ classes: compiled.value,
248
+ rewrite: true
249
+ });
250
+ }
251
+ for (const {
252
+ slot,
253
+ classes: slotClasses,
254
+ rewrite
255
+ } of compiledSlots) {
256
+ if (rewrite) {
257
+ slot.value = t__namespace.stringLiteral(slotClasses);
258
+ transformed = true;
259
+ }
260
+ for (const c of slotClasses.split(/\s+/)) {
261
+ if (c) {
262
+ szsPendingClasses.push(c);
263
+ }
264
+ }
265
+ }
266
+ return;
267
+ }
203
268
  if (attrName !== "sz") {
204
269
  return;
205
270
  }
@@ -724,22 +789,23 @@ function transformSourceCode(source, filename, options) {
724
789
  transformed = true;
725
790
  }
726
791
  },
727
- // ── dynamic() literal extraction ──────────────────────────────────
728
- // Detects `dynamic({...})` and `dynamic(CONST_IDENTIFIER)` calls
729
- // with statically-analyzable arguments and adds the resulting
730
- // class tokens to collectedClasses so prescanAndWriteClasses()
731
- // includes them in csszyx-classes.html for Tailwind to scan.
732
- // This means dynamic() with static/const args works in Astro SSR
733
- // without needing client:* directives.
792
+ // ── dynamic() / szr() literal extraction ─────────────────────────
793
+ // Detects `dynamic({...})` / `szr({...})` and their
794
+ // `(CONST_IDENTIFIER)` forms with statically-analyzable arguments
795
+ // and adds the resulting class tokens to collectedClasses so
796
+ // prescanAndWriteClasses() includes them in csszyx-classes.html
797
+ // for Tailwind to scan. A bare static `szr({...})` type-checks and
798
+ // resolves at runtime, so without this its classes were silently
799
+ // dead under Tailwind `source(none)`.
734
800
  CallExpression(path) {
735
801
  const callee = path.node.callee;
736
- if (!t__namespace.isIdentifier(callee) || callee.name !== "dynamic") {
802
+ if (!t__namespace.isIdentifier(callee) || callee.name !== "dynamic" && callee.name !== "szr") {
737
803
  return;
738
804
  }
739
805
  if (path.node.arguments.length === 0) {
740
806
  return;
741
807
  }
742
- const arg = path.node.arguments[0];
808
+ const arg = unwrapTsExpression(path.node.arguments[0]);
743
809
  if (t__namespace.isObjectExpression(arg)) {
744
810
  const staticObj = evaluateStaticObject(arg);
745
811
  if (!staticObj) {
@@ -753,10 +819,7 @@ function transformSourceCode(source, filename, options) {
753
819
  }
754
820
  return;
755
821
  }
756
- let argExpr = arg;
757
- while (t__namespace.isTSAsExpression(argExpr) || t__namespace.isTSSatisfiesExpression(argExpr)) {
758
- argExpr = argExpr.expression;
759
- }
822
+ const argExpr = arg;
760
823
  if (t__namespace.isIdentifier(argExpr)) {
761
824
  const binding = path.scope.getBinding(argExpr.name);
762
825
  if (!binding) {
@@ -789,6 +852,9 @@ function transformSourceCode(source, filename, options) {
789
852
  })
790
853
  ]
791
854
  });
855
+ for (const c of szsPendingClasses) {
856
+ collectedClasses.add(c);
857
+ }
792
858
  return {
793
859
  code: result?.code || source,
794
860
  transformed,
@@ -840,6 +906,31 @@ function parseStyleStringToObjectExpr(styleStr) {
840
906
  }
841
907
  return t__namespace.objectExpression(objProps);
842
908
  }
909
+ function isHostElementName(name) {
910
+ return t__namespace.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
911
+ }
912
+ function szsUnsupportedMessage$1(filename) {
913
+ return `[csszyx] szs at ${filename ?? "<anonymous>"}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
914
+ }
915
+ function isPureLiteralSzValue$1(node) {
916
+ if (t__namespace.isStringLiteral(node) || t__namespace.isNumericLiteral(node) || t__namespace.isBooleanLiteral(node)) {
917
+ return true;
918
+ }
919
+ if (t__namespace.isUnaryExpression(node) && node.operator === "-" && t__namespace.isNumericLiteral(node.argument)) {
920
+ return true;
921
+ }
922
+ if (t__namespace.isObjectExpression(node)) {
923
+ return node.properties.every(
924
+ (prop) => t__namespace.isObjectProperty(prop) && !prop.computed && t__namespace.isIdentifier(prop.key) && isPureLiteralSzValue$1(prop.value)
925
+ );
926
+ }
927
+ return false;
928
+ }
929
+ function isValidSzsSlotMap$1(slotMap) {
930
+ return slotMap.properties.every(
931
+ (prop) => t__namespace.isObjectProperty(prop) && !prop.computed && t__namespace.isIdentifier(prop.key) && (t__namespace.isStringLiteral(prop.value) || t__namespace.isObjectExpression(prop.value) && isPureLiteralSzValue$1(prop.value))
932
+ );
933
+ }
843
934
  function emptyClassToUndefined(node) {
844
935
  return t__namespace.isStringLiteral(node) && node.value === "" ? t__namespace.identifier("undefined") : node;
845
936
  }
@@ -967,22 +1058,44 @@ function tryHoistNestedConditional(node, getBinding) {
967
1058
  if (topLevel !== 0 || nested !== 1 || test === null) {
968
1059
  return null;
969
1060
  }
1061
+ const condPropIndex = node.properties.findIndex(
1062
+ (prop) => t__namespace.isObjectProperty(prop) && t__namespace.isObjectExpression(prop.value) && countAllConditionals(prop.value) === 1
1063
+ );
1064
+ if (condPropIndex === -1) {
1065
+ return null;
1066
+ }
1067
+ const staticNode = t__namespace.objectExpression(node.properties.filter((_, i) => i !== condPropIndex));
1068
+ const condNode = t__namespace.objectExpression([node.properties[condPropIndex]]);
1069
+ const staticClasses = staticNode.properties.length > 0 ? tryStaticTransformNode(staticNode, getBinding) : null;
970
1070
  const consequent = tryStaticTransformNode(
971
- cloneObjectPickingBranch(node, "consequent"),
1071
+ cloneObjectPickingBranch(condNode, "consequent"),
972
1072
  getBinding
973
1073
  );
974
1074
  const alternate = tryStaticTransformNode(
975
- cloneObjectPickingBranch(node, "alternate"),
1075
+ cloneObjectPickingBranch(condNode, "alternate"),
976
1076
  getBinding
977
1077
  );
978
- if (!consequent || !alternate || !t__namespace.isStringLiteral(consequent) || !t__namespace.isStringLiteral(alternate)) {
1078
+ if (!consequent || !alternate || !t__namespace.isStringLiteral(consequent) || !t__namespace.isStringLiteral(alternate) || staticNode.properties.length > 0 && (!staticClasses || !t__namespace.isStringLiteral(staticClasses))) {
979
1079
  return null;
980
1080
  }
981
- return t__namespace.conditionalExpression(
1081
+ const ternary = t__namespace.conditionalExpression(
982
1082
  test,
983
1083
  emptyClassToUndefined(consequent),
984
1084
  emptyClassToUndefined(alternate)
985
1085
  );
1086
+ if (!staticClasses || !t__namespace.isStringLiteral(staticClasses) || staticClasses.value === "") {
1087
+ return ternary;
1088
+ }
1089
+ return t__namespace.templateLiteral(
1090
+ [
1091
+ t__namespace.templateElement(
1092
+ { raw: `${staticClasses.value} `, cooked: `${staticClasses.value} ` },
1093
+ false
1094
+ ),
1095
+ t__namespace.templateElement({ raw: "", cooked: "" }, true)
1096
+ ],
1097
+ [ternary]
1098
+ );
986
1099
  }
987
1100
  function tryHoistConditionalSpread(node, getBinding) {
988
1101
  let conditionalSpreadIdx = -1;
@@ -1035,16 +1148,27 @@ function readStaticConfigObject(configExpr, key, scope) {
1035
1148
  }
1036
1149
  return null;
1037
1150
  }
1151
+ function unwrapTsExpression(node) {
1152
+ let current = node;
1153
+ while (t__namespace.isTSSatisfiesExpression(current) || t__namespace.isTSAsExpression(current) || t__namespace.isTSNonNullExpression(current) || t__namespace.isParenthesizedExpression(current)) {
1154
+ current = current.expression;
1155
+ }
1156
+ return current;
1157
+ }
1038
1158
  function resolveToConstObjectExpression(node, scope) {
1039
- if (t__namespace.isObjectExpression(node)) {
1040
- return node;
1159
+ const unwrapped = unwrapTsExpression(node);
1160
+ if (t__namespace.isObjectExpression(unwrapped)) {
1161
+ return unwrapped;
1041
1162
  }
1042
- if (t__namespace.isIdentifier(node)) {
1043
- const binding = scope.getBinding(node.name);
1163
+ if (t__namespace.isIdentifier(unwrapped)) {
1164
+ const binding = scope.getBinding(unwrapped.name);
1044
1165
  if (binding?.kind === "const" && binding.constant) {
1045
1166
  const declNode = binding.path.node;
1046
- if (t__namespace.isVariableDeclarator(declNode) && t__namespace.isObjectExpression(declNode.init)) {
1047
- return declNode.init;
1167
+ if (t__namespace.isVariableDeclarator(declNode)) {
1168
+ const init = unwrapTsExpression(declNode.init);
1169
+ if (t__namespace.isObjectExpression(init)) {
1170
+ return init;
1171
+ }
1048
1172
  }
1049
1173
  }
1050
1174
  }
@@ -1069,7 +1193,7 @@ function evaluateStaticObject(node) {
1069
1193
  } else {
1070
1194
  return null;
1071
1195
  }
1072
- const value = prop.value;
1196
+ const value = unwrapTsExpression(prop.value);
1073
1197
  if (t__namespace.isStringLiteral(value)) {
1074
1198
  result[key] = value.value;
1075
1199
  } else if (t__namespace.isNumericLiteral(value)) {
@@ -1392,6 +1516,18 @@ function collectFromExpr(node, classes) {
1392
1516
  } else if (t__namespace.isConditionalExpression(node)) {
1393
1517
  collectFromExpr(node.consequent, classes);
1394
1518
  collectFromExpr(node.alternate, classes);
1519
+ } else if (t__namespace.isTemplateLiteral(node)) {
1520
+ for (let i = 0; i < node.quasis.length; i++) {
1521
+ for (const c of (node.quasis[i].value.cooked ?? "").split(/\s+/)) {
1522
+ if (c) {
1523
+ classes.add(c);
1524
+ }
1525
+ }
1526
+ const expr = node.expressions[i];
1527
+ if (expr && t__namespace.isExpression(expr)) {
1528
+ collectFromExpr(expr, classes);
1529
+ }
1530
+ }
1395
1531
  }
1396
1532
  }
1397
1533
  function collectCandidatesFromBabelExpr(node, path, classes) {
@@ -2405,6 +2541,7 @@ class OxcNotImplementedError extends Error {
2405
2541
  }
2406
2542
  function transformOxc(source, filename, options) {
2407
2543
  const classes = /* @__PURE__ */ new Set();
2544
+ const szsPendingClasses = [];
2408
2545
  const rawClassNames = /* @__PURE__ */ new Set();
2409
2546
  const diagnostics = [];
2410
2547
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -2427,7 +2564,7 @@ function transformOxc(source, filename, options) {
2427
2564
  const effectiveFilename = filename ?? "file.tsx";
2428
2565
  transformCore.setSzWarnLocation(void 0);
2429
2566
  const astBudget = options?.astBudget ?? AST_BUDGET;
2430
- const parsed = oxcParser.parseSync(effectiveFilename, source);
2567
+ const parsed = /\.(?:js|mjs|cjs)$/.test(effectiveFilename) ? oxcParser.parseSync(effectiveFilename, source, { lang: "jsx" }) : oxcParser.parseSync(effectiveFilename, source);
2431
2568
  if (parsed.errors.length > 0) {
2432
2569
  throw new Error(
2433
2570
  `oxc-parser errors in ${effectiveFilename}: ` + parsed.errors.map((e) => e.message).join("; ")
@@ -2476,6 +2613,7 @@ function transformOxc(source, filename, options) {
2476
2613
  const openingNode = node;
2477
2614
  const attrs = openingNode.attributes ?? [];
2478
2615
  const szAttrs = [];
2616
+ const szsAttrs = [];
2479
2617
  let classNameAttr = null;
2480
2618
  let styleAttr = null;
2481
2619
  let szRecoverAttr = null;
@@ -2508,6 +2646,8 @@ function transformOxc(source, filename, options) {
2508
2646
  const name = attr.name?.name;
2509
2647
  if (name === "sz") {
2510
2648
  szAttrs.push(attr);
2649
+ } else if (name === "szs") {
2650
+ szsAttrs.push(attr);
2511
2651
  } else if (name === "className" || name === "class") {
2512
2652
  classNameAttr = attr;
2513
2653
  } else if (name === "style") {
@@ -2558,6 +2698,89 @@ function transformOxc(source, filename, options) {
2558
2698
  }
2559
2699
  }
2560
2700
  }
2701
+ for (const szsAttr of szsAttrs) {
2702
+ if (isHostOpeningElementName(openingNode.name)) {
2703
+ diagnostics.push(
2704
+ `[csszyx] szs at ${effectiveFilename}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
2705
+ );
2706
+ continue;
2707
+ }
2708
+ const szsValue = szsAttr.value;
2709
+ const szsExpression = szsValue && szsValue.type === "JSXExpressionContainer" ? szsValue.expression : null;
2710
+ if (!szsExpression || szsExpression.type !== "ObjectExpression") {
2711
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2712
+ continue;
2713
+ }
2714
+ const slotMap = szsExpression;
2715
+ if (!isValidSzsSlotMap(slotMap)) {
2716
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2717
+ continue;
2718
+ }
2719
+ const { line: szsWarnLine } = offsetToLineColumn(source, szsAttr.start);
2720
+ transformCore.setSzWarnLocation(
2721
+ transformCore.formatSzWarnLocation(effectiveFilename, szsWarnLine, options?.rootDir)
2722
+ );
2723
+ const slotEntries = [];
2724
+ let anyCompiled = false;
2725
+ let slotFailed = false;
2726
+ for (const propRaw of slotMap.properties) {
2727
+ const prop = propRaw;
2728
+ const keyText = source.slice(prop.key.start, prop.key.end);
2729
+ const propValue = prop.value;
2730
+ const literal = propValue.type === "Literal" ? propValue.value : null;
2731
+ if (typeof literal === "string") {
2732
+ slotEntries.push({
2733
+ keyText,
2734
+ classNames: literal,
2735
+ text: source.slice(propValue.start, propValue.end)
2736
+ });
2737
+ continue;
2738
+ }
2739
+ try {
2740
+ const slotObject = astObjectToSzObject(
2741
+ propValue,
2742
+ effectiveFilename,
2743
+ objectBindings
2744
+ );
2745
+ const compiled = transformCore.transform(
2746
+ applyGlobalVarAliasesToSzObject(
2747
+ slotObject,
2748
+ globalVarAliases,
2749
+ cssVariableMap
2750
+ )
2751
+ ).className;
2752
+ slotEntries.push({
2753
+ keyText,
2754
+ classNames: compiled,
2755
+ text: JSON.stringify(compiled)
2756
+ });
2757
+ anyCompiled = true;
2758
+ } catch (err) {
2759
+ if (err instanceof OxcNotImplementedError) {
2760
+ slotFailed = true;
2761
+ break;
2762
+ }
2763
+ throw err;
2764
+ }
2765
+ }
2766
+ transformCore.setSzWarnLocation(void 0);
2767
+ if (slotFailed) {
2768
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2769
+ continue;
2770
+ }
2771
+ if (anyCompiled) {
2772
+ const body = slotEntries.map((entry) => `${entry.keyText}: ${entry.text}`).join(", ");
2773
+ edits.overwrite(szsAttr.start, szsAttr.end, `szs={{ ${body} }}`);
2774
+ transformed = true;
2775
+ }
2776
+ for (const entry of slotEntries) {
2777
+ for (const c of entry.classNames.split(/\s+/)) {
2778
+ if (c) {
2779
+ szsPendingClasses.push(c);
2780
+ }
2781
+ }
2782
+ }
2783
+ }
2561
2784
  if (szAttrs.length === 0) {
2562
2785
  applyHoistedStyleProps();
2563
2786
  return;
@@ -2918,6 +3141,24 @@ function transformOxc(source, filename, options) {
2918
3141
  ];
2919
3142
  const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2920
3143
  if (classNameAttr) {
3144
+ const classNameValue = classNameAttr.value;
3145
+ if (existingRaw === null && classNameValue && classNameValue.type === "JSXExpressionContainer") {
3146
+ const exprNode = classNameValue.expression;
3147
+ const exprSource = source.slice(exprNode.start, exprNode.end);
3148
+ edits.overwrite(
3149
+ classNameAttr.start,
3150
+ classNameAttr.end,
3151
+ `className={_szMerge(${exprSource}, ${JSON.stringify(szDerived.join(" "))})}`
3152
+ );
3153
+ for (const szAttr of szAttrs) {
3154
+ const deleteStart = whitespaceStart(source, szAttr.start);
3155
+ edits.remove(deleteStart, szAttr.end);
3156
+ }
3157
+ usesRuntime = true;
3158
+ usesMerge = true;
3159
+ transformed = true;
3160
+ return;
3161
+ }
2921
3162
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2922
3163
  for (const szAttr of szAttrs) {
2923
3164
  const deleteStart = whitespaceStart(source, szAttr.start);
@@ -2936,6 +3177,9 @@ function transformOxc(source, filename, options) {
2936
3177
  }
2937
3178
  transformed = true;
2938
3179
  });
3180
+ for (const c of szsPendingClasses) {
3181
+ classes.add(c);
3182
+ }
2939
3183
  return {
2940
3184
  code: transformed ? edits.toString() : source,
2941
3185
  transformed,
@@ -3005,6 +3249,49 @@ function buildRuntimeFallbackDiagnostic(expression, source) {
3005
3249
  return `sz fallback at ${lineCol}: ${reason}.
3006
3250
  Suggestion: ${suggestion}`;
3007
3251
  }
3252
+ function isHostOpeningElementName(nameNode) {
3253
+ return nameNode.type === "JSXIdentifier" && /^[a-z]/.test(String(nameNode.name));
3254
+ }
3255
+ function szsUnsupportedMessage(filename) {
3256
+ return `[csszyx] szs at ${filename}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
3257
+ }
3258
+ function isPureLiteralSzValue(node) {
3259
+ if (node.type === "Literal") {
3260
+ const value = node.value;
3261
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
3262
+ }
3263
+ if (node.type === "UnaryExpression") {
3264
+ const unary = node;
3265
+ return unary.operator === "-" && unary.argument.type === "Literal" && typeof unary.argument.value === "number";
3266
+ }
3267
+ if (node.type === "ObjectExpression") {
3268
+ const properties = node.properties;
3269
+ return properties.every((propRaw) => {
3270
+ if (propRaw.type !== "Property") {
3271
+ return false;
3272
+ }
3273
+ const prop = propRaw;
3274
+ return !prop.computed && prop.key.type === "Identifier" && isPureLiteralSzValue(prop.value);
3275
+ });
3276
+ }
3277
+ return false;
3278
+ }
3279
+ function isValidSzsSlotMap(slotMap) {
3280
+ return slotMap.properties.every((propRaw) => {
3281
+ if (propRaw.type !== "Property") {
3282
+ return false;
3283
+ }
3284
+ const prop = propRaw;
3285
+ if (prop.computed || prop.key.type !== "Identifier") {
3286
+ return false;
3287
+ }
3288
+ const value = prop.value;
3289
+ if (value.type === "Literal" && typeof value.value === "string") {
3290
+ return true;
3291
+ }
3292
+ return value.type === "ObjectExpression" && isPureLiteralSzValue(value);
3293
+ });
3294
+ }
3008
3295
  function extractElementName(nameNode) {
3009
3296
  if (nameNode.type === "JSXIdentifier") {
3010
3297
  return String(nameNode.name);
@@ -3385,7 +3672,11 @@ function assertAstBudget(root, filename, astBudget) {
3385
3672
  });
3386
3673
  }
3387
3674
  function collectDynamicCallClasses(node, filename, bindings, classes) {
3388
- if (node.callee.type !== "Identifier" || node.callee.name !== "dynamic") {
3675
+ if (node.callee.type !== "Identifier") {
3676
+ return;
3677
+ }
3678
+ const calleeName = node.callee.name;
3679
+ if (calleeName !== "dynamic" && calleeName !== "szr") {
3389
3680
  return;
3390
3681
  }
3391
3682
  const [firstArg] = node.arguments;
@@ -3527,11 +3818,25 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3527
3818
  if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3528
3819
  return null;
3529
3820
  }
3530
- const compileBranch = (pick) => {
3821
+ const condPropIndex = node.properties.findIndex(
3822
+ (prop) => prop.type === "Property" && prop.value.type === "ObjectExpression" && countOxcConditionals(prop.value) === 1
3823
+ );
3824
+ if (condPropIndex === -1) {
3825
+ return null;
3826
+ }
3827
+ const staticNode = {
3828
+ ...node,
3829
+ properties: node.properties.filter((_, i) => i !== condPropIndex)
3830
+ };
3831
+ const condNode = {
3832
+ ...node,
3833
+ properties: [node.properties[condPropIndex]]
3834
+ };
3835
+ const compile = (target, pick) => {
3531
3836
  try {
3532
3837
  return transformCore.transform(
3533
3838
  applyGlobalVarAliasesToSzObject(
3534
- astObjectToSzObject(node, filename, bindings, pick),
3839
+ astObjectToSzObject(target, filename, bindings, pick),
3535
3840
  globalVarAliases,
3536
3841
  cssVariableMap
3537
3842
  )
@@ -3543,19 +3848,24 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3543
3848
  throw err;
3544
3849
  }
3545
3850
  };
3546
- const consequent = compileBranch("consequent");
3547
- const alternate = compileBranch("alternate");
3548
- if (consequent === null || alternate === null) {
3851
+ const staticClasses = staticNode.properties.length > 0 ? compile(staticNode) : "";
3852
+ const consequent = compile(condNode, "consequent");
3853
+ const alternate = compile(condNode, "alternate");
3854
+ if (staticClasses === null || consequent === null || alternate === null) {
3549
3855
  return null;
3550
3856
  }
3551
- for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3857
+ for (const cls of `${staticClasses} ${consequent} ${alternate}`.split(/\s+/)) {
3552
3858
  if (cls) {
3553
3859
  classes.add(cls);
3554
3860
  }
3555
3861
  }
3556
3862
  const testSource = source.slice(first.test.start, first.test.end);
3557
3863
  const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3558
- return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3864
+ const ternary = `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3865
+ if (staticClasses === "") {
3866
+ return ternary;
3867
+ }
3868
+ return `\`${staticClasses} \${${ternary}}\``;
3559
3869
  }
3560
3870
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3561
3871
  let conditionalSpread = null;
@@ -4342,6 +4652,7 @@ function extractKeyName(key) {
4342
4652
  return null;
4343
4653
  }
4344
4654
  function astValueToSzValue(node, filename, bindings, branchPick) {
4655
+ node = unwrapExpression(node);
4345
4656
  if (branchPick && node.type === "ConditionalExpression") {
4346
4657
  return astValueToSzValue(
4347
4658
  node[branchPick],
package/dist/index.d.cts CHANGED
@@ -13612,6 +13612,24 @@ type SzArrayElement = SzProps | false | null | undefined;
13612
13612
  * ```
13613
13613
  */
13614
13614
  type SzPropValue = string | SzProps | SzArrayElement[];
13615
+ /**
13616
+ * Value of the `szs` prop — a map of a component's slot names to sz values, so a
13617
+ * consumer styles the parts a compound component renders internally. The build
13618
+ * transform compiles each slot VALUE to its Tailwind class string (keeping the
13619
+ * key), safelists and mangles the classes exactly like `sz`, and the component
13620
+ * forwards `props.szs?.<slot>` into the matching child's `className`.
13621
+ *
13622
+ * Component authors declare their slot set so consumers get autocompletion and
13623
+ * typo checking:
13624
+ *
13625
+ * @example
13626
+ * ```tsx
13627
+ * type CardProps = { szs?: Szs<'header' | 'icon'> };
13628
+ * // consumer:
13629
+ * <Card szs={{ header: { bg: 'gray-100' }, icon: { color: 'red-500' } }} />
13630
+ * ```
13631
+ */
13632
+ type Szs<Slots extends string = string> = Partial<Record<Slots, SzPropValue>>;
13615
13633
 
13616
13634
  /**
13617
13635
  * @csszyx/compiler - TypeScript compiler package for csszyx.
@@ -13662,4 +13680,4 @@ declare const DEFAULT_COMPILER_OPTIONS: Required<CompilerOptions>;
13662
13680
  declare function mergeOptions(options?: Partial<CompilerOptions>): Required<CompilerOptions>;
13663
13681
 
13664
13682
  export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, sortStrings, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13665
- export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
13683
+ export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, Szs, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
package/dist/index.d.mts CHANGED
@@ -13612,6 +13612,24 @@ type SzArrayElement = SzProps | false | null | undefined;
13612
13612
  * ```
13613
13613
  */
13614
13614
  type SzPropValue = string | SzProps | SzArrayElement[];
13615
+ /**
13616
+ * Value of the `szs` prop — a map of a component's slot names to sz values, so a
13617
+ * consumer styles the parts a compound component renders internally. The build
13618
+ * transform compiles each slot VALUE to its Tailwind class string (keeping the
13619
+ * key), safelists and mangles the classes exactly like `sz`, and the component
13620
+ * forwards `props.szs?.<slot>` into the matching child's `className`.
13621
+ *
13622
+ * Component authors declare their slot set so consumers get autocompletion and
13623
+ * typo checking:
13624
+ *
13625
+ * @example
13626
+ * ```tsx
13627
+ * type CardProps = { szs?: Szs<'header' | 'icon'> };
13628
+ * // consumer:
13629
+ * <Card szs={{ header: { bg: 'gray-100' }, icon: { color: 'red-500' } }} />
13630
+ * ```
13631
+ */
13632
+ type Szs<Slots extends string = string> = Partial<Record<Slots, SzPropValue>>;
13615
13633
 
13616
13634
  /**
13617
13635
  * @csszyx/compiler - TypeScript compiler package for csszyx.
@@ -13662,4 +13680,4 @@ declare const DEFAULT_COMPILER_OPTIONS: Required<CompilerOptions>;
13662
13680
  declare function mergeOptions(options?: Partial<CompilerOptions>): Required<CompilerOptions>;
13663
13681
 
13664
13682
  export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, sortStrings, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13665
- export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
13683
+ export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, Szs, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { init, version, transform_sz, encode } from '@csszyx/core';
2
- import { t as transform, s as setSzWarnLocation, f as formatSzWarnLocation, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, d as stripInvalidColorStrings } from './shared/compiler.CghwJ6p5.mjs';
3
- export { B as BOOLEAN_SHORTHANDS, e as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.CghwJ6p5.mjs';
2
+ import { t as transform, s as setSzWarnLocation, f as formatSzWarnLocation, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, d as stripInvalidColorStrings } from './shared/compiler.zZfo8y65.mjs';
3
+ export { B as BOOLEAN_SHORTHANDS, e as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.zZfo8y65.mjs';
4
4
  import { parseSync } from 'oxc-parser';
5
5
  import * as t from '@babel/types';
6
6
  import { createHash } from 'node:crypto';
@@ -62,6 +62,7 @@ function transformSourceCode(source, filename, options) {
62
62
  let usesColorVar = false;
63
63
  let transformed = false;
64
64
  const collectedClasses = /* @__PURE__ */ new Set();
65
+ const szsPendingClasses = [];
65
66
  const rawClassNames = /* @__PURE__ */ new Set();
66
67
  const diagnostics = [];
67
68
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -181,6 +182,70 @@ function transformSourceCode(source, filename, options) {
181
182
  transformed = true;
182
183
  return;
183
184
  }
185
+ if (attrName === "szs") {
186
+ const openingEl = path.parentPath?.isJSXOpeningElement() ? path.parentPath.node : null;
187
+ if (openingEl && isHostElementName(openingEl.name)) {
188
+ diagnostics.push(
189
+ `[csszyx] szs at ${filename ?? "<anonymous>"}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
190
+ );
191
+ return;
192
+ }
193
+ const container = path.node.value;
194
+ if (!t.isJSXExpressionContainer(container) || !t.isObjectExpression(container.expression)) {
195
+ diagnostics.push(szsUnsupportedMessage$1(filename));
196
+ return;
197
+ }
198
+ const slotMap = container.expression;
199
+ if (!isValidSzsSlotMap$1(slotMap)) {
200
+ diagnostics.push(szsUnsupportedMessage$1(filename));
201
+ return;
202
+ }
203
+ setSzWarnLocation(
204
+ formatSzWarnLocation(
205
+ filename ?? "file.tsx",
206
+ path.node.loc?.start.line,
207
+ options?.rootDir
208
+ )
209
+ );
210
+ const compiledSlots = [];
211
+ for (const prop of slotMap.properties) {
212
+ const slot = prop;
213
+ if (t.isStringLiteral(slot.value)) {
214
+ compiledSlots.push({
215
+ slot,
216
+ classes: slot.value.value,
217
+ rewrite: false
218
+ });
219
+ continue;
220
+ }
221
+ const compiled = tryStaticTransformNode(slot.value);
222
+ if (!compiled || !t.isStringLiteral(compiled)) {
223
+ diagnostics.push(szsUnsupportedMessage$1(filename));
224
+ return;
225
+ }
226
+ compiledSlots.push({
227
+ slot,
228
+ classes: compiled.value,
229
+ rewrite: true
230
+ });
231
+ }
232
+ for (const {
233
+ slot,
234
+ classes: slotClasses,
235
+ rewrite
236
+ } of compiledSlots) {
237
+ if (rewrite) {
238
+ slot.value = t.stringLiteral(slotClasses);
239
+ transformed = true;
240
+ }
241
+ for (const c of slotClasses.split(/\s+/)) {
242
+ if (c) {
243
+ szsPendingClasses.push(c);
244
+ }
245
+ }
246
+ }
247
+ return;
248
+ }
184
249
  if (attrName !== "sz") {
185
250
  return;
186
251
  }
@@ -705,22 +770,23 @@ function transformSourceCode(source, filename, options) {
705
770
  transformed = true;
706
771
  }
707
772
  },
708
- // ── dynamic() literal extraction ──────────────────────────────────
709
- // Detects `dynamic({...})` and `dynamic(CONST_IDENTIFIER)` calls
710
- // with statically-analyzable arguments and adds the resulting
711
- // class tokens to collectedClasses so prescanAndWriteClasses()
712
- // includes them in csszyx-classes.html for Tailwind to scan.
713
- // This means dynamic() with static/const args works in Astro SSR
714
- // without needing client:* directives.
773
+ // ── dynamic() / szr() literal extraction ─────────────────────────
774
+ // Detects `dynamic({...})` / `szr({...})` and their
775
+ // `(CONST_IDENTIFIER)` forms with statically-analyzable arguments
776
+ // and adds the resulting class tokens to collectedClasses so
777
+ // prescanAndWriteClasses() includes them in csszyx-classes.html
778
+ // for Tailwind to scan. A bare static `szr({...})` type-checks and
779
+ // resolves at runtime, so without this its classes were silently
780
+ // dead under Tailwind `source(none)`.
715
781
  CallExpression(path) {
716
782
  const callee = path.node.callee;
717
- if (!t.isIdentifier(callee) || callee.name !== "dynamic") {
783
+ if (!t.isIdentifier(callee) || callee.name !== "dynamic" && callee.name !== "szr") {
718
784
  return;
719
785
  }
720
786
  if (path.node.arguments.length === 0) {
721
787
  return;
722
788
  }
723
- const arg = path.node.arguments[0];
789
+ const arg = unwrapTsExpression(path.node.arguments[0]);
724
790
  if (t.isObjectExpression(arg)) {
725
791
  const staticObj = evaluateStaticObject(arg);
726
792
  if (!staticObj) {
@@ -734,10 +800,7 @@ function transformSourceCode(source, filename, options) {
734
800
  }
735
801
  return;
736
802
  }
737
- let argExpr = arg;
738
- while (t.isTSAsExpression(argExpr) || t.isTSSatisfiesExpression(argExpr)) {
739
- argExpr = argExpr.expression;
740
- }
803
+ const argExpr = arg;
741
804
  if (t.isIdentifier(argExpr)) {
742
805
  const binding = path.scope.getBinding(argExpr.name);
743
806
  if (!binding) {
@@ -770,6 +833,9 @@ function transformSourceCode(source, filename, options) {
770
833
  })
771
834
  ]
772
835
  });
836
+ for (const c of szsPendingClasses) {
837
+ collectedClasses.add(c);
838
+ }
773
839
  return {
774
840
  code: result?.code || source,
775
841
  transformed,
@@ -821,6 +887,31 @@ function parseStyleStringToObjectExpr(styleStr) {
821
887
  }
822
888
  return t.objectExpression(objProps);
823
889
  }
890
+ function isHostElementName(name) {
891
+ return t.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
892
+ }
893
+ function szsUnsupportedMessage$1(filename) {
894
+ return `[csszyx] szs at ${filename ?? "<anonymous>"}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
895
+ }
896
+ function isPureLiteralSzValue$1(node) {
897
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node)) {
898
+ return true;
899
+ }
900
+ if (t.isUnaryExpression(node) && node.operator === "-" && t.isNumericLiteral(node.argument)) {
901
+ return true;
902
+ }
903
+ if (t.isObjectExpression(node)) {
904
+ return node.properties.every(
905
+ (prop) => t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.key) && isPureLiteralSzValue$1(prop.value)
906
+ );
907
+ }
908
+ return false;
909
+ }
910
+ function isValidSzsSlotMap$1(slotMap) {
911
+ return slotMap.properties.every(
912
+ (prop) => t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.key) && (t.isStringLiteral(prop.value) || t.isObjectExpression(prop.value) && isPureLiteralSzValue$1(prop.value))
913
+ );
914
+ }
824
915
  function emptyClassToUndefined(node) {
825
916
  return t.isStringLiteral(node) && node.value === "" ? t.identifier("undefined") : node;
826
917
  }
@@ -948,22 +1039,44 @@ function tryHoistNestedConditional(node, getBinding) {
948
1039
  if (topLevel !== 0 || nested !== 1 || test === null) {
949
1040
  return null;
950
1041
  }
1042
+ const condPropIndex = node.properties.findIndex(
1043
+ (prop) => t.isObjectProperty(prop) && t.isObjectExpression(prop.value) && countAllConditionals(prop.value) === 1
1044
+ );
1045
+ if (condPropIndex === -1) {
1046
+ return null;
1047
+ }
1048
+ const staticNode = t.objectExpression(node.properties.filter((_, i) => i !== condPropIndex));
1049
+ const condNode = t.objectExpression([node.properties[condPropIndex]]);
1050
+ const staticClasses = staticNode.properties.length > 0 ? tryStaticTransformNode(staticNode, getBinding) : null;
951
1051
  const consequent = tryStaticTransformNode(
952
- cloneObjectPickingBranch(node, "consequent"),
1052
+ cloneObjectPickingBranch(condNode, "consequent"),
953
1053
  getBinding
954
1054
  );
955
1055
  const alternate = tryStaticTransformNode(
956
- cloneObjectPickingBranch(node, "alternate"),
1056
+ cloneObjectPickingBranch(condNode, "alternate"),
957
1057
  getBinding
958
1058
  );
959
- if (!consequent || !alternate || !t.isStringLiteral(consequent) || !t.isStringLiteral(alternate)) {
1059
+ if (!consequent || !alternate || !t.isStringLiteral(consequent) || !t.isStringLiteral(alternate) || staticNode.properties.length > 0 && (!staticClasses || !t.isStringLiteral(staticClasses))) {
960
1060
  return null;
961
1061
  }
962
- return t.conditionalExpression(
1062
+ const ternary = t.conditionalExpression(
963
1063
  test,
964
1064
  emptyClassToUndefined(consequent),
965
1065
  emptyClassToUndefined(alternate)
966
1066
  );
1067
+ if (!staticClasses || !t.isStringLiteral(staticClasses) || staticClasses.value === "") {
1068
+ return ternary;
1069
+ }
1070
+ return t.templateLiteral(
1071
+ [
1072
+ t.templateElement(
1073
+ { raw: `${staticClasses.value} `, cooked: `${staticClasses.value} ` },
1074
+ false
1075
+ ),
1076
+ t.templateElement({ raw: "", cooked: "" }, true)
1077
+ ],
1078
+ [ternary]
1079
+ );
967
1080
  }
968
1081
  function tryHoistConditionalSpread(node, getBinding) {
969
1082
  let conditionalSpreadIdx = -1;
@@ -1016,16 +1129,27 @@ function readStaticConfigObject(configExpr, key, scope) {
1016
1129
  }
1017
1130
  return null;
1018
1131
  }
1132
+ function unwrapTsExpression(node) {
1133
+ let current = node;
1134
+ while (t.isTSSatisfiesExpression(current) || t.isTSAsExpression(current) || t.isTSNonNullExpression(current) || t.isParenthesizedExpression(current)) {
1135
+ current = current.expression;
1136
+ }
1137
+ return current;
1138
+ }
1019
1139
  function resolveToConstObjectExpression(node, scope) {
1020
- if (t.isObjectExpression(node)) {
1021
- return node;
1140
+ const unwrapped = unwrapTsExpression(node);
1141
+ if (t.isObjectExpression(unwrapped)) {
1142
+ return unwrapped;
1022
1143
  }
1023
- if (t.isIdentifier(node)) {
1024
- const binding = scope.getBinding(node.name);
1144
+ if (t.isIdentifier(unwrapped)) {
1145
+ const binding = scope.getBinding(unwrapped.name);
1025
1146
  if (binding?.kind === "const" && binding.constant) {
1026
1147
  const declNode = binding.path.node;
1027
- if (t.isVariableDeclarator(declNode) && t.isObjectExpression(declNode.init)) {
1028
- return declNode.init;
1148
+ if (t.isVariableDeclarator(declNode)) {
1149
+ const init = unwrapTsExpression(declNode.init);
1150
+ if (t.isObjectExpression(init)) {
1151
+ return init;
1152
+ }
1029
1153
  }
1030
1154
  }
1031
1155
  }
@@ -1050,7 +1174,7 @@ function evaluateStaticObject(node) {
1050
1174
  } else {
1051
1175
  return null;
1052
1176
  }
1053
- const value = prop.value;
1177
+ const value = unwrapTsExpression(prop.value);
1054
1178
  if (t.isStringLiteral(value)) {
1055
1179
  result[key] = value.value;
1056
1180
  } else if (t.isNumericLiteral(value)) {
@@ -1373,6 +1497,18 @@ function collectFromExpr(node, classes) {
1373
1497
  } else if (t.isConditionalExpression(node)) {
1374
1498
  collectFromExpr(node.consequent, classes);
1375
1499
  collectFromExpr(node.alternate, classes);
1500
+ } else if (t.isTemplateLiteral(node)) {
1501
+ for (let i = 0; i < node.quasis.length; i++) {
1502
+ for (const c of (node.quasis[i].value.cooked ?? "").split(/\s+/)) {
1503
+ if (c) {
1504
+ classes.add(c);
1505
+ }
1506
+ }
1507
+ const expr = node.expressions[i];
1508
+ if (expr && t.isExpression(expr)) {
1509
+ collectFromExpr(expr, classes);
1510
+ }
1511
+ }
1376
1512
  }
1377
1513
  }
1378
1514
  function collectCandidatesFromBabelExpr(node, path, classes) {
@@ -2386,6 +2522,7 @@ class OxcNotImplementedError extends Error {
2386
2522
  }
2387
2523
  function transformOxc(source, filename, options) {
2388
2524
  const classes = /* @__PURE__ */ new Set();
2525
+ const szsPendingClasses = [];
2389
2526
  const rawClassNames = /* @__PURE__ */ new Set();
2390
2527
  const diagnostics = [];
2391
2528
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -2408,7 +2545,7 @@ function transformOxc(source, filename, options) {
2408
2545
  const effectiveFilename = filename ?? "file.tsx";
2409
2546
  setSzWarnLocation(void 0);
2410
2547
  const astBudget = options?.astBudget ?? AST_BUDGET;
2411
- const parsed = parseSync(effectiveFilename, source);
2548
+ const parsed = /\.(?:js|mjs|cjs)$/.test(effectiveFilename) ? parseSync(effectiveFilename, source, { lang: "jsx" }) : parseSync(effectiveFilename, source);
2412
2549
  if (parsed.errors.length > 0) {
2413
2550
  throw new Error(
2414
2551
  `oxc-parser errors in ${effectiveFilename}: ` + parsed.errors.map((e) => e.message).join("; ")
@@ -2457,6 +2594,7 @@ function transformOxc(source, filename, options) {
2457
2594
  const openingNode = node;
2458
2595
  const attrs = openingNode.attributes ?? [];
2459
2596
  const szAttrs = [];
2597
+ const szsAttrs = [];
2460
2598
  let classNameAttr = null;
2461
2599
  let styleAttr = null;
2462
2600
  let szRecoverAttr = null;
@@ -2489,6 +2627,8 @@ function transformOxc(source, filename, options) {
2489
2627
  const name = attr.name?.name;
2490
2628
  if (name === "sz") {
2491
2629
  szAttrs.push(attr);
2630
+ } else if (name === "szs") {
2631
+ szsAttrs.push(attr);
2492
2632
  } else if (name === "className" || name === "class") {
2493
2633
  classNameAttr = attr;
2494
2634
  } else if (name === "style") {
@@ -2539,6 +2679,89 @@ function transformOxc(source, filename, options) {
2539
2679
  }
2540
2680
  }
2541
2681
  }
2682
+ for (const szsAttr of szsAttrs) {
2683
+ if (isHostOpeningElementName(openingNode.name)) {
2684
+ diagnostics.push(
2685
+ `[csszyx] szs at ${effectiveFilename}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
2686
+ );
2687
+ continue;
2688
+ }
2689
+ const szsValue = szsAttr.value;
2690
+ const szsExpression = szsValue && szsValue.type === "JSXExpressionContainer" ? szsValue.expression : null;
2691
+ if (!szsExpression || szsExpression.type !== "ObjectExpression") {
2692
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2693
+ continue;
2694
+ }
2695
+ const slotMap = szsExpression;
2696
+ if (!isValidSzsSlotMap(slotMap)) {
2697
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2698
+ continue;
2699
+ }
2700
+ const { line: szsWarnLine } = offsetToLineColumn(source, szsAttr.start);
2701
+ setSzWarnLocation(
2702
+ formatSzWarnLocation(effectiveFilename, szsWarnLine, options?.rootDir)
2703
+ );
2704
+ const slotEntries = [];
2705
+ let anyCompiled = false;
2706
+ let slotFailed = false;
2707
+ for (const propRaw of slotMap.properties) {
2708
+ const prop = propRaw;
2709
+ const keyText = source.slice(prop.key.start, prop.key.end);
2710
+ const propValue = prop.value;
2711
+ const literal = propValue.type === "Literal" ? propValue.value : null;
2712
+ if (typeof literal === "string") {
2713
+ slotEntries.push({
2714
+ keyText,
2715
+ classNames: literal,
2716
+ text: source.slice(propValue.start, propValue.end)
2717
+ });
2718
+ continue;
2719
+ }
2720
+ try {
2721
+ const slotObject = astObjectToSzObject(
2722
+ propValue,
2723
+ effectiveFilename,
2724
+ objectBindings
2725
+ );
2726
+ const compiled = transform(
2727
+ applyGlobalVarAliasesToSzObject(
2728
+ slotObject,
2729
+ globalVarAliases,
2730
+ cssVariableMap
2731
+ )
2732
+ ).className;
2733
+ slotEntries.push({
2734
+ keyText,
2735
+ classNames: compiled,
2736
+ text: JSON.stringify(compiled)
2737
+ });
2738
+ anyCompiled = true;
2739
+ } catch (err) {
2740
+ if (err instanceof OxcNotImplementedError) {
2741
+ slotFailed = true;
2742
+ break;
2743
+ }
2744
+ throw err;
2745
+ }
2746
+ }
2747
+ setSzWarnLocation(void 0);
2748
+ if (slotFailed) {
2749
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2750
+ continue;
2751
+ }
2752
+ if (anyCompiled) {
2753
+ const body = slotEntries.map((entry) => `${entry.keyText}: ${entry.text}`).join(", ");
2754
+ edits.overwrite(szsAttr.start, szsAttr.end, `szs={{ ${body} }}`);
2755
+ transformed = true;
2756
+ }
2757
+ for (const entry of slotEntries) {
2758
+ for (const c of entry.classNames.split(/\s+/)) {
2759
+ if (c) {
2760
+ szsPendingClasses.push(c);
2761
+ }
2762
+ }
2763
+ }
2764
+ }
2542
2765
  if (szAttrs.length === 0) {
2543
2766
  applyHoistedStyleProps();
2544
2767
  return;
@@ -2899,6 +3122,24 @@ function transformOxc(source, filename, options) {
2899
3122
  ];
2900
3123
  const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2901
3124
  if (classNameAttr) {
3125
+ const classNameValue = classNameAttr.value;
3126
+ if (existingRaw === null && classNameValue && classNameValue.type === "JSXExpressionContainer") {
3127
+ const exprNode = classNameValue.expression;
3128
+ const exprSource = source.slice(exprNode.start, exprNode.end);
3129
+ edits.overwrite(
3130
+ classNameAttr.start,
3131
+ classNameAttr.end,
3132
+ `className={_szMerge(${exprSource}, ${JSON.stringify(szDerived.join(" "))})}`
3133
+ );
3134
+ for (const szAttr of szAttrs) {
3135
+ const deleteStart = whitespaceStart(source, szAttr.start);
3136
+ edits.remove(deleteStart, szAttr.end);
3137
+ }
3138
+ usesRuntime = true;
3139
+ usesMerge = true;
3140
+ transformed = true;
3141
+ return;
3142
+ }
2902
3143
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2903
3144
  for (const szAttr of szAttrs) {
2904
3145
  const deleteStart = whitespaceStart(source, szAttr.start);
@@ -2917,6 +3158,9 @@ function transformOxc(source, filename, options) {
2917
3158
  }
2918
3159
  transformed = true;
2919
3160
  });
3161
+ for (const c of szsPendingClasses) {
3162
+ classes.add(c);
3163
+ }
2920
3164
  return {
2921
3165
  code: transformed ? edits.toString() : source,
2922
3166
  transformed,
@@ -2986,6 +3230,49 @@ function buildRuntimeFallbackDiagnostic(expression, source) {
2986
3230
  return `sz fallback at ${lineCol}: ${reason}.
2987
3231
  Suggestion: ${suggestion}`;
2988
3232
  }
3233
+ function isHostOpeningElementName(nameNode) {
3234
+ return nameNode.type === "JSXIdentifier" && /^[a-z]/.test(String(nameNode.name));
3235
+ }
3236
+ function szsUnsupportedMessage(filename) {
3237
+ return `[csszyx] szs at ${filename}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
3238
+ }
3239
+ function isPureLiteralSzValue(node) {
3240
+ if (node.type === "Literal") {
3241
+ const value = node.value;
3242
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
3243
+ }
3244
+ if (node.type === "UnaryExpression") {
3245
+ const unary = node;
3246
+ return unary.operator === "-" && unary.argument.type === "Literal" && typeof unary.argument.value === "number";
3247
+ }
3248
+ if (node.type === "ObjectExpression") {
3249
+ const properties = node.properties;
3250
+ return properties.every((propRaw) => {
3251
+ if (propRaw.type !== "Property") {
3252
+ return false;
3253
+ }
3254
+ const prop = propRaw;
3255
+ return !prop.computed && prop.key.type === "Identifier" && isPureLiteralSzValue(prop.value);
3256
+ });
3257
+ }
3258
+ return false;
3259
+ }
3260
+ function isValidSzsSlotMap(slotMap) {
3261
+ return slotMap.properties.every((propRaw) => {
3262
+ if (propRaw.type !== "Property") {
3263
+ return false;
3264
+ }
3265
+ const prop = propRaw;
3266
+ if (prop.computed || prop.key.type !== "Identifier") {
3267
+ return false;
3268
+ }
3269
+ const value = prop.value;
3270
+ if (value.type === "Literal" && typeof value.value === "string") {
3271
+ return true;
3272
+ }
3273
+ return value.type === "ObjectExpression" && isPureLiteralSzValue(value);
3274
+ });
3275
+ }
2989
3276
  function extractElementName(nameNode) {
2990
3277
  if (nameNode.type === "JSXIdentifier") {
2991
3278
  return String(nameNode.name);
@@ -3366,7 +3653,11 @@ function assertAstBudget(root, filename, astBudget) {
3366
3653
  });
3367
3654
  }
3368
3655
  function collectDynamicCallClasses(node, filename, bindings, classes) {
3369
- if (node.callee.type !== "Identifier" || node.callee.name !== "dynamic") {
3656
+ if (node.callee.type !== "Identifier") {
3657
+ return;
3658
+ }
3659
+ const calleeName = node.callee.name;
3660
+ if (calleeName !== "dynamic" && calleeName !== "szr") {
3370
3661
  return;
3371
3662
  }
3372
3663
  const [firstArg] = node.arguments;
@@ -3508,11 +3799,25 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3508
3799
  if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3509
3800
  return null;
3510
3801
  }
3511
- const compileBranch = (pick) => {
3802
+ const condPropIndex = node.properties.findIndex(
3803
+ (prop) => prop.type === "Property" && prop.value.type === "ObjectExpression" && countOxcConditionals(prop.value) === 1
3804
+ );
3805
+ if (condPropIndex === -1) {
3806
+ return null;
3807
+ }
3808
+ const staticNode = {
3809
+ ...node,
3810
+ properties: node.properties.filter((_, i) => i !== condPropIndex)
3811
+ };
3812
+ const condNode = {
3813
+ ...node,
3814
+ properties: [node.properties[condPropIndex]]
3815
+ };
3816
+ const compile = (target, pick) => {
3512
3817
  try {
3513
3818
  return transform(
3514
3819
  applyGlobalVarAliasesToSzObject(
3515
- astObjectToSzObject(node, filename, bindings, pick),
3820
+ astObjectToSzObject(target, filename, bindings, pick),
3516
3821
  globalVarAliases,
3517
3822
  cssVariableMap
3518
3823
  )
@@ -3524,19 +3829,24 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3524
3829
  throw err;
3525
3830
  }
3526
3831
  };
3527
- const consequent = compileBranch("consequent");
3528
- const alternate = compileBranch("alternate");
3529
- if (consequent === null || alternate === null) {
3832
+ const staticClasses = staticNode.properties.length > 0 ? compile(staticNode) : "";
3833
+ const consequent = compile(condNode, "consequent");
3834
+ const alternate = compile(condNode, "alternate");
3835
+ if (staticClasses === null || consequent === null || alternate === null) {
3530
3836
  return null;
3531
3837
  }
3532
- for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3838
+ for (const cls of `${staticClasses} ${consequent} ${alternate}`.split(/\s+/)) {
3533
3839
  if (cls) {
3534
3840
  classes.add(cls);
3535
3841
  }
3536
3842
  }
3537
3843
  const testSource = source.slice(first.test.start, first.test.end);
3538
3844
  const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3539
- return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3845
+ const ternary = `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3846
+ if (staticClasses === "") {
3847
+ return ternary;
3848
+ }
3849
+ return `\`${staticClasses} \${${ternary}}\``;
3540
3850
  }
3541
3851
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3542
3852
  let conditionalSpread = null;
@@ -4323,6 +4633,7 @@ function extractKeyName(key) {
4323
4633
  return null;
4324
4634
  }
4325
4635
  function astValueToSzValue(node, filename, bindings, branchPick) {
4636
+ node = unwrapExpression(node);
4326
4637
  if (branchPick && node.type === "ConditionalExpression") {
4327
4638
  return astValueToSzValue(
4328
4639
  node[branchPick],
@@ -1186,6 +1186,8 @@ function handleImportant(value) {
1186
1186
  }
1187
1187
  return { value, important: false };
1188
1188
  }
1189
+ const ALPHA_SAFE_NAMED_COLORS = /* @__PURE__ */ new Set(["white", "black", "transparent", "current", "inherit"]);
1190
+ const _warnedOpacityTokens = /* @__PURE__ */ new Set();
1189
1191
  function formatOpacity(op) {
1190
1192
  if (typeof op === "number") {
1191
1193
  if (Number.isInteger(op * 2)) {
@@ -1649,6 +1651,13 @@ function transformImpl(szProp, prefix, mangleMap) {
1649
1651
  const colorBase = rawColorBase.startsWith("--") ? `(${rawColorBase})` : needsArbitraryBrackets(rawColorBase) ? `[${normalizeArbitraryValue(rawColorBase)}]` : normalizeArbitraryValue(rawColorBase);
1650
1652
  if (colorObj.op !== void 0) {
1651
1653
  const opStr = formatOpacity(colorObj.op);
1654
+ if (process.env.NODE_ENV !== "production" && typeof window === "undefined" && !rawColorBase.startsWith("--") && !needsArbitraryBrackets(rawColorBase) && !/-\d{2,3}$/.test(rawColorBase) && !ALPHA_SAFE_NAMED_COLORS.has(rawColorBase) && !_warnedOpacityTokens.has(rawColorBase)) {
1655
+ _warnedOpacityTokens.add(rawColorBase);
1656
+ const at = szWarnLocation ? ` at ${szWarnLocation}` : "";
1657
+ console.warn(
1658
+ `[csszyx] "${prefix}${twPrefix}-${colorBase}/${opStr}"${at}: the /${opStr} opacity applies only if the "${rawColorBase}" theme token is alpha-capable (oklch or space-separated RGB). A comma-separated RGB triplet, or a token that resolves through its own alpha variable, silently ignores the modifier \u2014 verify the emitted rule.`
1659
+ );
1660
+ }
1652
1661
  classes.push(`${prefix}${twPrefix}-${colorBase}/${opStr}`);
1653
1662
  } else {
1654
1663
  classes.push(`${prefix}${twPrefix}-${colorBase}`);
@@ -2385,6 +2394,9 @@ function transformImpl(szProp, prefix, mangleMap) {
2385
2394
  hintProjectScanOnce(szWarnLocation);
2386
2395
  }
2387
2396
  }
2397
+ if (/^\d+(?:\.\d+)?$/.test(rawKey)) {
2398
+ continue;
2399
+ }
2388
2400
  if (value === true) {
2389
2401
  if (BOOLEAN_SHORTHANDS.has(rawKey)) {
2390
2402
  const mappedClass = BOOLEAN_TO_CLASS[rawKey] || key;
@@ -1184,6 +1184,8 @@ function handleImportant(value) {
1184
1184
  }
1185
1185
  return { value, important: false };
1186
1186
  }
1187
+ const ALPHA_SAFE_NAMED_COLORS = /* @__PURE__ */ new Set(["white", "black", "transparent", "current", "inherit"]);
1188
+ const _warnedOpacityTokens = /* @__PURE__ */ new Set();
1187
1189
  function formatOpacity(op) {
1188
1190
  if (typeof op === "number") {
1189
1191
  if (Number.isInteger(op * 2)) {
@@ -1647,6 +1649,13 @@ function transformImpl(szProp, prefix, mangleMap) {
1647
1649
  const colorBase = rawColorBase.startsWith("--") ? `(${rawColorBase})` : needsArbitraryBrackets(rawColorBase) ? `[${normalizeArbitraryValue(rawColorBase)}]` : normalizeArbitraryValue(rawColorBase);
1648
1650
  if (colorObj.op !== void 0) {
1649
1651
  const opStr = formatOpacity(colorObj.op);
1652
+ if (process.env.NODE_ENV !== "production" && typeof window === "undefined" && !rawColorBase.startsWith("--") && !needsArbitraryBrackets(rawColorBase) && !/-\d{2,3}$/.test(rawColorBase) && !ALPHA_SAFE_NAMED_COLORS.has(rawColorBase) && !_warnedOpacityTokens.has(rawColorBase)) {
1653
+ _warnedOpacityTokens.add(rawColorBase);
1654
+ const at = szWarnLocation ? ` at ${szWarnLocation}` : "";
1655
+ console.warn(
1656
+ `[csszyx] "${prefix}${twPrefix}-${colorBase}/${opStr}"${at}: the /${opStr} opacity applies only if the "${rawColorBase}" theme token is alpha-capable (oklch or space-separated RGB). A comma-separated RGB triplet, or a token that resolves through its own alpha variable, silently ignores the modifier \u2014 verify the emitted rule.`
1657
+ );
1658
+ }
1650
1659
  classes.push(`${prefix}${twPrefix}-${colorBase}/${opStr}`);
1651
1660
  } else {
1652
1661
  classes.push(`${prefix}${twPrefix}-${colorBase}`);
@@ -2383,6 +2392,9 @@ function transformImpl(szProp, prefix, mangleMap) {
2383
2392
  hintProjectScanOnce(szWarnLocation);
2384
2393
  }
2385
2394
  }
2395
+ if (/^\d+(?:\.\d+)?$/.test(rawKey)) {
2396
+ continue;
2397
+ }
2386
2398
  if (value === true) {
2387
2399
  if (BOOLEAN_SHORTHANDS.has(rawKey)) {
2388
2400
  const mappedClass = BOOLEAN_TO_CLASS[rawKey] || key;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const transformCore = require('./shared/compiler.mibv6qPF.cjs');
3
+ const transformCore = require('./shared/compiler.dkTeNO_S.cjs');
4
4
 
5
5
 
6
6
 
@@ -1 +1 @@
1
- export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, j as SzDepthError, V as VARIANT_MAP, f as formatSzWarnLocation, b as getVariantPrefix, k as isForbiddenSzKey, i as isValidSzProp, l as normalizeArbitraryValue, m as normalizeArbitraryVariant, n as normalizeClassName, s as setSzWarnLocation, t as transform } from './shared/compiler.CghwJ6p5.mjs';
1
+ export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, j as SzDepthError, V as VARIANT_MAP, f as formatSzWarnLocation, b as getVariantPrefix, k as isForbiddenSzKey, i as isValidSzProp, l as normalizeArbitraryValue, m as normalizeArbitraryVariant, n as normalizeClassName, s as setSzWarnLocation, t as transform } from './shared/compiler.zZfo8y65.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/compiler",
3
- "version": "0.10.9",
3
+ "version": "0.10.11",
4
4
  "description": "Core compiler and transformation logic for csszyx",
5
5
  "keywords": [
6
6
  "csszyx",
@@ -65,7 +65,7 @@
65
65
  "@babel/types": "^7.23.6",
66
66
  "magic-string": "0.30.21",
67
67
  "oxc-parser": "0.131.0",
68
- "@csszyx/core": "0.10.9"
68
+ "@csszyx/core": "0.10.11"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__core": "^7.20.5",