@csszyx/compiler 0.10.9 → 0.10.10

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
@@ -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
  }
@@ -789,6 +854,9 @@ function transformSourceCode(source, filename, options) {
789
854
  })
790
855
  ]
791
856
  });
857
+ for (const c of szsPendingClasses) {
858
+ collectedClasses.add(c);
859
+ }
792
860
  return {
793
861
  code: result?.code || source,
794
862
  transformed,
@@ -840,6 +908,31 @@ function parseStyleStringToObjectExpr(styleStr) {
840
908
  }
841
909
  return t__namespace.objectExpression(objProps);
842
910
  }
911
+ function isHostElementName(name) {
912
+ return t__namespace.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
913
+ }
914
+ function szsUnsupportedMessage$1(filename) {
915
+ 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.`;
916
+ }
917
+ function isPureLiteralSzValue$1(node) {
918
+ if (t__namespace.isStringLiteral(node) || t__namespace.isNumericLiteral(node) || t__namespace.isBooleanLiteral(node)) {
919
+ return true;
920
+ }
921
+ if (t__namespace.isUnaryExpression(node) && node.operator === "-" && t__namespace.isNumericLiteral(node.argument)) {
922
+ return true;
923
+ }
924
+ if (t__namespace.isObjectExpression(node)) {
925
+ return node.properties.every(
926
+ (prop) => t__namespace.isObjectProperty(prop) && !prop.computed && t__namespace.isIdentifier(prop.key) && isPureLiteralSzValue$1(prop.value)
927
+ );
928
+ }
929
+ return false;
930
+ }
931
+ function isValidSzsSlotMap$1(slotMap) {
932
+ return slotMap.properties.every(
933
+ (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))
934
+ );
935
+ }
843
936
  function emptyClassToUndefined(node) {
844
937
  return t__namespace.isStringLiteral(node) && node.value === "" ? t__namespace.identifier("undefined") : node;
845
938
  }
@@ -967,22 +1060,44 @@ function tryHoistNestedConditional(node, getBinding) {
967
1060
  if (topLevel !== 0 || nested !== 1 || test === null) {
968
1061
  return null;
969
1062
  }
1063
+ const condPropIndex = node.properties.findIndex(
1064
+ (prop) => t__namespace.isObjectProperty(prop) && t__namespace.isObjectExpression(prop.value) && countAllConditionals(prop.value) === 1
1065
+ );
1066
+ if (condPropIndex === -1) {
1067
+ return null;
1068
+ }
1069
+ const staticNode = t__namespace.objectExpression(node.properties.filter((_, i) => i !== condPropIndex));
1070
+ const condNode = t__namespace.objectExpression([node.properties[condPropIndex]]);
1071
+ const staticClasses = staticNode.properties.length > 0 ? tryStaticTransformNode(staticNode, getBinding) : null;
970
1072
  const consequent = tryStaticTransformNode(
971
- cloneObjectPickingBranch(node, "consequent"),
1073
+ cloneObjectPickingBranch(condNode, "consequent"),
972
1074
  getBinding
973
1075
  );
974
1076
  const alternate = tryStaticTransformNode(
975
- cloneObjectPickingBranch(node, "alternate"),
1077
+ cloneObjectPickingBranch(condNode, "alternate"),
976
1078
  getBinding
977
1079
  );
978
- if (!consequent || !alternate || !t__namespace.isStringLiteral(consequent) || !t__namespace.isStringLiteral(alternate)) {
1080
+ if (!consequent || !alternate || !t__namespace.isStringLiteral(consequent) || !t__namespace.isStringLiteral(alternate) || staticNode.properties.length > 0 && (!staticClasses || !t__namespace.isStringLiteral(staticClasses))) {
979
1081
  return null;
980
1082
  }
981
- return t__namespace.conditionalExpression(
1083
+ const ternary = t__namespace.conditionalExpression(
982
1084
  test,
983
1085
  emptyClassToUndefined(consequent),
984
1086
  emptyClassToUndefined(alternate)
985
1087
  );
1088
+ if (!staticClasses || !t__namespace.isStringLiteral(staticClasses) || staticClasses.value === "") {
1089
+ return ternary;
1090
+ }
1091
+ return t__namespace.templateLiteral(
1092
+ [
1093
+ t__namespace.templateElement(
1094
+ { raw: `${staticClasses.value} `, cooked: `${staticClasses.value} ` },
1095
+ false
1096
+ ),
1097
+ t__namespace.templateElement({ raw: "", cooked: "" }, true)
1098
+ ],
1099
+ [ternary]
1100
+ );
986
1101
  }
987
1102
  function tryHoistConditionalSpread(node, getBinding) {
988
1103
  let conditionalSpreadIdx = -1;
@@ -1392,6 +1507,18 @@ function collectFromExpr(node, classes) {
1392
1507
  } else if (t__namespace.isConditionalExpression(node)) {
1393
1508
  collectFromExpr(node.consequent, classes);
1394
1509
  collectFromExpr(node.alternate, classes);
1510
+ } else if (t__namespace.isTemplateLiteral(node)) {
1511
+ for (let i = 0; i < node.quasis.length; i++) {
1512
+ for (const c of (node.quasis[i].value.cooked ?? "").split(/\s+/)) {
1513
+ if (c) {
1514
+ classes.add(c);
1515
+ }
1516
+ }
1517
+ const expr = node.expressions[i];
1518
+ if (expr && t__namespace.isExpression(expr)) {
1519
+ collectFromExpr(expr, classes);
1520
+ }
1521
+ }
1395
1522
  }
1396
1523
  }
1397
1524
  function collectCandidatesFromBabelExpr(node, path, classes) {
@@ -2405,6 +2532,7 @@ class OxcNotImplementedError extends Error {
2405
2532
  }
2406
2533
  function transformOxc(source, filename, options) {
2407
2534
  const classes = /* @__PURE__ */ new Set();
2535
+ const szsPendingClasses = [];
2408
2536
  const rawClassNames = /* @__PURE__ */ new Set();
2409
2537
  const diagnostics = [];
2410
2538
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -2476,6 +2604,7 @@ function transformOxc(source, filename, options) {
2476
2604
  const openingNode = node;
2477
2605
  const attrs = openingNode.attributes ?? [];
2478
2606
  const szAttrs = [];
2607
+ const szsAttrs = [];
2479
2608
  let classNameAttr = null;
2480
2609
  let styleAttr = null;
2481
2610
  let szRecoverAttr = null;
@@ -2508,6 +2637,8 @@ function transformOxc(source, filename, options) {
2508
2637
  const name = attr.name?.name;
2509
2638
  if (name === "sz") {
2510
2639
  szAttrs.push(attr);
2640
+ } else if (name === "szs") {
2641
+ szsAttrs.push(attr);
2511
2642
  } else if (name === "className" || name === "class") {
2512
2643
  classNameAttr = attr;
2513
2644
  } else if (name === "style") {
@@ -2558,6 +2689,89 @@ function transformOxc(source, filename, options) {
2558
2689
  }
2559
2690
  }
2560
2691
  }
2692
+ for (const szsAttr of szsAttrs) {
2693
+ if (isHostOpeningElementName(openingNode.name)) {
2694
+ diagnostics.push(
2695
+ `[csszyx] szs at ${effectiveFilename}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
2696
+ );
2697
+ continue;
2698
+ }
2699
+ const szsValue = szsAttr.value;
2700
+ const szsExpression = szsValue && szsValue.type === "JSXExpressionContainer" ? szsValue.expression : null;
2701
+ if (!szsExpression || szsExpression.type !== "ObjectExpression") {
2702
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2703
+ continue;
2704
+ }
2705
+ const slotMap = szsExpression;
2706
+ if (!isValidSzsSlotMap(slotMap)) {
2707
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2708
+ continue;
2709
+ }
2710
+ const { line: szsWarnLine } = offsetToLineColumn(source, szsAttr.start);
2711
+ transformCore.setSzWarnLocation(
2712
+ transformCore.formatSzWarnLocation(effectiveFilename, szsWarnLine, options?.rootDir)
2713
+ );
2714
+ const slotEntries = [];
2715
+ let anyCompiled = false;
2716
+ let slotFailed = false;
2717
+ for (const propRaw of slotMap.properties) {
2718
+ const prop = propRaw;
2719
+ const keyText = source.slice(prop.key.start, prop.key.end);
2720
+ const propValue = prop.value;
2721
+ const literal = propValue.type === "Literal" ? propValue.value : null;
2722
+ if (typeof literal === "string") {
2723
+ slotEntries.push({
2724
+ keyText,
2725
+ classNames: literal,
2726
+ text: source.slice(propValue.start, propValue.end)
2727
+ });
2728
+ continue;
2729
+ }
2730
+ try {
2731
+ const slotObject = astObjectToSzObject(
2732
+ propValue,
2733
+ effectiveFilename,
2734
+ objectBindings
2735
+ );
2736
+ const compiled = transformCore.transform(
2737
+ applyGlobalVarAliasesToSzObject(
2738
+ slotObject,
2739
+ globalVarAliases,
2740
+ cssVariableMap
2741
+ )
2742
+ ).className;
2743
+ slotEntries.push({
2744
+ keyText,
2745
+ classNames: compiled,
2746
+ text: JSON.stringify(compiled)
2747
+ });
2748
+ anyCompiled = true;
2749
+ } catch (err) {
2750
+ if (err instanceof OxcNotImplementedError) {
2751
+ slotFailed = true;
2752
+ break;
2753
+ }
2754
+ throw err;
2755
+ }
2756
+ }
2757
+ transformCore.setSzWarnLocation(void 0);
2758
+ if (slotFailed) {
2759
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2760
+ continue;
2761
+ }
2762
+ if (anyCompiled) {
2763
+ const body = slotEntries.map((entry) => `${entry.keyText}: ${entry.text}`).join(", ");
2764
+ edits.overwrite(szsAttr.start, szsAttr.end, `szs={{ ${body} }}`);
2765
+ transformed = true;
2766
+ }
2767
+ for (const entry of slotEntries) {
2768
+ for (const c of entry.classNames.split(/\s+/)) {
2769
+ if (c) {
2770
+ szsPendingClasses.push(c);
2771
+ }
2772
+ }
2773
+ }
2774
+ }
2561
2775
  if (szAttrs.length === 0) {
2562
2776
  applyHoistedStyleProps();
2563
2777
  return;
@@ -2936,6 +3150,9 @@ function transformOxc(source, filename, options) {
2936
3150
  }
2937
3151
  transformed = true;
2938
3152
  });
3153
+ for (const c of szsPendingClasses) {
3154
+ classes.add(c);
3155
+ }
2939
3156
  return {
2940
3157
  code: transformed ? edits.toString() : source,
2941
3158
  transformed,
@@ -3005,6 +3222,49 @@ function buildRuntimeFallbackDiagnostic(expression, source) {
3005
3222
  return `sz fallback at ${lineCol}: ${reason}.
3006
3223
  Suggestion: ${suggestion}`;
3007
3224
  }
3225
+ function isHostOpeningElementName(nameNode) {
3226
+ return nameNode.type === "JSXIdentifier" && /^[a-z]/.test(String(nameNode.name));
3227
+ }
3228
+ function szsUnsupportedMessage(filename) {
3229
+ return `[csszyx] szs at ${filename}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
3230
+ }
3231
+ function isPureLiteralSzValue(node) {
3232
+ if (node.type === "Literal") {
3233
+ const value = node.value;
3234
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
3235
+ }
3236
+ if (node.type === "UnaryExpression") {
3237
+ const unary = node;
3238
+ return unary.operator === "-" && unary.argument.type === "Literal" && typeof unary.argument.value === "number";
3239
+ }
3240
+ if (node.type === "ObjectExpression") {
3241
+ const properties = node.properties;
3242
+ return properties.every((propRaw) => {
3243
+ if (propRaw.type !== "Property") {
3244
+ return false;
3245
+ }
3246
+ const prop = propRaw;
3247
+ return !prop.computed && prop.key.type === "Identifier" && isPureLiteralSzValue(prop.value);
3248
+ });
3249
+ }
3250
+ return false;
3251
+ }
3252
+ function isValidSzsSlotMap(slotMap) {
3253
+ return slotMap.properties.every((propRaw) => {
3254
+ if (propRaw.type !== "Property") {
3255
+ return false;
3256
+ }
3257
+ const prop = propRaw;
3258
+ if (prop.computed || prop.key.type !== "Identifier") {
3259
+ return false;
3260
+ }
3261
+ const value = prop.value;
3262
+ if (value.type === "Literal" && typeof value.value === "string") {
3263
+ return true;
3264
+ }
3265
+ return value.type === "ObjectExpression" && isPureLiteralSzValue(value);
3266
+ });
3267
+ }
3008
3268
  function extractElementName(nameNode) {
3009
3269
  if (nameNode.type === "JSXIdentifier") {
3010
3270
  return String(nameNode.name);
@@ -3527,11 +3787,25 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3527
3787
  if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3528
3788
  return null;
3529
3789
  }
3530
- const compileBranch = (pick) => {
3790
+ const condPropIndex = node.properties.findIndex(
3791
+ (prop) => prop.type === "Property" && prop.value.type === "ObjectExpression" && countOxcConditionals(prop.value) === 1
3792
+ );
3793
+ if (condPropIndex === -1) {
3794
+ return null;
3795
+ }
3796
+ const staticNode = {
3797
+ ...node,
3798
+ properties: node.properties.filter((_, i) => i !== condPropIndex)
3799
+ };
3800
+ const condNode = {
3801
+ ...node,
3802
+ properties: [node.properties[condPropIndex]]
3803
+ };
3804
+ const compile = (target, pick) => {
3531
3805
  try {
3532
3806
  return transformCore.transform(
3533
3807
  applyGlobalVarAliasesToSzObject(
3534
- astObjectToSzObject(node, filename, bindings, pick),
3808
+ astObjectToSzObject(target, filename, bindings, pick),
3535
3809
  globalVarAliases,
3536
3810
  cssVariableMap
3537
3811
  )
@@ -3543,19 +3817,24 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3543
3817
  throw err;
3544
3818
  }
3545
3819
  };
3546
- const consequent = compileBranch("consequent");
3547
- const alternate = compileBranch("alternate");
3548
- if (consequent === null || alternate === null) {
3820
+ const staticClasses = staticNode.properties.length > 0 ? compile(staticNode) : "";
3821
+ const consequent = compile(condNode, "consequent");
3822
+ const alternate = compile(condNode, "alternate");
3823
+ if (staticClasses === null || consequent === null || alternate === null) {
3549
3824
  return null;
3550
3825
  }
3551
- for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3826
+ for (const cls of `${staticClasses} ${consequent} ${alternate}`.split(/\s+/)) {
3552
3827
  if (cls) {
3553
3828
  classes.add(cls);
3554
3829
  }
3555
3830
  }
3556
3831
  const testSource = source.slice(first.test.start, first.test.end);
3557
3832
  const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3558
- return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3833
+ const ternary = `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3834
+ if (staticClasses === "") {
3835
+ return ternary;
3836
+ }
3837
+ return `\`${staticClasses} \${${ternary}}\``;
3559
3838
  }
3560
3839
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3561
3840
  let conditionalSpread = null;
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
@@ -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
  }
@@ -770,6 +835,9 @@ function transformSourceCode(source, filename, options) {
770
835
  })
771
836
  ]
772
837
  });
838
+ for (const c of szsPendingClasses) {
839
+ collectedClasses.add(c);
840
+ }
773
841
  return {
774
842
  code: result?.code || source,
775
843
  transformed,
@@ -821,6 +889,31 @@ function parseStyleStringToObjectExpr(styleStr) {
821
889
  }
822
890
  return t.objectExpression(objProps);
823
891
  }
892
+ function isHostElementName(name) {
893
+ return t.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
894
+ }
895
+ function szsUnsupportedMessage$1(filename) {
896
+ 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.`;
897
+ }
898
+ function isPureLiteralSzValue$1(node) {
899
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node)) {
900
+ return true;
901
+ }
902
+ if (t.isUnaryExpression(node) && node.operator === "-" && t.isNumericLiteral(node.argument)) {
903
+ return true;
904
+ }
905
+ if (t.isObjectExpression(node)) {
906
+ return node.properties.every(
907
+ (prop) => t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.key) && isPureLiteralSzValue$1(prop.value)
908
+ );
909
+ }
910
+ return false;
911
+ }
912
+ function isValidSzsSlotMap$1(slotMap) {
913
+ return slotMap.properties.every(
914
+ (prop) => t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.key) && (t.isStringLiteral(prop.value) || t.isObjectExpression(prop.value) && isPureLiteralSzValue$1(prop.value))
915
+ );
916
+ }
824
917
  function emptyClassToUndefined(node) {
825
918
  return t.isStringLiteral(node) && node.value === "" ? t.identifier("undefined") : node;
826
919
  }
@@ -948,22 +1041,44 @@ function tryHoistNestedConditional(node, getBinding) {
948
1041
  if (topLevel !== 0 || nested !== 1 || test === null) {
949
1042
  return null;
950
1043
  }
1044
+ const condPropIndex = node.properties.findIndex(
1045
+ (prop) => t.isObjectProperty(prop) && t.isObjectExpression(prop.value) && countAllConditionals(prop.value) === 1
1046
+ );
1047
+ if (condPropIndex === -1) {
1048
+ return null;
1049
+ }
1050
+ const staticNode = t.objectExpression(node.properties.filter((_, i) => i !== condPropIndex));
1051
+ const condNode = t.objectExpression([node.properties[condPropIndex]]);
1052
+ const staticClasses = staticNode.properties.length > 0 ? tryStaticTransformNode(staticNode, getBinding) : null;
951
1053
  const consequent = tryStaticTransformNode(
952
- cloneObjectPickingBranch(node, "consequent"),
1054
+ cloneObjectPickingBranch(condNode, "consequent"),
953
1055
  getBinding
954
1056
  );
955
1057
  const alternate = tryStaticTransformNode(
956
- cloneObjectPickingBranch(node, "alternate"),
1058
+ cloneObjectPickingBranch(condNode, "alternate"),
957
1059
  getBinding
958
1060
  );
959
- if (!consequent || !alternate || !t.isStringLiteral(consequent) || !t.isStringLiteral(alternate)) {
1061
+ if (!consequent || !alternate || !t.isStringLiteral(consequent) || !t.isStringLiteral(alternate) || staticNode.properties.length > 0 && (!staticClasses || !t.isStringLiteral(staticClasses))) {
960
1062
  return null;
961
1063
  }
962
- return t.conditionalExpression(
1064
+ const ternary = t.conditionalExpression(
963
1065
  test,
964
1066
  emptyClassToUndefined(consequent),
965
1067
  emptyClassToUndefined(alternate)
966
1068
  );
1069
+ if (!staticClasses || !t.isStringLiteral(staticClasses) || staticClasses.value === "") {
1070
+ return ternary;
1071
+ }
1072
+ return t.templateLiteral(
1073
+ [
1074
+ t.templateElement(
1075
+ { raw: `${staticClasses.value} `, cooked: `${staticClasses.value} ` },
1076
+ false
1077
+ ),
1078
+ t.templateElement({ raw: "", cooked: "" }, true)
1079
+ ],
1080
+ [ternary]
1081
+ );
967
1082
  }
968
1083
  function tryHoistConditionalSpread(node, getBinding) {
969
1084
  let conditionalSpreadIdx = -1;
@@ -1373,6 +1488,18 @@ function collectFromExpr(node, classes) {
1373
1488
  } else if (t.isConditionalExpression(node)) {
1374
1489
  collectFromExpr(node.consequent, classes);
1375
1490
  collectFromExpr(node.alternate, classes);
1491
+ } else if (t.isTemplateLiteral(node)) {
1492
+ for (let i = 0; i < node.quasis.length; i++) {
1493
+ for (const c of (node.quasis[i].value.cooked ?? "").split(/\s+/)) {
1494
+ if (c) {
1495
+ classes.add(c);
1496
+ }
1497
+ }
1498
+ const expr = node.expressions[i];
1499
+ if (expr && t.isExpression(expr)) {
1500
+ collectFromExpr(expr, classes);
1501
+ }
1502
+ }
1376
1503
  }
1377
1504
  }
1378
1505
  function collectCandidatesFromBabelExpr(node, path, classes) {
@@ -2386,6 +2513,7 @@ class OxcNotImplementedError extends Error {
2386
2513
  }
2387
2514
  function transformOxc(source, filename, options) {
2388
2515
  const classes = /* @__PURE__ */ new Set();
2516
+ const szsPendingClasses = [];
2389
2517
  const rawClassNames = /* @__PURE__ */ new Set();
2390
2518
  const diagnostics = [];
2391
2519
  const recoveryTokens = /* @__PURE__ */ new Map();
@@ -2457,6 +2585,7 @@ function transformOxc(source, filename, options) {
2457
2585
  const openingNode = node;
2458
2586
  const attrs = openingNode.attributes ?? [];
2459
2587
  const szAttrs = [];
2588
+ const szsAttrs = [];
2460
2589
  let classNameAttr = null;
2461
2590
  let styleAttr = null;
2462
2591
  let szRecoverAttr = null;
@@ -2489,6 +2618,8 @@ function transformOxc(source, filename, options) {
2489
2618
  const name = attr.name?.name;
2490
2619
  if (name === "sz") {
2491
2620
  szAttrs.push(attr);
2621
+ } else if (name === "szs") {
2622
+ szsAttrs.push(attr);
2492
2623
  } else if (name === "className" || name === "class") {
2493
2624
  classNameAttr = attr;
2494
2625
  } else if (name === "style") {
@@ -2539,6 +2670,89 @@ function transformOxc(source, filename, options) {
2539
2670
  }
2540
2671
  }
2541
2672
  }
2673
+ for (const szsAttr of szsAttrs) {
2674
+ if (isHostOpeningElementName(openingNode.name)) {
2675
+ diagnostics.push(
2676
+ `[csszyx] szs at ${effectiveFilename}: szs has no effect on a host element \u2014 it maps slot names of a custom component. Attribute left unchanged.`
2677
+ );
2678
+ continue;
2679
+ }
2680
+ const szsValue = szsAttr.value;
2681
+ const szsExpression = szsValue && szsValue.type === "JSXExpressionContainer" ? szsValue.expression : null;
2682
+ if (!szsExpression || szsExpression.type !== "ObjectExpression") {
2683
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2684
+ continue;
2685
+ }
2686
+ const slotMap = szsExpression;
2687
+ if (!isValidSzsSlotMap(slotMap)) {
2688
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2689
+ continue;
2690
+ }
2691
+ const { line: szsWarnLine } = offsetToLineColumn(source, szsAttr.start);
2692
+ setSzWarnLocation(
2693
+ formatSzWarnLocation(effectiveFilename, szsWarnLine, options?.rootDir)
2694
+ );
2695
+ const slotEntries = [];
2696
+ let anyCompiled = false;
2697
+ let slotFailed = false;
2698
+ for (const propRaw of slotMap.properties) {
2699
+ const prop = propRaw;
2700
+ const keyText = source.slice(prop.key.start, prop.key.end);
2701
+ const propValue = prop.value;
2702
+ const literal = propValue.type === "Literal" ? propValue.value : null;
2703
+ if (typeof literal === "string") {
2704
+ slotEntries.push({
2705
+ keyText,
2706
+ classNames: literal,
2707
+ text: source.slice(propValue.start, propValue.end)
2708
+ });
2709
+ continue;
2710
+ }
2711
+ try {
2712
+ const slotObject = astObjectToSzObject(
2713
+ propValue,
2714
+ effectiveFilename,
2715
+ objectBindings
2716
+ );
2717
+ const compiled = transform(
2718
+ applyGlobalVarAliasesToSzObject(
2719
+ slotObject,
2720
+ globalVarAliases,
2721
+ cssVariableMap
2722
+ )
2723
+ ).className;
2724
+ slotEntries.push({
2725
+ keyText,
2726
+ classNames: compiled,
2727
+ text: JSON.stringify(compiled)
2728
+ });
2729
+ anyCompiled = true;
2730
+ } catch (err) {
2731
+ if (err instanceof OxcNotImplementedError) {
2732
+ slotFailed = true;
2733
+ break;
2734
+ }
2735
+ throw err;
2736
+ }
2737
+ }
2738
+ setSzWarnLocation(void 0);
2739
+ if (slotFailed) {
2740
+ diagnostics.push(szsUnsupportedMessage(effectiveFilename));
2741
+ continue;
2742
+ }
2743
+ if (anyCompiled) {
2744
+ const body = slotEntries.map((entry) => `${entry.keyText}: ${entry.text}`).join(", ");
2745
+ edits.overwrite(szsAttr.start, szsAttr.end, `szs={{ ${body} }}`);
2746
+ transformed = true;
2747
+ }
2748
+ for (const entry of slotEntries) {
2749
+ for (const c of entry.classNames.split(/\s+/)) {
2750
+ if (c) {
2751
+ szsPendingClasses.push(c);
2752
+ }
2753
+ }
2754
+ }
2755
+ }
2542
2756
  if (szAttrs.length === 0) {
2543
2757
  applyHoistedStyleProps();
2544
2758
  return;
@@ -2917,6 +3131,9 @@ function transformOxc(source, filename, options) {
2917
3131
  }
2918
3132
  transformed = true;
2919
3133
  });
3134
+ for (const c of szsPendingClasses) {
3135
+ classes.add(c);
3136
+ }
2920
3137
  return {
2921
3138
  code: transformed ? edits.toString() : source,
2922
3139
  transformed,
@@ -2986,6 +3203,49 @@ function buildRuntimeFallbackDiagnostic(expression, source) {
2986
3203
  return `sz fallback at ${lineCol}: ${reason}.
2987
3204
  Suggestion: ${suggestion}`;
2988
3205
  }
3206
+ function isHostOpeningElementName(nameNode) {
3207
+ return nameNode.type === "JSXIdentifier" && /^[a-z]/.test(String(nameNode.name));
3208
+ }
3209
+ function szsUnsupportedMessage(filename) {
3210
+ return `[csszyx] szs at ${filename}: every slot must be an identifier key with a static object literal (or class string) value. Attribute left unchanged.`;
3211
+ }
3212
+ function isPureLiteralSzValue(node) {
3213
+ if (node.type === "Literal") {
3214
+ const value = node.value;
3215
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
3216
+ }
3217
+ if (node.type === "UnaryExpression") {
3218
+ const unary = node;
3219
+ return unary.operator === "-" && unary.argument.type === "Literal" && typeof unary.argument.value === "number";
3220
+ }
3221
+ if (node.type === "ObjectExpression") {
3222
+ const properties = node.properties;
3223
+ return properties.every((propRaw) => {
3224
+ if (propRaw.type !== "Property") {
3225
+ return false;
3226
+ }
3227
+ const prop = propRaw;
3228
+ return !prop.computed && prop.key.type === "Identifier" && isPureLiteralSzValue(prop.value);
3229
+ });
3230
+ }
3231
+ return false;
3232
+ }
3233
+ function isValidSzsSlotMap(slotMap) {
3234
+ return slotMap.properties.every((propRaw) => {
3235
+ if (propRaw.type !== "Property") {
3236
+ return false;
3237
+ }
3238
+ const prop = propRaw;
3239
+ if (prop.computed || prop.key.type !== "Identifier") {
3240
+ return false;
3241
+ }
3242
+ const value = prop.value;
3243
+ if (value.type === "Literal" && typeof value.value === "string") {
3244
+ return true;
3245
+ }
3246
+ return value.type === "ObjectExpression" && isPureLiteralSzValue(value);
3247
+ });
3248
+ }
2989
3249
  function extractElementName(nameNode) {
2990
3250
  if (nameNode.type === "JSXIdentifier") {
2991
3251
  return String(nameNode.name);
@@ -3508,11 +3768,25 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3508
3768
  if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3509
3769
  return null;
3510
3770
  }
3511
- const compileBranch = (pick) => {
3771
+ const condPropIndex = node.properties.findIndex(
3772
+ (prop) => prop.type === "Property" && prop.value.type === "ObjectExpression" && countOxcConditionals(prop.value) === 1
3773
+ );
3774
+ if (condPropIndex === -1) {
3775
+ return null;
3776
+ }
3777
+ const staticNode = {
3778
+ ...node,
3779
+ properties: node.properties.filter((_, i) => i !== condPropIndex)
3780
+ };
3781
+ const condNode = {
3782
+ ...node,
3783
+ properties: [node.properties[condPropIndex]]
3784
+ };
3785
+ const compile = (target, pick) => {
3512
3786
  try {
3513
3787
  return transform(
3514
3788
  applyGlobalVarAliasesToSzObject(
3515
- astObjectToSzObject(node, filename, bindings, pick),
3789
+ astObjectToSzObject(target, filename, bindings, pick),
3516
3790
  globalVarAliases,
3517
3791
  cssVariableMap
3518
3792
  )
@@ -3524,19 +3798,24 @@ function buildNestedConditionalClassExpression(node, filename, bindings, source,
3524
3798
  throw err;
3525
3799
  }
3526
3800
  };
3527
- const consequent = compileBranch("consequent");
3528
- const alternate = compileBranch("alternate");
3529
- if (consequent === null || alternate === null) {
3801
+ const staticClasses = staticNode.properties.length > 0 ? compile(staticNode) : "";
3802
+ const consequent = compile(condNode, "consequent");
3803
+ const alternate = compile(condNode, "alternate");
3804
+ if (staticClasses === null || consequent === null || alternate === null) {
3530
3805
  return null;
3531
3806
  }
3532
- for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3807
+ for (const cls of `${staticClasses} ${consequent} ${alternate}`.split(/\s+/)) {
3533
3808
  if (cls) {
3534
3809
  classes.add(cls);
3535
3810
  }
3536
3811
  }
3537
3812
  const testSource = source.slice(first.test.start, first.test.end);
3538
3813
  const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3539
- return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3814
+ const ternary = `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3815
+ if (staticClasses === "") {
3816
+ return ternary;
3817
+ }
3818
+ return `\`${staticClasses} \${${ternary}}\``;
3540
3819
  }
3541
3820
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3542
3821
  let conditionalSpread = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/compiler",
3
- "version": "0.10.9",
3
+ "version": "0.10.10",
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.10"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__core": "^7.20.5",