styled-components-to-stylex-codemod 0.0.26 → 0.0.28

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.
Files changed (2) hide show
  1. package/dist/transform.mjs +623 -285
  2. package/package.json +1 -1
@@ -591,6 +591,51 @@ function literalToString(node) {
591
591
  return typeof v === "string" ? v : null;
592
592
  }
593
593
  /**
594
+ * Scans the file AST for TypeScript string/numeric enum declarations and
595
+ * returns a two-level map: enumName -> memberName -> static value.
596
+ * Only handles members with literal initializers.
597
+ */
598
+ function buildEnumValueMap(root, j) {
599
+ const map = /* @__PURE__ */ new Map();
600
+ root.find(j.TSEnumDeclaration).forEach((path) => {
601
+ const enumNode = path.value;
602
+ const enumName = enumNode.id.name;
603
+ const members = /* @__PURE__ */ new Map();
604
+ let nextNumericValue = 0;
605
+ for (const member of enumNode.members) {
606
+ const memberName = member.id.type === "Identifier" ? member.id.name : member.id.value;
607
+ if (member.initializer) {
608
+ const val = literalToStaticValue(member.initializer);
609
+ if (val !== null) {
610
+ members.set(memberName, val);
611
+ nextNumericValue = typeof val === "number" ? val + 1 : 0;
612
+ }
613
+ } else {
614
+ members.set(memberName, nextNumericValue);
615
+ nextNumericValue++;
616
+ }
617
+ }
618
+ if (members.size > 0) map.set(enumName, members);
619
+ });
620
+ return map;
621
+ }
622
+ /**
623
+ * Resolves a MemberExpression node (e.g., `ProgressType.success`) to its
624
+ * static enum value using a pre-built enum map. Returns null if the node
625
+ * is not a MemberExpression or doesn't reference a known enum member.
626
+ */
627
+ function resolveStaticExpressionValue(node, enumValueMap) {
628
+ const v = literalToStaticValue(node);
629
+ if (v !== null) return v;
630
+ if (!enumValueMap || !node || typeof node !== "object") return null;
631
+ const n = node;
632
+ if (n.type !== "MemberExpression" || n.computed) return null;
633
+ const obj = n.object;
634
+ const prop = n.property;
635
+ if (obj?.type !== "Identifier" || !obj.name || prop?.type !== "Identifier" || !prop.name) return null;
636
+ return enumValueMap.get(obj.name)?.get(prop.name) ?? null;
637
+ }
638
+ /**
594
639
  * Returns true if an AST node represents an "empty" CSS branch value.
595
640
  * Styled-components treats falsy interpolations as "omit this declaration".
596
641
  *
@@ -674,6 +719,16 @@ function unwrapLogicalFallback(expr) {
674
719
  return null;
675
720
  }
676
721
  /**
722
+ * Returns true when `expr` is a `??` / `||` logical expression whose
723
+ * right-hand side is a non-literal value (e.g., `props.fallbackColor`).
724
+ *
725
+ * Used to bail on theme resolution when the fallback references a runtime
726
+ * value the user depends on — dropping it silently would change semantics.
727
+ */
728
+ function hasNonLiteralLogicalFallback(expr) {
729
+ return isLogicalExpressionNode(expr) && (expr.operator === "??" || expr.operator === "||") && literalToStaticValue(expr.right) === null;
730
+ }
731
+ /**
677
732
  * Type guard for ConditionalExpression nodes.
678
733
  */
679
734
  function isConditionalExpressionNode(node) {
@@ -1360,7 +1415,7 @@ const isValidIdentifier = (name) => /^[$A-Z_][0-9A-Z_$]*$/i.test(name);
1360
1415
  * - Disjunction: `"a || b"` → `a || b`
1361
1416
  * - Grouped negation: `"!(a || b)"` → `!(a || b)`
1362
1417
  */
1363
- function parseVariantWhenToAst(j, when) {
1418
+ function parseVariantWhenToAst(j, when, booleanProps) {
1364
1419
  const buildMemberExpr = (raw) => {
1365
1420
  if (!raw.includes(".")) return null;
1366
1421
  const parts = raw.split(".").map((part) => part.trim()).filter(Boolean);
@@ -1414,7 +1469,7 @@ function parseVariantWhenToAst(j, when) {
1414
1469
  isBoolean: true
1415
1470
  };
1416
1471
  if (trimmed.startsWith("!(") && trimmed.endsWith(")")) {
1417
- const innerParsed = parseVariantWhenToAst(j, trimmed.slice(2, -1).trim());
1472
+ const innerParsed = parseVariantWhenToAst(j, trimmed.slice(2, -1).trim(), booleanProps);
1418
1473
  return {
1419
1474
  cond: j.unaryExpression("!", innerParsed.cond),
1420
1475
  props: innerParsed.props,
@@ -1422,7 +1477,7 @@ function parseVariantWhenToAst(j, when) {
1422
1477
  };
1423
1478
  }
1424
1479
  if (trimmed.includes("&&")) {
1425
- const parsed = trimmed.split("&&").map((s) => s.trim()).filter(Boolean).map((p) => parseVariantWhenToAst(j, p));
1480
+ const parsed = trimmed.split("&&").map((s) => s.trim()).filter(Boolean).map((p) => parseVariantWhenToAst(j, p, booleanProps));
1426
1481
  const firstParsed = parsed[0];
1427
1482
  if (!firstParsed) return {
1428
1483
  cond: j.identifier("true"),
@@ -1436,7 +1491,7 @@ function parseVariantWhenToAst(j, when) {
1436
1491
  };
1437
1492
  }
1438
1493
  if (trimmed.includes(" || ")) {
1439
- const parsed = trimmed.split(" || ").map((s) => s.trim()).filter(Boolean).map((p) => parseVariantWhenToAst(j, p));
1494
+ const parsed = trimmed.split(" || ").map((s) => s.trim()).filter(Boolean).map((p) => parseVariantWhenToAst(j, p, booleanProps));
1440
1495
  const firstParsedOr = parsed[0];
1441
1496
  if (!firstParsedOr) return {
1442
1497
  cond: j.identifier("true"),
@@ -1450,7 +1505,7 @@ function parseVariantWhenToAst(j, when) {
1450
1505
  };
1451
1506
  }
1452
1507
  if (trimmed.startsWith("!")) {
1453
- const innerParsed = parseVariantWhenToAst(j, trimmed.slice(1).trim());
1508
+ const innerParsed = parseVariantWhenToAst(j, trimmed.slice(1).trim(), booleanProps);
1454
1509
  return {
1455
1510
  cond: j.unaryExpression("!", innerParsed.cond),
1456
1511
  props: innerParsed.props,
@@ -1471,10 +1526,11 @@ function parseVariantWhenToAst(j, when) {
1471
1526
  };
1472
1527
  }
1473
1528
  const simple = parsePropRef(trimmed);
1529
+ const propIsBoolean = !!(simple.propName && booleanProps?.has(simple.propName));
1474
1530
  return {
1475
1531
  cond: simple.expr,
1476
1532
  props: simple.propName ? [simple.propName] : [],
1477
- isBoolean: false
1533
+ isBoolean: propIsBoolean
1478
1534
  };
1479
1535
  }
1480
1536
  /**
@@ -1483,8 +1539,8 @@ function parseVariantWhenToAst(j, when) {
1483
1539
  * function's parameter destructuring.
1484
1540
  */
1485
1541
  function collectConditionProps(j, args) {
1486
- const { when, destructureProps } = args;
1487
- const parsed = parseVariantWhenToAst(j, when);
1542
+ const { when, destructureProps, booleanProps } = args;
1543
+ const parsed = parseVariantWhenToAst(j, when, booleanProps);
1488
1544
  if (destructureProps) {
1489
1545
  for (const p of parsed.props) if (p && !destructureProps.includes(p)) destructureProps.push(p);
1490
1546
  }
@@ -1492,8 +1548,9 @@ function collectConditionProps(j, args) {
1492
1548
  }
1493
1549
  /**
1494
1550
  * Creates a conditional style expression that's safe for stylex.props().
1495
- * For boolean conditions, uses && (since false is valid for stylex.props).
1496
- * For non-boolean conditions (could be "" or 0), uses ternary with undefined fallback.
1551
+ * For boolean conditions, uses `cond && expr` (since `false` is a valid StyleXArray element).
1552
+ * For non-boolean conditions, uses `cond ? expr : undefined` to avoid producing
1553
+ * values like `""` or `0` which are not valid StyleXArray elements.
1497
1554
  */
1498
1555
  function makeConditionalStyleExpr(j, args) {
1499
1556
  const { cond, expr, isBoolean } = args;
@@ -1525,7 +1582,7 @@ function areEquivalentWhen(left, right) {
1525
1582
  * `prop ? trueExpr : falseExpr` instead of emitting separate conditionals.
1526
1583
  */
1527
1584
  function buildExtraStylexPropsExprs(j, args) {
1528
- const { entries, destructureProps } = args;
1585
+ const { entries, destructureProps, booleanProps } = args;
1529
1586
  const result = [];
1530
1587
  const consumed = /* @__PURE__ */ new Set();
1531
1588
  for (let i = 0; i < entries.length; i++) {
@@ -1552,7 +1609,8 @@ function buildExtraStylexPropsExprs(j, args) {
1552
1609
  }
1553
1610
  const { cond, isBoolean } = collectConditionProps(j, {
1554
1611
  when: entry.when,
1555
- destructureProps
1612
+ destructureProps,
1613
+ booleanProps
1556
1614
  });
1557
1615
  result.push(makeConditionalStyleExpr(j, {
1558
1616
  cond,
@@ -1880,10 +1938,11 @@ function analyzeBeforeEmitStep(ctx) {
1880
1938
  if (decl.isDirectJsxResolution) continue;
1881
1939
  if (decl.base.kind !== "intrinsic") continue;
1882
1940
  if (relationChildStyleKeys.has(decl.styleKey)) continue;
1883
- if (decl.promotedStyleProps?.length) continue;
1941
+ const usageCount = getJsxUsageCount(decl.localName);
1942
+ if (decl.promotedStyleProps?.length && decl.promotedStyleProps.length >= usageCount) continue;
1884
1943
  const { ref } = getJsxAttributeUsage(decl.localName);
1885
1944
  if (ref) continue;
1886
- if (getJsxUsageCount(decl.localName) > INLINE_USAGE_THRESHOLD) decl.needsWrapperComponent = true;
1945
+ if (usageCount > INLINE_USAGE_THRESHOLD) decl.needsWrapperComponent = true;
1887
1946
  }
1888
1947
  const hasSpreadInJsx = (name) => {
1889
1948
  let found = false;
@@ -1970,11 +2029,13 @@ function analyzeBeforeEmitStep(ctx) {
1970
2029
  existingPropNames.add("className").add("style").add("children").add("ref").add("key").add("as");
1971
2030
  if (decl.base.kind === "component") collectBaseComponentPropNames(root, j, decl.base.ident, existingPropNames);
1972
2031
  if (decl.base.kind === "intrinsic") for (const attr of BLOCKED_INTRINSIC_ATTR_RENAMES[decl.base.tagName] ?? []) existingPropNames.add(attr);
1973
- if (collectCallSiteAttrNames(root, j, decl.localName, existingPropNames)) continue;
2032
+ const { hasSpread, explicitTransientAtSpreadSites } = collectCallSiteAttrNames(root, j, decl.localName, existingPropNames);
1974
2033
  const renames = /* @__PURE__ */ new Map();
1975
2034
  for (const prop of transientProps) {
1976
2035
  const stripped = prop.slice(1);
1977
- if (!existingPropNames.has(stripped)) renames.set(prop, stripped);
2036
+ if (existingPropNames.has(stripped)) continue;
2037
+ if (hasSpread && !explicitTransientAtSpreadSites?.has(prop)) continue;
2038
+ renames.set(prop, stripped);
1978
2039
  }
1979
2040
  if (renames.size > 0) {
1980
2041
  if (collectReferencedTypeNames(decl.propsType).some((name) => isTypeNameUsedElsewhere(root, j, name, decl.localName) || !isTypeLocallyDefined(root, j, name))) continue;
@@ -2148,10 +2209,12 @@ function analyzeBeforeEmitStep(ctx) {
2148
2209
  decl.variantStyleKeys[whenKey] = styleKey;
2149
2210
  }
2150
2211
  if (decl.callSiteCombinedStyles?.length) for (const { styleKey, styles } of decl.callSiteCombinedStyles) ctx.resolvedStyleObjects.set(styleKey, styles);
2151
- if (decl.promotedStyleProps?.length) for (const entry of decl.promotedStyleProps) if (entry.mergeIntoBase) {
2212
+ if (decl.promotedStyleProps?.length) for (const entry of decl.promotedStyleProps) if (entry.mergeIntoBase) if (isAstNode(entry.styleValue)) ctx.resolvedStyleObjects.set(decl.styleKey, entry.styleValue);
2213
+ else {
2152
2214
  const existing = ctx.resolvedStyleObjects.get(decl.styleKey);
2153
2215
  if (existing && typeof existing === "object" && !isAstNode(existing)) Object.assign(existing, entry.styleValue);
2154
- } else ctx.resolvedStyleObjects.set(entry.styleKey, entry.styleValue);
2216
+ }
2217
+ else ctx.resolvedStyleObjects.set(entry.styleKey, entry.styleValue);
2155
2218
  }
2156
2219
  return CONTINUE;
2157
2220
  }
@@ -2245,19 +2308,26 @@ function collectResolverImportNames(ctx) {
2245
2308
  }
2246
2309
  return names;
2247
2310
  }
2248
- /**
2249
- * Collects non-`$`-prefixed attribute names from JSX call sites of a component.
2250
- * Returns true if any call site uses a JSX spread attribute (e.g., `{...props}`),
2251
- * which means the spread may contain `$`-prefixed keys at runtime — all renames
2252
- * must be blocked to prevent mismatches.
2253
- */
2254
2311
  function collectCallSiteAttrNames(root, j, componentName, names) {
2255
2312
  let hasSpread = false;
2313
+ const spreadSiteTransientProps = [];
2256
2314
  const collectFromElement = (openingElement) => {
2257
- for (const attr of openingElement.attributes ?? []) if (attr.type === "JSXSpreadAttribute") hasSpread = true;
2258
- else if (attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier") {
2315
+ let siteHasSpread = false;
2316
+ const siteTransientAfterSpread = /* @__PURE__ */ new Set();
2317
+ const siteTransientBeforeSpread = /* @__PURE__ */ new Set();
2318
+ for (const attr of openingElement.attributes ?? []) if (attr.type === "JSXSpreadAttribute") {
2319
+ hasSpread = true;
2320
+ siteHasSpread = true;
2321
+ for (const name of siteTransientAfterSpread) siteTransientBeforeSpread.add(name);
2322
+ siteTransientAfterSpread.clear();
2323
+ } else if (attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier") {
2259
2324
  const name = attr.name.name;
2260
- if (!name.startsWith("$")) names.add(name);
2325
+ if (name.startsWith("$")) siteTransientAfterSpread.add(name);
2326
+ else names.add(name);
2327
+ }
2328
+ if (siteHasSpread) {
2329
+ for (const name of siteTransientBeforeSpread) siteTransientAfterSpread.delete(name);
2330
+ spreadSiteTransientProps.push(siteTransientAfterSpread);
2261
2331
  }
2262
2332
  };
2263
2333
  root.find(j.JSXElement, { openingElement: { name: {
@@ -2268,7 +2338,20 @@ function collectCallSiteAttrNames(root, j, componentName, names) {
2268
2338
  type: "JSXIdentifier",
2269
2339
  name: componentName
2270
2340
  } }).forEach((p) => collectFromElement(p.node));
2271
- return hasSpread;
2341
+ if (!hasSpread) return {
2342
+ hasSpread: false,
2343
+ explicitTransientAtSpreadSites: null
2344
+ };
2345
+ if (spreadSiteTransientProps.length === 0) return {
2346
+ hasSpread: true,
2347
+ explicitTransientAtSpreadSites: /* @__PURE__ */ new Set()
2348
+ };
2349
+ const intersection = new Set(spreadSiteTransientProps[0]);
2350
+ for (let i = 1; i < spreadSiteTransientProps.length; i++) for (const prop of intersection) if (!spreadSiteTransientProps[i].has(prop)) intersection.delete(prop);
2351
+ return {
2352
+ hasSpread: true,
2353
+ explicitTransientAtSpreadSites: intersection
2354
+ };
2272
2355
  }
2273
2356
  /**
2274
2357
  * Collects prop names from a locally-defined base component's type that match
@@ -2382,6 +2465,7 @@ function applyTransientPropRenames(decl, renames) {
2382
2465
  }
2383
2466
  if (decl.styleFnFromProps) for (const sf of decl.styleFnFromProps) {
2384
2467
  sf.jsxProp = renames.get(sf.jsxProp) ?? sf.jsxProp;
2468
+ if (sf.propsObjectKey) sf.propsObjectKey = renames.get(sf.propsObjectKey) ?? sf.propsObjectKey;
2385
2469
  if (sf.conditionWhen) sf.conditionWhen = renamePropsInWhenString(sf.conditionWhen, renames);
2386
2470
  if (sf.callArg) renameIdentifiersInAst(sf.callArg, renames);
2387
2471
  }
@@ -2466,7 +2550,6 @@ function renameIdentifiersInAst(node, renames) {
2466
2550
  if (n.type === "Identifier" && typeof n.name === "string") {
2467
2551
  const renamed = renames.get(n.name);
2468
2552
  if (renamed) n.name = renamed;
2469
- return;
2470
2553
  }
2471
2554
  for (const [key, value] of Object.entries(n)) {
2472
2555
  if (AST_METADATA_KEYS.has(key)) continue;
@@ -2621,8 +2704,11 @@ const NUMERIC_CSS_PROPS = new Set([
2621
2704
  "widows",
2622
2705
  "columnCount"
2623
2706
  ]);
2624
- /** CSS properties that accept both numeric and string values (e.g., grid line numbers or span syntax). */
2625
- const NUMBER_OR_STRING_CSS_PROPS = new Set([
2707
+ /**
2708
+ * CSS properties that accept numeric values in standard CSS / React inline styles
2709
+ * but are typed as `string` in StyleX. Numeric values must be coerced to strings.
2710
+ */
2711
+ const STYLEX_STRING_ONLY_CSS_PROPS = new Set([
2626
2712
  "gridRow",
2627
2713
  "gridColumn",
2628
2714
  "gridRowStart",
@@ -2635,16 +2721,22 @@ const IDENTIFIER_NAME_RE = /^[$A-Z_][0-9A-Z_$]*$/i;
2635
2721
  function isValidIdentifierName$1(name) {
2636
2722
  return IDENTIFIER_NAME_RE.test(name);
2637
2723
  }
2724
+ function coerceToStringForStyleX(cssProp, value) {
2725
+ if (STYLEX_STRING_ONLY_CSS_PROPS.has(cssProp) && typeof value === "number") return String(value);
2726
+ return value;
2727
+ }
2638
2728
  /**
2639
2729
  * Infers a TS type keyword for a dynamic expression based on the CSS property it's assigned to.
2640
2730
  * Numeric-only properties get `number`; ambiguous length-like values get `number | string`.
2731
+ * Properties in STYLEX_STRING_ONLY_CSS_PROPS always get `string` even when the value is numeric.
2641
2732
  */
2642
2733
  function inferTypeForCssProp(cssProp, expr) {
2734
+ if (STYLEX_STRING_ONLY_CSS_PROPS.has(cssProp)) return "string";
2643
2735
  const staticVal = literalToStaticValue(expr);
2644
2736
  if (typeof staticVal === "number") return "number";
2645
2737
  if (typeof staticVal === "string") return "string";
2646
2738
  if (NUMERIC_CSS_PROPS.has(cssProp)) return "number";
2647
- if (NUMBER_OR_STRING_CSS_PROPS.has(cssProp) || LENGTH_LIKE_CSS_PROP_RE.test(cssProp)) return "numberOrString";
2739
+ if (LENGTH_LIKE_CSS_PROP_RE.test(cssProp)) return "numberOrString";
2648
2740
  return "string";
2649
2741
  }
2650
2742
  /**
@@ -2773,7 +2865,7 @@ function analyzePromotableStyleProps(root, j, styledDecls, declByLocal, getJsxUs
2773
2865
  const hasDynamic = site.properties.some((p) => p.dynamicExpr !== null);
2774
2866
  if (allStatic && !hasDynamic) {
2775
2867
  const staticObj = {};
2776
- for (const p of site.properties) staticObj[p.key] = p.staticValue;
2868
+ for (const p of site.properties) staticObj[p.key] = coerceToStringForStyleX(p.key, p.staticValue);
2777
2869
  if (usageCount <= 1 && !decl.isExported && !hasPropertyOverlap(staticObj, resolvedStyleObjects.get(decl.styleKey))) {
2778
2870
  promotedEntries.push({
2779
2871
  styleKey: decl.styleKey,
@@ -2791,15 +2883,31 @@ function analyzePromotableStyleProps(root, j, styledDecls, declByLocal, getJsxUs
2791
2883
  site.opening.__promotedStyleKey = styleKey;
2792
2884
  }
2793
2885
  } else if (hasDynamic) {
2794
- const styleKey = generatePromotedDynamicStyleKey(decl.styleKey, usedKeyNames, site.children);
2795
- usedKeyNames.add(styleKey);
2796
- const staticObj = {};
2886
+ const inlineStaticObj = {};
2797
2887
  const dynamicParams = [];
2798
- for (const p of site.properties) if (p.staticValue !== null) staticObj[p.key] = p.staticValue;
2888
+ for (const p of site.properties) if (p.staticValue !== null) inlineStaticObj[p.key] = coerceToStringForStyleX(p.key, p.staticValue);
2799
2889
  else dynamicParams.push({
2800
2890
  cssProp: p.key,
2801
2891
  expr: p.dynamicExpr
2802
2892
  });
2893
+ const baseObj = resolvedStyleObjects.get(decl.styleKey);
2894
+ const baseIsSimpleObject = baseObj && typeof baseObj === "object" && !isAstNode(baseObj) && Object.values(baseObj).every((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || isAstNode(v));
2895
+ const isExtendedByOther = styledDecls.some((d) => d !== decl && d.base.kind === "component" && d.base.ident === decl.localName);
2896
+ const canMergeDynamic = usageCount <= 1 && !decl.isExported && !isExtendedByOther && baseIsSimpleObject && !hasPropertyOverlap(inlineStaticObj, baseObj);
2897
+ const dynamicPropKeys = new Set(dynamicParams.map((dp) => dp.cssProp));
2898
+ const mergedStaticProps = [];
2899
+ if (canMergeDynamic) {
2900
+ for (const [k, v] of Object.entries(baseObj)) if (!dynamicPropKeys.has(k)) mergedStaticProps.push({
2901
+ key: k,
2902
+ value: v
2903
+ });
2904
+ }
2905
+ for (const [k, v] of Object.entries(inlineStaticObj)) mergedStaticProps.push({
2906
+ key: k,
2907
+ value: v
2908
+ });
2909
+ const styleKey = canMergeDynamic ? decl.styleKey : generatePromotedDynamicStyleKey(decl.styleKey, usedKeyNames, site.children);
2910
+ if (!canMergeDynamic) usedKeyNames.add(styleKey);
2803
2911
  const paramEntries = [];
2804
2912
  const seenParamNames = /* @__PURE__ */ new Set();
2805
2913
  for (const dp of dynamicParams) {
@@ -2819,24 +2927,34 @@ function analyzePromotableStyleProps(root, j, styledDecls, declByLocal, getJsxUs
2819
2927
  id.typeAnnotation = j.tsTypeAnnotation(typeNode);
2820
2928
  return id;
2821
2929
  });
2822
- const bodyProperties = site.properties.map((p) => {
2823
- if (p.staticValue !== null) {
2824
- const val = typeof p.staticValue === "string" ? j.stringLiteral(p.staticValue) : typeof p.staticValue === "number" ? j.numericLiteral(p.staticValue) : j.booleanLiteral(p.staticValue);
2825
- return j.property("init", j.identifier(p.key), val);
2826
- } else {
2827
- const prop = j.property("init", j.identifier(p.key), j.identifier(p.key));
2828
- prop.shorthand = true;
2829
- return prop;
2830
- }
2831
- });
2930
+ const bodyProperties = [];
2931
+ for (const sp of mergedStaticProps) {
2932
+ const val = isAstNode(sp.value) ? sp.value : typeof sp.value === "string" ? j.stringLiteral(sp.value) : typeof sp.value === "number" ? j.numericLiteral(sp.value) : j.booleanLiteral(sp.value);
2933
+ bodyProperties.push(j.property("init", j.identifier(sp.key), val));
2934
+ }
2935
+ for (const p of site.properties) if (p.dynamicExpr !== null) {
2936
+ const prop = j.property("init", j.identifier(p.key), j.identifier(p.key));
2937
+ prop.shorthand = true;
2938
+ bodyProperties.push(prop);
2939
+ }
2832
2940
  const fnNode = j.arrowFunctionExpression(params, j.objectExpression(bodyProperties));
2833
- promotedEntries.push({
2834
- styleKey,
2835
- styleValue: fnNode
2836
- });
2837
- site.opening.__promotedStyleKey = styleKey;
2838
- const callArgs = dynamicParams.map((dp) => dp.expr);
2839
- site.opening.__promotedStyleArgs = callArgs;
2941
+ if (canMergeDynamic) {
2942
+ promotedEntries.push({
2943
+ styleKey: decl.styleKey,
2944
+ styleValue: fnNode,
2945
+ mergeIntoBase: true
2946
+ });
2947
+ site.opening.__promotedMergeIntoBase = true;
2948
+ site.opening.__promotedMergeArgs = dynamicParams.map((dp) => STYLEX_STRING_ONLY_CSS_PROPS.has(dp.cssProp) ? j.callExpression(j.identifier("String"), [dp.expr]) : dp.expr);
2949
+ } else {
2950
+ promotedEntries.push({
2951
+ styleKey,
2952
+ styleValue: fnNode
2953
+ });
2954
+ site.opening.__promotedStyleKey = styleKey;
2955
+ const callArgs = dynamicParams.map((dp) => STYLEX_STRING_ONLY_CSS_PROPS.has(dp.cssProp) ? j.callExpression(j.identifier("String"), [dp.expr]) : dp.expr);
2956
+ site.opening.__promotedStyleArgs = callArgs;
2957
+ }
2840
2958
  }
2841
2959
  }
2842
2960
  if (promotedEntries.length > 0) decl.promotedStyleProps = promotedEntries;
@@ -4434,6 +4552,14 @@ function printNode(node) {
4434
4552
  function areAllValuesSame(values) {
4435
4553
  return values.length > 1 && values.every((value) => value === values[0]);
4436
4554
  }
4555
+ /**
4556
+ * Shorthands that StyleX requires to be fully expanded to physical longhands.
4557
+ * Unlike `padding`/`margin` (which StyleX's Babel plugin can expand internally),
4558
+ * `scrollPadding`/`scrollMargin` and their logical forms (Block/Inline) are not
4559
+ * recognized by `@stylexjs/valid-styles` and must always be written as
4560
+ * Top/Right/Bottom/Left.
4561
+ */
4562
+ const PHYSICAL_ONLY_SHORTHANDS = new Set(["scrollMargin", "scrollPadding"]);
4437
4563
  function expandQuadValues(values) {
4438
4564
  const top = values[0] ?? "";
4439
4565
  const right = values[1] ?? top;
@@ -4465,12 +4591,13 @@ function splitDirectionalProperty(args) {
4465
4591
  const bottom = values[2] ?? top;
4466
4592
  const left = values[3] ?? right;
4467
4593
  const withImportant = (value) => important ? `${value} !important` : value;
4468
- if (values.length === 1 && !important && !alwaysExpand) return [{
4594
+ const physicalOnly = PHYSICAL_ONLY_SHORTHANDS.has(prop);
4595
+ if (values.length === 1 && !important && !alwaysExpand && !physicalOnly) return [{
4469
4596
  prop,
4470
4597
  value: withImportant(top)
4471
4598
  }];
4472
4599
  const quad = expandQuadValues(values);
4473
- if (important) return [
4600
+ if (important || physicalOnly) return [
4474
4601
  {
4475
4602
  prop: `${prop}Top`,
4476
4603
  value: withImportant(quad[0])
@@ -7991,8 +8118,8 @@ function buildInlineResolverVariantDimensions(args) {
7991
8118
  return type === "ArrowFunctionExpression" || type === "FunctionExpression";
7992
8119
  });
7993
8120
  const hasCompleteCallsiteVisibility = !willHaveExternalInterface(ctx, decl, styledDecls) && !decl.usedAsValue;
7994
- if (variantKeys.length === 1 && !hasPropReferencingTemplateExpressions) {
7995
- if (hasCompleteCallsiteVisibility) {
8121
+ if (variantKeys.length === 1) {
8122
+ if (hasCompleteCallsiteVisibility && !hasPropReferencingTemplateExpressions) {
7996
8123
  if (usageResult.propsByUsage.filter((siteProps) => propName in siteProps).length === totalCallSites) {
7997
8124
  const [, singleVariantStyles] = Object.entries(variants)[0];
7998
8125
  for (const [cssKey, cssVal] of Object.entries(singleVariantStyles)) foldedBaseSx[cssKey] = String(cssVal);
@@ -9625,6 +9752,22 @@ function buildVariantDimPropTypeMap(d) {
9625
9752
  return [dim.propName, typeText];
9626
9753
  }));
9627
9754
  }
9755
+ /**
9756
+ * Collects prop names that are known to have `boolean` type from all available sources:
9757
+ * - `staticBooleanVariants` without a variantKey (confirmed boolean from prepass)
9758
+ * - `variantDimensions` with `isBooleanProp` flag
9759
+ * - `propsType` AST type literal with `boolean` type annotations
9760
+ *
9761
+ * Used to emit `cond && styles.key` (valid for StyleXArray since `false` is accepted)
9762
+ * instead of `cond ? styles.key : undefined` for boolean-guarded variant conditions.
9763
+ */
9764
+ function collectBooleanPropNames(d) {
9765
+ const result = /* @__PURE__ */ new Set();
9766
+ for (const sbv of d.staticBooleanVariants ?? []) if (!sbv.variantKey) result.add(sbv.propName);
9767
+ for (const dim of d.variantDimensions ?? []) if (dim.isBooleanProp) result.add(dim.propName);
9768
+ collectBooleanPropsFromTypeLiteral(d.propsType, result);
9769
+ return result;
9770
+ }
9628
9771
  function getAttrsAsString(d) {
9629
9772
  const v = d.attrsInfo?.staticAttrs?.as;
9630
9773
  return typeof v === "string" ? v : null;
@@ -9658,6 +9801,41 @@ function injectStylePropsIntoTypeLiteralString(typeText, options) {
9658
9801
  }
9659
9802
  return `${typeText} & { ${propsToAdd.join(", ")} }`;
9660
9803
  }
9804
+ function collectBooleanPropsFromTypeLiteral(node, result) {
9805
+ for (const name of extractBooleanProps(node)) {
9806
+ result.add(name);
9807
+ if (name.startsWith("$")) result.add(name.slice(1));
9808
+ }
9809
+ }
9810
+ function extractBooleanProps(node) {
9811
+ const typed = node;
9812
+ if (!typed || typeof typed !== "object") return /* @__PURE__ */ new Set();
9813
+ if (typed.type === "TSTypeLiteral") {
9814
+ const props = /* @__PURE__ */ new Set();
9815
+ for (const member of typed.members ?? []) {
9816
+ if (member?.type !== "TSPropertySignature") continue;
9817
+ const key = member.key;
9818
+ const name = key?.type === "Identifier" ? key.name : null;
9819
+ if (!name) continue;
9820
+ if ((member.typeAnnotation?.typeAnnotation)?.type === "TSBooleanKeyword") props.add(name);
9821
+ }
9822
+ return props;
9823
+ }
9824
+ if (typed.type === "TSIntersectionType") {
9825
+ const combined = /* @__PURE__ */ new Set();
9826
+ for (const t of typed.types ?? []) for (const p of extractBooleanProps(t)) combined.add(p);
9827
+ return combined;
9828
+ }
9829
+ if (typed.type === "TSUnionType") {
9830
+ const arms = (typed.types ?? []).map(extractBooleanProps);
9831
+ if (arms.length === 0) return /* @__PURE__ */ new Set();
9832
+ const first = arms[0];
9833
+ const intersection = /* @__PURE__ */ new Set();
9834
+ for (const name of first) if (arms.every((arm) => arm.has(name))) intersection.add(name);
9835
+ return intersection;
9836
+ }
9837
+ return /* @__PURE__ */ new Set();
9838
+ }
9661
9839
 
9662
9840
  //#endregion
9663
9841
  //#region src/internal/emit-wrappers/jsx-builders.ts
@@ -9816,6 +9994,15 @@ function styleRef(j, stylesIdentifier, key) {
9816
9994
  return j.memberExpression(j.identifier(stylesIdentifier), j.identifier(key));
9817
9995
  }
9818
9996
  /**
9997
+ * When a style function uses a `props` object parameter, wraps the raw call
9998
+ * argument in `{ [propsObjectKey]: rawArg }`. Returns `rawArg` unchanged when
9999
+ * `propsObjectKey` is not set.
10000
+ */
10001
+ function wrapCallArgForPropsObject(j, rawArg, propsObjectKey) {
10002
+ if (!propsObjectKey) return rawArg;
10003
+ return j.objectExpression([j.property("init", j.identifier(propsObjectKey), rawArg)]);
10004
+ }
10005
+ /**
9819
10006
  * Splits a declaration's extra style keys into those that appear before and
9820
10007
  * after the base style in stylex.props() argument order.
9821
10008
  */
@@ -10093,9 +10280,10 @@ function buildStyleFnExpressions(emitter, args) {
10093
10280
  }
10094
10281
  return null;
10095
10282
  };
10283
+ const booleanProps = collectBooleanPropNames(d);
10096
10284
  for (const p of styleFnPairs) {
10097
10285
  const propExpr = p.jsxProp === "__props" ? propsId : propExprBuilder(p.jsxProp);
10098
- const callArg = p.callArg ?? propExpr;
10286
+ const callArg = wrapCallArgForPropsObject(j, p.callArg ?? propExpr, p.propsObjectKey);
10099
10287
  const call = j.callExpression(styleRef(j, stylesIdentifier, p.fnKey), [callArg]);
10100
10288
  if (p.callArg?.type === "Identifier") {
10101
10289
  const name = p.callArg.name;
@@ -10122,7 +10310,8 @@ function buildStyleFnExpressions(emitter, args) {
10122
10310
  if (p.conditionWhen) {
10123
10311
  const { cond, isBoolean } = collectConditionProps(j, {
10124
10312
  when: p.conditionWhen,
10125
- destructureProps
10313
+ destructureProps,
10314
+ booleanProps
10126
10315
  });
10127
10316
  pushExpr(makeConditionalStyleExpr(j, {
10128
10317
  cond,
@@ -11769,6 +11958,7 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
11769
11958
  const needsUseTheme = appendThemeBooleanStyleArgs(d.needsUseThemeHook, styleArgs, j, stylesIdentifier, () => ctx.markNeedsUseThemeImport());
11770
11959
  for (const gp of appendAllPseudoStyleArgs(d, styleArgs, j, stylesIdentifier)) if (!destructureProps.includes(gp)) destructureProps.push(gp);
11771
11960
  const compoundVariantKeys = collectCompoundVariantKeys(d.compoundVariants);
11961
+ const booleanProps = collectBooleanPropNames(d);
11772
11962
  const hasSourceOrder = !!(d.variantSourceOrder && Object.keys(d.variantSourceOrder).length > 0);
11773
11963
  const orderedEntries = [];
11774
11964
  if (d.variantStyleKeys) {
@@ -11787,7 +11977,8 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
11787
11977
  const positiveWhen = getPositiveWhen(when, otherWhen) ?? when;
11788
11978
  const { cond } = emitter.collectConditionProps({
11789
11979
  when: positiveWhen,
11790
- destructureProps
11980
+ destructureProps,
11981
+ booleanProps
11791
11982
  });
11792
11983
  const isCurrentPositive = areEquivalentWhen(when, positiveWhen);
11793
11984
  const trueKey = isCurrentPositive ? variantKey : otherKey;
@@ -11805,7 +11996,8 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
11805
11996
  }
11806
11997
  const { cond, isBoolean } = emitter.collectConditionProps({
11807
11998
  when,
11808
- destructureProps
11999
+ destructureProps,
12000
+ booleanProps
11809
12001
  });
11810
12002
  const styleExpr = j.memberExpression(j.identifier(stylesIdentifier), j.identifier(variantKey));
11811
12003
  const expr = emitter.makeConditionalStyleExpr({
@@ -12167,12 +12359,12 @@ function appendAllPseudoStyleArgs(d, styleArgs, j, stylesIdentifier) {
12167
12359
  * Each entry is mapped to an expression via `buildExpr`, then optionally
12168
12360
  * wrapped in a conditional if the entry has a `guard`.
12169
12361
  */
12170
- function appendGuardedStyleArgs(entries, styleArgs, j, buildExpr) {
12362
+ function appendGuardedStyleArgs(entries, styleArgs, j, buildExpr, booleanProps) {
12171
12363
  const guardProps = [];
12172
12364
  for (const entry of entries) {
12173
12365
  const expr = buildExpr(entry);
12174
12366
  if (entry.guard) {
12175
- const parsed = parseVariantWhenToAst(j, entry.guard.when);
12367
+ const parsed = parseVariantWhenToAst(j, entry.guard.when, booleanProps);
12176
12368
  for (const p of parsed.props) if (p && !guardProps.includes(p)) guardProps.push(p);
12177
12369
  styleArgs.push(makeConditionalStyleExpr(j, {
12178
12370
  cond: parsed.cond,
@@ -12364,6 +12556,7 @@ function emitComponentWrappers(emitter) {
12364
12556
  ];
12365
12557
  const hasSourceOrder = !!(d.variantSourceOrder && Object.keys(d.variantSourceOrder).length > 0);
12366
12558
  const orderedEntries = [];
12559
+ const booleanProps = collectBooleanPropNames(d);
12367
12560
  if (d.variantStyleKeys) {
12368
12561
  const sortedEntries = sortVariantEntriesBySpecificity(Object.entries(d.variantStyleKeys));
12369
12562
  const consumedVariantIndices = /* @__PURE__ */ new Set();
@@ -12378,7 +12571,8 @@ function emitComponentWrappers(emitter) {
12378
12571
  const positiveWhen = getPositiveWhen(when, otherWhen) ?? when;
12379
12572
  const { cond } = emitter.collectConditionProps({
12380
12573
  when: positiveWhen,
12381
- destructureProps
12574
+ destructureProps,
12575
+ booleanProps
12382
12576
  });
12383
12577
  for (let i = prevLength; i < destructureProps.length; i++) styleOnlyConditionProps.add(destructureProps[i]);
12384
12578
  const isCurrentPositive = areEquivalentWhen(when, positiveWhen);
@@ -12397,7 +12591,8 @@ function emitComponentWrappers(emitter) {
12397
12591
  }
12398
12592
  const { cond, isBoolean } = emitter.collectConditionProps({
12399
12593
  when,
12400
- destructureProps
12594
+ destructureProps,
12595
+ booleanProps
12401
12596
  });
12402
12597
  for (let i = prevLength; i < destructureProps.length; i++) styleOnlyConditionProps.add(destructureProps[i]);
12403
12598
  const styleExpr = j.memberExpression(j.identifier(stylesIdentifier), j.identifier(variantKey));
@@ -13344,6 +13539,7 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
13344
13539
  ...extraStyleArgsAfterBase
13345
13540
  ];
13346
13541
  const compoundVariantKeys = collectCompoundVariantKeys(d.compoundVariants);
13542
+ const booleanProps = collectBooleanPropNames(d);
13347
13543
  const hasSourceOrder = !!(d.variantSourceOrder && Object.keys(d.variantSourceOrder).length > 0);
13348
13544
  const orderedEntries = [];
13349
13545
  if (d.variantStyleKeys) {
@@ -13352,7 +13548,8 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
13352
13548
  if (compoundVariantKeys.has(when)) continue;
13353
13549
  const { cond, isBoolean } = emitter.collectConditionProps({
13354
13550
  when,
13355
- destructureProps
13551
+ destructureProps,
13552
+ booleanProps
13356
13553
  });
13357
13554
  const styleExpr = j.memberExpression(j.identifier(stylesIdentifier), j.identifier(variantKey));
13358
13555
  const expr = emitter.makeConditionalStyleExpr({
@@ -13653,13 +13850,17 @@ function emitShouldForwardPropWrappers(ctx) {
13653
13850
  const needsUseTheme = appendThemeBooleanStyleArgs(d.needsUseThemeHook, styleArgs, j, stylesIdentifier, () => ctx.markNeedsUseThemeImport());
13654
13851
  const pseudoGuardProps = appendAllPseudoStyleArgs(d, styleArgs, j, stylesIdentifier);
13655
13852
  const compoundVariantKeys = collectCompoundVariantKeys(d.compoundVariants);
13853
+ const booleanProps = collectBooleanPropNames(d);
13656
13854
  const hasSourceOrder = !!(d.variantSourceOrder && Object.keys(d.variantSourceOrder).length > 0);
13657
13855
  const orderedEntries = [];
13658
13856
  if (d.variantStyleKeys) {
13659
13857
  const sortedEntries = sortVariantEntriesBySpecificity(Object.entries(d.variantStyleKeys));
13660
13858
  for (const [when, variantKey] of sortedEntries) {
13661
13859
  if (compoundVariantKeys.has(when)) continue;
13662
- const { cond, isBoolean } = emitter.collectConditionProps({ when });
13860
+ const { cond, isBoolean } = emitter.collectConditionProps({
13861
+ when,
13862
+ booleanProps
13863
+ });
13663
13864
  const styleExpr = j.memberExpression(j.identifier(stylesIdentifier), j.identifier(variantKey));
13664
13865
  const expr = emitter.makeConditionalStyleExpr({
13665
13866
  cond,
@@ -13703,13 +13904,14 @@ function emitShouldForwardPropWrappers(ctx) {
13703
13904
  const prefix = dropPrefix;
13704
13905
  const isPrefixProp = !!prefix && typeof p.jsxProp === "string" && p.jsxProp !== "__props" && p.jsxProp.startsWith(prefix);
13705
13906
  const propExpr = isPrefixProp ? knownPrefixPropsSet.has(p.jsxProp) ? j.identifier(p.jsxProp) : j.memberExpression(j.identifier("props"), j.literal(p.jsxProp), true) : p.jsxProp === "__props" ? j.identifier("props") : j.identifier(p.jsxProp);
13706
- const callArg = p.callArg ?? propExpr;
13907
+ const callArg = wrapCallArgForPropsObject(j, p.callArg ?? propExpr, p.propsObjectKey);
13707
13908
  const call = j.callExpression(styleRef(j, stylesIdentifier, p.fnKey), [callArg]);
13708
13909
  let expr;
13709
13910
  if (p.conditionWhen) {
13710
13911
  const { cond, isBoolean } = emitter.collectConditionProps({
13711
13912
  when: p.conditionWhen,
13712
- destructureProps: destructureParts
13913
+ destructureProps: destructureParts,
13914
+ booleanProps
13713
13915
  });
13714
13916
  expr = emitter.makeConditionalStyleExpr({
13715
13917
  cond,
@@ -14876,8 +15078,8 @@ function createThemeResolvers(args) {
14876
15078
  const themeAccessExpr = unwrapLogicalFallback(bodyExpr) ?? bodyExpr;
14877
15079
  const direct = resolveThemeValue(themeAccessExpr, cssProperty);
14878
15080
  if (direct) {
14879
- if (isDirectionalThemeResult(direct)) return direct;
14880
- return wrapWithLogicalFallback(direct, bodyExpr, j);
15081
+ if (hasNonLiteralLogicalFallback(bodyExpr)) return null;
15082
+ return direct;
14881
15083
  }
14882
15084
  const paramName = expr.params?.[0]?.type === "Identifier" ? expr.params[0].name : null;
14883
15085
  const unwrap = (node) => {
@@ -14911,8 +15113,8 @@ function createThemeResolvers(args) {
14911
15113
  if (!themePath) return null;
14912
15114
  const resolved = resolveThemePath(themePath, getNodeLocStart(unwrapped) ?? void 0, cssProperty);
14913
15115
  if (!resolved) return null;
14914
- if (isDirectionalThemeResult(resolved)) return resolved;
14915
- return wrapWithLogicalFallback(resolved, bodyExpr, j);
15116
+ if (hasNonLiteralLogicalFallback(bodyExpr)) return null;
15117
+ return resolved;
14916
15118
  };
14917
15119
  return {
14918
15120
  hasLocalThemeBinding,
@@ -14921,20 +15123,6 @@ function createThemeResolvers(args) {
14921
15123
  };
14922
15124
  }
14923
15125
  /**
14924
- * If `originalExpr` was a logical fallback (`X ?? "default"` / `X || "default"`),
14925
- * wraps the resolved AST node in a LogicalExpression preserving the original
14926
- * operator and fallback value.
14927
- *
14928
- * Returns null if the fallback (RHS) is not a static literal, because dynamic
14929
- * references (e.g., `props.fallbackColor`) would be invalid in a static
14930
- * `stylex.create()` context where `props` is not in scope.
14931
- */
14932
- function wrapWithLogicalFallback(resolved, originalExpr, j) {
14933
- if (!isLogicalExpressionNode(originalExpr) || originalExpr.operator !== "??" && originalExpr.operator !== "||") return resolved;
14934
- if (literalToStaticValue(originalExpr.right) === null) return null;
14935
- return j.logicalExpression(originalExpr.operator, resolved, originalExpr.right);
14936
- }
14937
- /**
14938
15126
  * Type guard for the internal `__directional` tagged result.
14939
15127
  */
14940
15128
  function isDirectionalThemeResult(value) {
@@ -16088,6 +16276,7 @@ function createLowerRulesState(ctx) {
16088
16276
  j,
16089
16277
  importMap
16090
16278
  });
16279
+ const enumValueMap = buildEnumValueMap(root, j);
16091
16280
  const crossFileSelectorsByLocal = /* @__PURE__ */ new Map();
16092
16281
  if (ctx.crossFileSelectorUsages) for (const usage of ctx.crossFileSelectorUsages) crossFileSelectorsByLocal.set(usage.localName, usage);
16093
16282
  const state = {
@@ -16132,6 +16321,7 @@ function createLowerRulesState(ctx) {
16132
16321
  resolveCssHelperTemplate,
16133
16322
  resolveImportInScope,
16134
16323
  resolveImportForExpr,
16324
+ enumValueMap,
16135
16325
  crossFileSelectorsByLocal,
16136
16326
  inlineKeyframeNameMap: void 0,
16137
16327
  bail: false,
@@ -17024,7 +17214,15 @@ function tryResolveConditionalValue(node, ctx) {
17024
17214
  if (!isArrowFunctionExpression(expr)) return null;
17025
17215
  const info = getArrowFnThemeParamInfo(expr);
17026
17216
  const paramName = info?.kind === "propsParam" ? info.propsName : null;
17217
+ const propsParamName = paramName ?? void 0;
17218
+ const themeBindingName = info?.kind === "themeBinding" ? info.themeName : void 0;
17027
17219
  const paramBindings = !paramName && !info ? getArrowFnParamBindings(expr) : null;
17220
+ const runtimeCallState = { info: null };
17221
+ const buildRuntimeCallResult = () => runtimeCallState.info ? {
17222
+ type: "runtimeCallOnly",
17223
+ resolveCallContext: runtimeCallState.info.resolveCallContext,
17224
+ resolveCallResult: runtimeCallState.info.resolveCallResult
17225
+ } : null;
17028
17226
  const body = getFunctionBodyExpr(expr);
17029
17227
  if (!body || body.type !== "ConditionalExpression") return null;
17030
17228
  const checkThemeBooleanTest = (test) => {
@@ -17148,17 +17346,66 @@ function tryResolveConditionalValue(node, ctx) {
17148
17346
  };
17149
17347
  }
17150
17348
  if (!b || typeof b !== "object") return null;
17349
+ const callHasThemeArg = (call) => (call.arguments ?? []).some((arg) => {
17350
+ if (!arg || typeof arg !== "object" || arg.type !== "MemberExpression") return false;
17351
+ if (propsParamName) {
17352
+ const parts = getMemberPathFromIdentifier(arg, propsParamName);
17353
+ return parts !== null && parts[0] === "theme" && parts.length > 1;
17354
+ }
17355
+ if (themeBindingName) {
17356
+ const parts = getMemberPathFromIdentifier(arg, themeBindingName);
17357
+ return parts !== null && parts.length > 0;
17358
+ }
17359
+ return false;
17360
+ });
17361
+ const markAsRuntimeCall = (call) => {
17362
+ runtimeCallState.info = {
17363
+ resolveCallContext: {
17364
+ callSiteFilePath: ctx.filePath,
17365
+ calleeImportedName: "<local>",
17366
+ calleeSource: {
17367
+ kind: "specifier",
17368
+ value: ctx.filePath
17369
+ },
17370
+ args: [],
17371
+ ...call.loc?.start ? { loc: {
17372
+ line: call.loc.start.line,
17373
+ column: call.loc.start.column
17374
+ } } : {}
17375
+ },
17376
+ resolveCallResult: { preserveRuntimeCall: true }
17377
+ };
17378
+ };
17151
17379
  const resolveCallExpr = (call, cssProperty) => {
17152
- const res = resolveImportedHelperCall(call, ctx, void 0, cssProperty);
17153
- if (res.kind === "resolved" && "expr" in res.result) return res.result;
17380
+ const res = resolveImportedHelperCall(call, ctx, propsParamName, cssProperty, themeBindingName);
17381
+ if (res.kind === "resolved") {
17382
+ if ("expr" in res.result) return res.result;
17383
+ if (res.result.preserveRuntimeCall) {
17384
+ runtimeCallState.info = {
17385
+ resolveCallContext: res.resolveCallContext,
17386
+ resolveCallResult: res.resolveCallResult
17387
+ };
17388
+ return null;
17389
+ }
17390
+ }
17154
17391
  if (isCallExpressionNode(call.callee)) {
17155
17392
  const inner = call.callee;
17156
17393
  const outerArgs = call.arguments ?? [];
17157
17394
  if (outerArgs.length === 1 && outerArgs[0] && typeof outerArgs[0] === "object") {
17158
- const innerRes = resolveImportedHelperCall(inner, ctx, void 0, cssProperty);
17159
- if (innerRes.kind === "resolved" && "expr" in innerRes.result) return innerRes.result;
17395
+ const innerRes = resolveImportedHelperCall(inner, ctx, propsParamName, cssProperty, themeBindingName);
17396
+ if (innerRes.kind === "resolved") {
17397
+ if ("expr" in innerRes.result) return innerRes.result;
17398
+ if (innerRes.result.preserveRuntimeCall) {
17399
+ runtimeCallState.info = {
17400
+ resolveCallContext: innerRes.resolveCallContext,
17401
+ resolveCallResult: innerRes.resolveCallResult
17402
+ };
17403
+ return null;
17404
+ }
17405
+ }
17160
17406
  }
17161
17407
  }
17408
+ if (callHasThemeArg(call)) markAsRuntimeCall(call);
17162
17409
  return null;
17163
17410
  };
17164
17411
  const templateResult = resolveTemplateLiteralExpressions(b, (expr) => {
@@ -17239,7 +17486,7 @@ function tryResolveConditionalValue(node, ctx) {
17239
17486
  };
17240
17487
  const extractConditionInfo = (test) => {
17241
17488
  if (test.type !== "BinaryExpression" || test.operator !== "===" && test.operator !== "!==") return null;
17242
- const rhsRaw = literalToStaticValue(test.right);
17489
+ const rhsRaw = resolveStaticExpressionValue(test.right, ctx.enumValueMap);
17243
17490
  if (rhsRaw === null) return null;
17244
17491
  if (paramName && test.left.type === "MemberExpression") {
17245
17492
  const leftPath = getMemberPathFromIdentifier(test.left, paramName);
@@ -17389,7 +17636,7 @@ function tryResolveConditionalValue(node, ctx) {
17389
17636
  }
17390
17637
  }
17391
17638
  }
17392
- if (!cons || !alt) return null;
17639
+ if (!cons || !alt) return buildRuntimeCallResult();
17393
17640
  if (new Set([cons.usage, alt.usage]).size !== 1) return null;
17394
17641
  const usage = cons.usage;
17395
17642
  const variants = [{
@@ -17438,6 +17685,7 @@ function tryResolveConditionalValue(node, ctx) {
17438
17685
  };
17439
17686
  }
17440
17687
  }
17688
+ if (!cons || !alt) return buildRuntimeCallResult();
17441
17689
  }
17442
17690
  if (paramBindings?.kind === "destructured" && test.type === "Identifier" && typeof test.name === "string") {
17443
17691
  const resolvedProp = resolveIdentifierToPropName(test, paramBindings);
@@ -17475,6 +17723,7 @@ function tryResolveConditionalValue(node, ctx) {
17475
17723
  if (result) return result;
17476
17724
  }
17477
17725
  }
17726
+ if (!cons || !alt) return buildRuntimeCallResult();
17478
17727
  }
17479
17728
  }
17480
17729
  const condInfo = extractConditionInfo(test);
@@ -17520,7 +17769,7 @@ function tryResolveConditionalValue(node, ctx) {
17520
17769
  };
17521
17770
  }
17522
17771
  }
17523
- return null;
17772
+ return buildRuntimeCallResult();
17524
17773
  }
17525
17774
  function tryResolveConditionalCssBlock(node, ctx) {
17526
17775
  const expr = node.expr;
@@ -17600,7 +17849,7 @@ function tryResolveConditionalCssBlockTernary(node, ctx) {
17600
17849
  return null;
17601
17850
  }
17602
17851
  if (t.type === "BinaryExpression" && (t.operator === "===" || t.operator === "!==")) {
17603
- const rhsRaw = literalToStaticValue(t.right);
17852
+ const rhsRaw = resolveStaticExpressionValue(t.right, ctx.enumValueMap);
17604
17853
  if (rhsRaw === null) return null;
17605
17854
  const left = t.left;
17606
17855
  if (paramName && left?.type === "MemberExpression") {
@@ -17982,7 +18231,7 @@ function replaceThemeRefsWithHookVar(expr, paramName, info) {
17982
18231
  * Order matters: more-specific transforms first, then fall back to prop-access emission.
17983
18232
  */
17984
18233
  function resolveDynamicNode(node, ctx) {
17985
- return tryResolveThemeAccess(node, ctx) ?? tryResolveCallExpression(node, ctx) ?? tryResolveArrowFnHelperCallWithThemeArg(node, ctx) ?? tryResolveArrowFnCallWithConditionalArgs(node, ctx) ?? tryResolveConditionalValue(node, ctx) ?? tryResolveIndexedThemeWithPropFallback(node, ctx) ?? tryResolveConditionalCssBlockTernary(node, ctx) ?? tryResolveConditionalCssBlock(node, ctx) ?? tryResolveArrowFnCallWithSinglePropArg(node, ctx) ?? tryResolveThemeDependentTemplateLiteral(node, ctx) ?? tryResolveStyleFunctionFromTemplateLiteral(node) ?? tryResolveInlineStyleValueForNestedPropAccess(node) ?? tryResolvePropAccess(node) ?? tryResolveConditionalPropStyleFunction(node) ?? tryResolveInlineStyleValueForConditionalExpression(node) ?? tryResolveInlineStyleValueForLogicalExpression(node) ?? tryResolveInlineStyleValueFromArrowFn(node);
18234
+ return tryResolveThemeAccess(node, ctx) ?? tryResolveCallExpression(node, ctx) ?? tryResolveArrowFnHelperCallWithThemeArg(node, ctx) ?? tryResolveArrowFnCallWithConditionalArgs(node, ctx) ?? tryResolveConditionalValue(node, ctx) ?? tryResolveIndexedThemeWithPropFallback(node, ctx) ?? tryResolveConditionalCssBlockTernary(node, ctx) ?? tryResolveConditionalCssBlock(node, ctx) ?? tryResolveArrowFnCallWithSinglePropArg(node, ctx) ?? tryResolveThemeDependentTemplateLiteral(node, ctx) ?? tryResolveStyleFunctionFromTemplateLiteral(node) ?? tryResolveInlineStyleValueForNestedPropAccess(node) ?? tryResolvePropAccess(node) ?? tryResolveConditionalPropStyleFunction(node) ?? tryResolveArrowFnPropExpression(node) ?? tryResolveInlineStyleValueForConditionalExpression(node) ?? tryResolveInlineStyleValueForLogicalExpression(node) ?? tryResolveInlineStyleValueFromArrowFn(node);
17986
18235
  }
17987
18236
  function tryResolveThemeAccess(node, ctx) {
17988
18237
  const expr = node.expr;
@@ -18011,11 +18260,10 @@ function tryResolveThemeAccess(node, ctx) {
18011
18260
  type: "resolvedDirectional",
18012
18261
  directional: res.directional
18013
18262
  };
18014
- const resultExpr = appendLogicalFallback(expr.body, res.expr);
18015
- if (resultExpr === null) return null;
18263
+ if (hasNonLiteralLogicalFallback(expr.body)) return null;
18016
18264
  return {
18017
18265
  type: "resolvedValue",
18018
- expr: resultExpr,
18266
+ expr: res.expr,
18019
18267
  imports: res.imports
18020
18268
  };
18021
18269
  }
@@ -18028,8 +18276,8 @@ function tryResolveThemeAccess(node, ctx) {
18028
18276
  * (LogicalExpression with `??` or `||` and theme access on the left)
18029
18277
  *
18030
18278
  * Returns the MemberExpression node, or null if the pattern doesn't match.
18031
- * The fallback (right side of `??`/`||`) is preserved by the caller via
18032
- * `appendLogicalFallback` so users can review and delete it.
18279
+ * The fallback (right side of `??`/`||`) is dropped because StyleX `defineVars`
18280
+ * tokens always resolve at runtime.
18033
18281
  */
18034
18282
  function extractThemeMemberExpression(body) {
18035
18283
  if (!body || typeof body !== "object") return null;
@@ -18375,6 +18623,29 @@ function collectPropsFromExprTree(nodes, paramName) {
18375
18623
  props
18376
18624
  };
18377
18625
  }
18626
+ /**
18627
+ * Checks whether the param name is used as a bare identifier anywhere in the
18628
+ * expression tree (i.e., not as the `object` of a non-computed MemberExpression
18629
+ * like `props.X`). This detects patterns like `helper(props)` or computed access
18630
+ * `props[expr]` where the full props object is needed, which would break
18631
+ * `emitStyleFunctionFromPropsObject` since that handler only forwards a subset
18632
+ * of collected prop names.
18633
+ */
18634
+ function hasBareParamUsage(root, paramName) {
18635
+ const visit = (node, skipIdent) => {
18636
+ if (!node || typeof node !== "object") return false;
18637
+ if (Array.isArray(node)) return node.some((child) => visit(child, false));
18638
+ const n = node;
18639
+ if (n.type === "Identifier" && n.name === paramName && !skipIdent) return true;
18640
+ for (const key of Object.keys(n)) {
18641
+ if (key === "loc" || key === "comments") continue;
18642
+ const child = n[key];
18643
+ if (visit(child, (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") && !n.computed && (key === "object" || key === "property"))) return true;
18644
+ }
18645
+ return false;
18646
+ };
18647
+ return visit(root, false);
18648
+ }
18378
18649
  function tryResolveStyleFunctionFromTemplateLiteral(node) {
18379
18650
  if (!node.css.property) return null;
18380
18651
  const expr = node.expr;
@@ -18460,6 +18731,35 @@ function tryDecomposeConditionalBranches(condBody, paramName) {
18460
18731
  isStaticWhenFalse
18461
18732
  };
18462
18733
  }
18734
+ /**
18735
+ * Handles arrow functions whose body is a computational expression referencing props.
18736
+ *
18737
+ * Catches expression types not covered by specific handlers (e.g. BinaryExpression,
18738
+ * UnaryExpression) and emits a StyleX style function that takes the props object.
18739
+ *
18740
+ * Pattern: `(props) => props.$depth * 16 + 4`
18741
+ * Output: `(props) => ({ paddingLeft: \`${props.$depth * 16 + 4}px\` })`
18742
+ */
18743
+ function tryResolveArrowFnPropExpression(node) {
18744
+ if (!node.css.property) return null;
18745
+ const expr = node.expr;
18746
+ if (!isArrowFunctionExpression(expr)) return null;
18747
+ const paramName = getArrowFnSingleParamName(expr);
18748
+ if (!paramName) return null;
18749
+ const body = getFunctionBodyExpr(expr);
18750
+ if (!body) return null;
18751
+ const bodyType = body.type;
18752
+ if (bodyType !== "BinaryExpression" && bodyType !== "UnaryExpression") return null;
18753
+ if (hasThemeAccessInArrowFn(expr)) return null;
18754
+ if (hasBareParamUsage(body, paramName)) return null;
18755
+ const { hasUsableProps, hasNonTransientProps, props } = collectPropsFromExprTree([body], paramName);
18756
+ if (!hasUsableProps) return null;
18757
+ if (hasNonTransientProps && node.component.withConfig?.shouldForwardProp) return null;
18758
+ return {
18759
+ type: "emitStyleFunctionFromPropsObject",
18760
+ props
18761
+ };
18762
+ }
18463
18763
  function tryResolveInlineStyleValueForNestedPropAccess(node) {
18464
18764
  if (!node.css.property) return null;
18465
18765
  const expr = node.expr;
@@ -18544,23 +18844,6 @@ function hasBooleanBranch(node) {
18544
18844
  if (n.type === "Literal" && typeof n.value === "boolean") return true;
18545
18845
  return false;
18546
18846
  }
18547
- /**
18548
- * If `body` is a logical fallback expression (`X ?? "default"` / `X || "default"`),
18549
- * appends the operator and fallback literal to the resolved expression string.
18550
- *
18551
- * Returns `null` when the body IS a logical expression but the fallback is non-literal
18552
- * (e.g., `props.fallbackColor`, `null`, `undefined`), signalling to the caller that
18553
- * it's unsafe to drop the fallback — the caller should bail instead of resolving.
18554
- *
18555
- * Returns `resolvedExpr` unchanged when the body is NOT a logical expression (no
18556
- * fallback to preserve).
18557
- */
18558
- function appendLogicalFallback(body, resolvedExpr) {
18559
- if (!isLogicalExpressionNode(body) || body.operator !== "??" && body.operator !== "||") return resolvedExpr;
18560
- const fallback = literalToStaticValue(body.right);
18561
- if (fallback === null) return null;
18562
- return `${resolvedExpr} ${body.operator} ${JSON.stringify(fallback)}`;
18563
- }
18564
18847
 
18565
18848
  //#endregion
18566
18849
  //#region src/internal/lower-rules/template-literals.ts
@@ -20729,7 +21012,7 @@ const createValuePatternHandlers = (ctx) => {
20729
21012
  const p = leftPath[0];
20730
21013
  propName = propName ?? p;
20731
21014
  if (propName !== p) return false;
20732
- const rhs = literalToStaticValue(test.right);
21015
+ const rhs = resolveStaticExpressionValue(test.right, ctx.enumValueMap);
20733
21016
  if (rhs === null) return false;
20734
21017
  const retValue = readIfReturnValue(stmt);
20735
21018
  if (retValue === null) return false;
@@ -20892,7 +21175,7 @@ const createValuePatternHandlers = (ctx) => {
20892
21175
  * Core concepts: per-component style buckets, helper factories, and resolver wiring.
20893
21176
  */
20894
21177
  function createDeclProcessingState(state, decl) {
20895
- const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveValueDirectional, resolveCall, resolveCallOptional, resolveSelector, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, markBail } = state;
21178
+ const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveValueDirectional, resolveCall, resolveCallOptional, resolveSelector, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, enumValueMap, markBail } = state;
20896
21179
  const styleObj = {};
20897
21180
  const perPropPseudo = {};
20898
21181
  const perPropMedia = {};
@@ -20990,6 +21273,7 @@ function createDeclProcessingState(state, decl) {
20990
21273
  ...sharedFromState,
20991
21274
  api,
20992
21275
  importMap,
21276
+ enumValueMap,
20993
21277
  decl,
20994
21278
  styleObj,
20995
21279
  variantBuckets,
@@ -21009,7 +21293,8 @@ function createDeclProcessingState(state, decl) {
21009
21293
  resolveCall,
21010
21294
  resolveCallOptional,
21011
21295
  resolveImport: resolveImportInScope,
21012
- hasImportIgnoringShadowing: (localName) => importMap.has(localName)
21296
+ hasImportIgnoringShadowing: (localName) => importMap.has(localName),
21297
+ enumValueMap
21013
21298
  };
21014
21299
  const withConfig = decl.shouldForwardProp ? { shouldForwardProp: true } : void 0;
21015
21300
  const componentInfo = decl.base.kind === "intrinsic" ? {
@@ -21859,13 +22144,13 @@ function handleSplitVariantsResolvedValue(ctx) {
21859
22144
  }
21860
22145
  if (isHeterogeneousBackground) {
21861
22146
  const isNestedTernary = allPosParsed.length > 1;
22147
+ const defaultStylexProp = neg ? resolveBackgroundStylexProp(neg.expr) : null;
21862
22148
  if (neg && negParsed) {
21863
- const negStylexProp = resolveBackgroundStylexProp(neg.expr);
21864
- const bucket = { ...variantBuckets.get(neg.when) };
21865
- applyParsed(bucket, negParsed, negStylexProp);
21866
- variantBuckets.set(neg.when, bucket);
21867
- const suffix = toSuffixFromProp(neg.when);
21868
- variantStyleKeys[neg.when] ??= `${decl.styleKey}${suffix}`;
22149
+ if (!applyParsed(styleObj, negParsed, defaultStylexProp)) {
22150
+ bailUnsupported(decl, "Resolved conditional border variant could not be expanded to longhand properties");
22151
+ setBail();
22152
+ return true;
22153
+ }
21869
22154
  }
21870
22155
  for (let i = 0; i < allPosParsed.length; i++) {
21871
22156
  const { when, nameHint, parsed } = allPosParsed[i];
@@ -21874,6 +22159,7 @@ function handleSplitVariantsResolvedValue(ctx) {
21874
22159
  const whenClean = when.replace(/^!/, "");
21875
22160
  const bucket = { ...variantBuckets.get(whenClean) };
21876
22161
  applyParsed(bucket, parsed, posStylexProp);
22162
+ if (defaultStylexProp && posStylexProp !== defaultStylexProp) bucket[defaultStylexProp] = j.literal("transparent");
21877
22163
  variantBuckets.set(whenClean, bucket);
21878
22164
  const suffix = isNestedTernary && nameHint && !new Set([
21879
22165
  "truthy",
@@ -23477,6 +23763,12 @@ function handleInterpolatedDeclaration(args) {
23477
23763
  const { prefix, suffix } = extractStaticPartsForDecl(d);
23478
23764
  const valueExpr = prefix || suffix ? buildTemplateWithStaticParts(j, valueExprRaw, prefix, suffix) : valueExprRaw;
23479
23765
  const param = j.identifier(paramName);
23766
+ if (/\.(ts|tsx)$/.test(filePath)) {
23767
+ if (!(decl.propsType?.type === "TSTypeReference")) {
23768
+ const typeName = `${decl.localName}Props`;
23769
+ param.typeAnnotation = j.tsTypeAnnotation(j.tsTypeReference(j.identifier(typeName)));
23770
+ }
23771
+ }
23480
23772
  const body = j.objectExpression([j.property("init", makeCssPropKey(j, out.prop), buildPseudoMediaPropValue({
23481
23773
  j,
23482
23774
  valueExpr,
@@ -23485,18 +23777,10 @@ function handleInterpolatedDeclaration(args) {
23485
23777
  }))]);
23486
23778
  styleFnDecls.set(fnKey, j.arrowFunctionExpression([param], body));
23487
23779
  }
23488
- if (!styleFnFromProps.some((p) => p.fnKey === fnKey)) {
23489
- const callArg = j.objectExpression((res.props ?? []).map((propName) => {
23490
- const prop = j.property("init", j.identifier(propName), j.identifier(propName));
23491
- prop.shorthand = true;
23492
- return prop;
23493
- }));
23494
- styleFnFromProps.push({
23495
- fnKey,
23496
- jsxProp: "__props",
23497
- callArg
23498
- });
23499
- }
23780
+ if (!styleFnFromProps.some((p) => p.fnKey === fnKey)) styleFnFromProps.push({
23781
+ fnKey,
23782
+ jsxProp: "__props"
23783
+ });
23500
23784
  }
23501
23785
  continue;
23502
23786
  }
@@ -25560,11 +25844,195 @@ function finalizeDeclProcessing(ctx) {
25560
25844
  });
25561
25845
  decl.styleFnFromProps = styleFnFromProps;
25562
25846
  }
25563
- for (const [k, v] of styleFnDecls.entries()) resolvedStyleObjects.set(k, v);
25847
+ mergeBaseIntoSingleStyleFn({
25848
+ j: state.j,
25849
+ decl,
25850
+ styleObj,
25851
+ styleFnFromProps,
25852
+ styleFnDecls,
25853
+ remainingStyleKeys,
25854
+ extraStyleObjects,
25855
+ styledDecls: state.styledDecls
25856
+ });
25857
+ convertStyleFnsToPropsPattern(state.j, styleFnDecls, styleFnFromProps, decl.styleKey);
25858
+ insertStyleFnDeclsAfterComponent(resolvedStyleObjects, styleFnDecls, {
25859
+ styleKey: decl.styleKey,
25860
+ extraStyleObjects,
25861
+ remainingStyleKeys,
25862
+ attrBuckets,
25863
+ enumVariant: decl.enumVariant
25864
+ });
25564
25865
  if (styleFnDecls.has(decl.styleKey) && Object.keys(styleObj).length === 0) decl.skipBaseStyleRef = true;
25565
25866
  if (inlineStyleProps.length) decl.inlineStyleProps = inlineStyleProps;
25566
25867
  }
25567
25868
  /**
25869
+ * Inserts styleFnDecls entries into resolvedStyleObjects right after the last
25870
+ * entry belonging to the current component. This ensures dynamic style functions
25871
+ * appear adjacent to their static counterparts in stylex.create() output.
25872
+ */
25873
+ function insertStyleFnDeclsAfterComponent(resolvedStyleObjects, styleFnDecls, component) {
25874
+ if (styleFnDecls.size === 0) return;
25875
+ const componentKeys = /* @__PURE__ */ new Set();
25876
+ componentKeys.add(component.styleKey);
25877
+ for (const k of component.extraStyleObjects.keys()) componentKeys.add(k);
25878
+ for (const k of Object.values(component.remainingStyleKeys)) componentKeys.add(k);
25879
+ for (const k of component.attrBuckets.keys()) componentKeys.add(k);
25880
+ if (component.enumVariant) {
25881
+ componentKeys.add(component.enumVariant.baseKey);
25882
+ for (const c of component.enumVariant.cases) componentKeys.add(c.styleKey);
25883
+ }
25884
+ for (const k of styleFnDecls.keys()) if (resolvedStyleObjects.has(k)) componentKeys.add(k);
25885
+ let lastComponentKey = null;
25886
+ for (const k of resolvedStyleObjects.keys()) if (componentKeys.has(k)) lastComponentKey = k;
25887
+ if (lastComponentKey === null) {
25888
+ for (const [k, v] of styleFnDecls.entries()) resolvedStyleObjects.set(k, v);
25889
+ return;
25890
+ }
25891
+ const emittedFnKeys = /* @__PURE__ */ new Set();
25892
+ const entries = [...resolvedStyleObjects.entries()];
25893
+ resolvedStyleObjects.clear();
25894
+ for (const [k, v] of entries) {
25895
+ if (styleFnDecls.has(k)) {
25896
+ resolvedStyleObjects.set(k, styleFnDecls.get(k));
25897
+ emittedFnKeys.add(k);
25898
+ } else resolvedStyleObjects.set(k, v);
25899
+ if (k === lastComponentKey) {
25900
+ for (const [fk, fv] of styleFnDecls.entries()) if (!emittedFnKeys.has(fk)) {
25901
+ resolvedStyleObjects.set(fk, fv);
25902
+ emittedFnKeys.add(fk);
25903
+ }
25904
+ }
25905
+ }
25906
+ }
25907
+ /**
25908
+ * Merges static base properties into a single unconditional style function.
25909
+ *
25910
+ * When a styled component has both static CSS properties and a single
25911
+ * unconditional dynamic style function, the static properties are folded
25912
+ * into the function's return object so that the emitted code uses a single
25913
+ * `styles.key(arg)` call instead of separate `styles.key, styles.keyDynamic(arg)`.
25914
+ *
25915
+ * Preconditions:
25916
+ * - Exactly one unconditional styleFn entry (no conditionWhen)
25917
+ * - Base styleObj has at least one property
25918
+ * - No variant style keys, extra style objects, or enum variants
25919
+ * - The component is not extended by other styled components
25920
+ */
25921
+ function mergeBaseIntoSingleStyleFn(args) {
25922
+ const { j, decl, styleObj, styleFnFromProps, styleFnDecls, remainingStyleKeys, extraStyleObjects, styledDecls } = args;
25923
+ if (Object.keys(styleObj).length === 0) return;
25924
+ if (styleFnFromProps.length === 0) return;
25925
+ if (Object.keys(remainingStyleKeys).length > 0) return;
25926
+ if (extraStyleObjects.size > 0) return;
25927
+ if (decl.enumVariant) return;
25928
+ for (const other of styledDecls) if (other !== decl && other.extendsStyleKey === decl.styleKey) return;
25929
+ const unconditionalEntries = styleFnFromProps.filter((p) => !p.conditionWhen && p.condition === "always");
25930
+ if (unconditionalEntries.length !== 1) return;
25931
+ const entry = unconditionalEntries[0];
25932
+ const fnKey = entry.fnKey;
25933
+ const fnAst = styleFnDecls.get(fnKey);
25934
+ if (!fnAst || typeof fnAst !== "object") return;
25935
+ const body = getFunctionBodyExpr(fnAst);
25936
+ if (!body || body.type !== "ObjectExpression") return;
25937
+ const bodyObj = body;
25938
+ if (!Array.isArray(bodyObj.properties)) return;
25939
+ const existingKeys = /* @__PURE__ */ new Set();
25940
+ for (const prop of bodyObj.properties) {
25941
+ const key = prop.key;
25942
+ if (key) existingKeys.add(key.name ?? key.value ?? "");
25943
+ }
25944
+ if (Object.keys(styleObj).filter((k) => !k.startsWith("__")).some((k) => existingKeys.has(k))) return;
25945
+ const prependProps = [];
25946
+ for (const [cssProp, cssValue] of Object.entries(styleObj)) {
25947
+ if (cssProp.startsWith("__")) continue;
25948
+ const valueAst = literalToAst(j, cssValue);
25949
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(cssProp) ? j.identifier(cssProp) : j.literal(cssProp);
25950
+ prependProps.push(j.property("init", key, valueAst));
25951
+ }
25952
+ bodyObj.properties.unshift(...prependProps);
25953
+ if (fnKey !== decl.styleKey) {
25954
+ styleFnDecls.delete(fnKey);
25955
+ styleFnDecls.set(decl.styleKey, fnAst);
25956
+ entry.fnKey = decl.styleKey;
25957
+ }
25958
+ for (const key of Object.keys(styleObj)) delete styleObj[key];
25959
+ }
25960
+ /**
25961
+ * Converts single-positional-param style functions to use a named `props`
25962
+ * object parameter. Skips functions that already use a `props` parameter
25963
+ * (e.g. consolidated multi-param functions).
25964
+ *
25965
+ * Before: `(color: string) => ({ color })`
25966
+ * After: `(props: { color: string }) => ({ color: props.color })`
25967
+ */
25968
+ function convertStyleFnsToPropsPattern(j, styleFnDecls, styleFnFromProps, baseStyleKey) {
25969
+ const managedFnKeys = new Set(styleFnFromProps.map((p) => p.fnKey));
25970
+ for (const [fnKey, fnAst] of styleFnDecls.entries()) {
25971
+ if (fnKey !== baseStyleKey) continue;
25972
+ if (!managedFnKeys.has(fnKey)) continue;
25973
+ if (!fnAst || typeof fnAst !== "object") continue;
25974
+ const fn = fnAst;
25975
+ if (!Array.isArray(fn.params) || fn.params.length !== 1) continue;
25976
+ const param = fn.params[0];
25977
+ if (param.type !== "Identifier" || !param.name || param.name === "props") continue;
25978
+ const paramName = param.name;
25979
+ const paramTypeAnnotation = param.typeAnnotation;
25980
+ const body = getFunctionBodyExpr(fn);
25981
+ if (!body || body.type !== "ObjectExpression") continue;
25982
+ replaceIdentifierInAst(j, body, paramName);
25983
+ const propsParam = j.identifier("props");
25984
+ if (paramTypeAnnotation) {
25985
+ const innerType = paramTypeAnnotation.typeAnnotation;
25986
+ if (innerType) {
25987
+ const propSignature = j.tsPropertySignature(j.identifier(paramName), j.tsTypeAnnotation(innerType));
25988
+ propsParam.typeAnnotation = j.tsTypeAnnotation(j.tsTypeLiteral([propSignature]));
25989
+ }
25990
+ }
25991
+ fn.params[0] = propsParam;
25992
+ for (const entry of styleFnFromProps) if (entry.fnKey === fnKey && !entry.propsObjectKey) entry.propsObjectKey = paramName;
25993
+ }
25994
+ }
25995
+ /**
25996
+ * Recursively replaces all `Identifier` references matching `oldName` with
25997
+ * `props.oldName` (a MemberExpression). Handles shorthand properties by
25998
+ * un-shorthanding them.
25999
+ */
26000
+ function replaceIdentifierInAst(j, node, oldName) {
26001
+ if (!node || typeof node !== "object") return;
26002
+ const n = node;
26003
+ if (n.type === "ObjectExpression") {
26004
+ const properties = n.properties;
26005
+ if (!Array.isArray(properties)) return;
26006
+ for (const prop of properties) {
26007
+ if (prop.type === "SpreadElement" || prop.type === "SpreadProperty") {
26008
+ if (prop.argument?.type === "Identifier" && prop.argument.name === oldName) prop.argument = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26009
+ else replaceIdentifierInAst(j, prop.argument, oldName);
26010
+ continue;
26011
+ }
26012
+ if (prop.type !== "Property") continue;
26013
+ if (prop.shorthand && prop.value?.type === "Identifier" && prop.value.name === oldName) {
26014
+ prop.shorthand = false;
26015
+ prop.value = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26016
+ continue;
26017
+ }
26018
+ if (prop.computed) if (prop.key?.type === "Identifier" && prop.key.name === oldName) prop.key = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26019
+ else replaceIdentifierInAst(j, prop.key, oldName);
26020
+ if (prop.value?.type === "Identifier" && prop.value.name === oldName) prop.value = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26021
+ else replaceIdentifierInAst(j, prop.value, oldName);
26022
+ }
26023
+ return;
26024
+ }
26025
+ for (const key of Object.keys(n)) {
26026
+ if (key === "type" || key === "loc" || key === "start" || key === "end" || key === "comments") continue;
26027
+ if (key === "property" && n.type === "MemberExpression" && !n.computed) continue;
26028
+ const child = n[key];
26029
+ if (Array.isArray(child)) for (let i = 0; i < child.length; i++) if (child[i]?.type === "Identifier" && child[i].name === oldName) child[i] = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26030
+ else replaceIdentifierInAst(j, child[i], oldName);
26031
+ else if (child?.type === "Identifier" && child.name === oldName) n[key] = j.memberExpression(j.identifier("props"), j.identifier(oldName));
26032
+ else if (child && typeof child === "object" && child.type) replaceIdentifierInAst(j, child, oldName);
26033
+ }
26034
+ }
26035
+ /**
25568
26036
  * Merges a per-property condition bucket (pseudo or media) into the style object.
25569
26037
  * When a property already exists as an object in styleObj, merges entries to
25570
26038
  * preserve both pseudo-class and media query entries on the same property.
@@ -26623,123 +27091,11 @@ function appendToMapList(map, key, value) {
26623
27091
  else map.set(key, [value]);
26624
27092
  }
26625
27093
 
26626
- //#endregion
26627
- //#region src/internal/post-process/event-handler-annotations.ts
26628
- /** Maps event handler prop names to their React event type. */
26629
- const EVENT_TYPE_MAP = {
26630
- onClick: "React.MouseEvent",
26631
- onContextMenu: "React.MouseEvent",
26632
- onMouseDown: "React.MouseEvent",
26633
- onMouseUp: "React.MouseEvent",
26634
- onMouseEnter: "React.MouseEvent",
26635
- onMouseLeave: "React.MouseEvent",
26636
- onMouseMove: "React.MouseEvent",
26637
- onMouseOver: "React.MouseEvent",
26638
- onDoubleClick: "React.MouseEvent",
26639
- onKeyDown: "React.KeyboardEvent",
26640
- onKeyUp: "React.KeyboardEvent",
26641
- onKeyPress: "React.KeyboardEvent",
26642
- onChange: "React.ChangeEvent",
26643
- onInput: "React.FormEvent",
26644
- onFocus: "React.FocusEvent",
26645
- onBlur: "React.FocusEvent",
26646
- onSubmit: "React.FormEvent",
26647
- onScroll: "React.UIEvent",
26648
- onWheel: "React.WheelEvent",
26649
- onDragStart: "React.DragEvent",
26650
- onDragEnd: "React.DragEvent",
26651
- onDrop: "React.DragEvent",
26652
- onDragOver: "React.DragEvent",
26653
- onTouchStart: "React.TouchEvent",
26654
- onTouchEnd: "React.TouchEvent",
26655
- onTouchMove: "React.TouchEvent",
26656
- onPointerDown: "React.PointerEvent",
26657
- onPointerUp: "React.PointerEvent",
26658
- onPointerMove: "React.PointerEvent",
26659
- onCopy: "React.ClipboardEvent",
26660
- onCut: "React.ClipboardEvent",
26661
- onPaste: "React.ClipboardEvent",
26662
- onAnimationEnd: "React.AnimationEvent",
26663
- onAnimationStart: "React.AnimationEvent",
26664
- onTransitionEnd: "React.TransitionEvent"
26665
- };
26666
- /** Maps intrinsic JSX tag names to their DOM element interface names. */
26667
- const INTRINSIC_TAG_TO_ELEMENT_TYPE = {
26668
- a: "HTMLAnchorElement",
26669
- button: "HTMLButtonElement",
26670
- div: "HTMLDivElement",
26671
- form: "HTMLFormElement",
26672
- img: "HTMLImageElement",
26673
- input: "HTMLInputElement",
26674
- label: "HTMLLabelElement",
26675
- li: "HTMLLIElement",
26676
- ol: "HTMLOListElement",
26677
- option: "HTMLOptionElement",
26678
- select: "HTMLSelectElement",
26679
- span: "HTMLSpanElement",
26680
- svg: "SVGSVGElement",
26681
- table: "HTMLTableElement",
26682
- tbody: "HTMLTableSectionElement",
26683
- td: "HTMLTableCellElement",
26684
- textarea: "HTMLTextAreaElement",
26685
- th: "HTMLTableCellElement",
26686
- thead: "HTMLTableSectionElement",
26687
- tr: "HTMLTableRowElement",
26688
- ul: "HTMLUListElement"
26689
- };
26690
- /**
26691
- * Annotates event handler arrow function parameters at JSX usage sites of converted components.
26692
- *
26693
- * `componentTagMap` is best-effort and only populated for intrinsic-base conversions.
26694
- * For wrappers around non-intrinsic components, event annotations stay non-generic.
26695
- *
26696
- * @returns true if any annotations were added
26697
- */
26698
- function annotateEventHandlerParams(args) {
26699
- const { root, j, convertedNames, componentTagMap } = args;
26700
- if (convertedNames.size === 0) return false;
26701
- let changed = false;
26702
- root.find(j.JSXOpeningElement).filter((path) => {
26703
- const name = path.node.name;
26704
- if (name.type === "JSXIdentifier") return convertedNames.has(name.name);
26705
- return false;
26706
- }).forEach((jsxPath) => {
26707
- const openingName = jsxPath.node.name;
26708
- const componentName = openingName.type === "JSXIdentifier" ? openingName.name : null;
26709
- const intrinsicTag = componentName ? componentTagMap.get(componentName) : void 0;
26710
- const elementType = intrinsicTag ? INTRINSIC_TAG_TO_ELEMENT_TYPE[intrinsicTag] : void 0;
26711
- for (const attr of jsxPath.node.attributes ?? []) {
26712
- if (attr.type !== "JSXAttribute" || !attr.name || attr.name.type !== "JSXIdentifier") continue;
26713
- const eventType = EVENT_TYPE_MAP[attr.name.name];
26714
- if (!eventType) continue;
26715
- const value = attr.value;
26716
- if (!value || value.type !== "JSXExpressionContainer") continue;
26717
- const expr = value.expression;
26718
- if (!expr || expr.type !== "ArrowFunctionExpression") continue;
26719
- const firstParam = expr.params[0];
26720
- if (!firstParam) continue;
26721
- if (firstParam.typeAnnotation) continue;
26722
- if (firstParam.type !== "Identifier") continue;
26723
- const parts = eventType.split(".");
26724
- const typeRef = j.tsTypeReference(j.tsQualifiedName(j.identifier(parts[0]), j.identifier(parts[1])));
26725
- if (elementType) typeRef.typeParameters = j.tsTypeParameterInstantiation([j.tsTypeReference(j.identifier(elementType))]);
26726
- const annotatedParam = j.identifier(firstParam.name);
26727
- annotatedParam.typeAnnotation = j.tsTypeAnnotation(typeRef);
26728
- const newArrow = j.arrowFunctionExpression([annotatedParam, ...expr.params.slice(1)], expr.body, expr.expression);
26729
- newArrow.async = expr.async ?? false;
26730
- newArrow.returnType = expr.returnType ?? null;
26731
- value.expression = newArrow;
26732
- changed = true;
26733
- }
26734
- });
26735
- return changed;
26736
- }
26737
-
26738
27094
  //#endregion
26739
27095
  //#region src/internal/transform-steps/post-process.ts
26740
27096
  /**
26741
27097
  * Step: post-process transformed AST and cleanup imports.
26742
- * Core concepts: relation overrides, import reconciliation, and event handler annotations.
27098
+ * Core concepts: relation overrides and import reconciliation.
26743
27099
  */
26744
27100
  /**
26745
27101
  * Performs post-processing rewrites, import cleanup, and descendant/ancestor selector adjustments.
@@ -26799,34 +27155,7 @@ function postProcessStep(ctx) {
26799
27155
  return true;
26800
27156
  }).size() === 0) fnPaths.forEach((p) => p.prune());
26801
27157
  }
26802
- if (/\.(ts|tsx)$/.test(file.path)) {
26803
- const hasLocalAsUsage = (componentName) => {
26804
- const hasAsAttr = (attrs) => (attrs ?? []).some((attr) => attr?.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && (attr.name.name === "as" || attr.name.name === "forwardedAs"));
26805
- if (root.find(j.JSXElement, { openingElement: { name: {
26806
- type: "JSXIdentifier",
26807
- name: componentName
26808
- } } }).some((p) => hasAsAttr(p.node?.openingElement?.attributes))) return true;
26809
- return root.find(j.JSXSelfClosingElement, { name: {
26810
- type: "JSXIdentifier",
26811
- name: componentName
26812
- } }).some((p) => hasAsAttr(p.node?.attributes));
26813
- };
26814
- const convertedNames = /* @__PURE__ */ new Set();
26815
- const componentTagMap = /* @__PURE__ */ new Map();
26816
- for (const decl of styledDecls) {
26817
- const isPolymorphic = !!decl.supportsAsProp || hasLocalAsUsage(decl.localName);
26818
- if (decl.base.kind === "intrinsic" && !isPolymorphic) {
26819
- convertedNames.add(decl.localName);
26820
- componentTagMap.set(decl.localName, decl.base.tagName);
26821
- }
26822
- }
26823
- if (annotateEventHandlerParams({
26824
- root,
26825
- j,
26826
- convertedNames,
26827
- componentTagMap
26828
- })) ctx.markChanged();
26829
- }
27158
+ if (/\.(ts|tsx)$/.test(file.path)) {}
26830
27159
  return CONTINUE;
26831
27160
  }
26832
27161
 
@@ -26921,13 +27250,16 @@ function rewriteJsxStep(ctx) {
26921
27250
  if (renamed) attr.name.name = renamed;
26922
27251
  }
26923
27252
  });
26924
- continue;
27253
+ if (!decl.promotedStyleProps?.length) continue;
26925
27254
  }
26926
27255
  root.find(j.JSXElement, { openingElement: { name: {
26927
27256
  type: "JSXIdentifier",
26928
27257
  name: decl.localName
26929
27258
  } } }).forEach((p) => {
26930
27259
  const opening = p.node.openingElement;
27260
+ if (decl.needsWrapperComponent) {
27261
+ if (!(opening.__promotedStyleKey && !opening.__promotedMergeIntoBase || opening.__promotedMergeArgs)) return;
27262
+ }
26931
27263
  const closing = p.node.closingElement;
26932
27264
  let finalTag = decl.base.kind === "intrinsic" ? decl.base.tagName : decl.base.ident;
26933
27265
  const inlineVariantDimensions = decl.inlinedBaseComponent?.hasInlineJsxVariants ? decl.variantDimensions ?? [] : [];
@@ -27049,10 +27381,13 @@ function rewriteJsxStep(ctx) {
27049
27381
  }
27050
27382
  }
27051
27383
  const baseStyleKey = matchedCombinedStyleKey ?? decl.styleKey;
27384
+ const mergeArgs = matchedCombinedStyleKey ? void 0 : opening.__promotedMergeArgs;
27385
+ const baseMember = j.memberExpression(j.identifier(ctx.stylesIdentifier ?? "styles"), j.identifier(baseStyleKey));
27386
+ const baseExpr = mergeArgs ? j.callExpression(baseMember, mergeArgs) : baseMember;
27052
27387
  const styleArgs = [
27053
27388
  ...decl.extendsStyleKey ? [j.memberExpression(j.identifier(ctx.stylesIdentifier ?? "styles"), j.identifier(decl.extendsStyleKey))] : [],
27054
27389
  ...extraMixinArgs,
27055
- j.memberExpression(j.identifier(ctx.stylesIdentifier ?? "styles"), j.identifier(baseStyleKey)),
27390
+ baseExpr,
27056
27391
  ...extraAfterBaseArgs
27057
27392
  ];
27058
27393
  const variantKeys = decl.variantStyleKeys ?? {};
@@ -27095,7 +27430,10 @@ function rewriteJsxStep(ctx) {
27095
27430
  if (styleFnProps.has(n)) {
27096
27431
  const pairs = styleFnPairs.filter((p) => p.jsxProp === n);
27097
27432
  const valueExpr = !attr.value ? j.literal(true) : attr.value.type === "StringLiteral" ? j.literal(attr.value.value) : attr.value.type === "Literal" ? j.literal(attr.value.value) : attr.value.type === "JSXExpressionContainer" ? attr.value.expression : null;
27098
- if (valueExpr) for (const p of pairs) styleArgs.push(j.callExpression(j.memberExpression(j.identifier(ctx.stylesIdentifier ?? "styles"), j.identifier(p.fnKey)), [valueExpr]));
27433
+ if (valueExpr) for (const p of pairs) {
27434
+ const callArg = wrapCallArgForPropsObject(j, valueExpr, p.propsObjectKey);
27435
+ styleArgs.push(j.callExpression(j.memberExpression(j.identifier(ctx.stylesIdentifier ?? "styles"), j.identifier(p.fnKey)), [callArg]));
27436
+ }
27099
27437
  return;
27100
27438
  }
27101
27439
  if (combinedStylePropNames.has(n) && matchedCombinedStyleKey) return;