styled-components-to-stylex-codemod 0.0.13 → 0.0.15

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.
@@ -1,6 +1,7 @@
1
- import { n as assertValidAdapter, t as Logger } from "./logger-D3j-qxgZ.mjs";
2
- import path, { dirname, resolve } from "node:path";
3
- import { existsSync, readFileSync } from "node:fs";
1
+ import { n as assertValidAdapter, t as Logger } from "./logger-D-R2KB6I.mjs";
2
+ import { n as parseStyledTemplateLiteral, t as PLACEHOLDER_RE } from "./styled-css-DBryFqQM.mjs";
3
+ import path, { basename, dirname, join, resolve } from "node:path";
4
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
4
5
  import { compile } from "stylis";
5
6
  import valueParser from "postcss-value-parser";
6
7
  import selectorParser from "postcss-selector-parser";
@@ -531,6 +532,19 @@ function isLogicalExpressionNode(node) {
531
532
  return !!node && typeof node === "object" && node.type === "LogicalExpression";
532
533
  }
533
534
  /**
535
+ * Extracts the left side of a `??` or `||` logical expression when it's a
536
+ * MemberExpression or OptionalMemberExpression. Returns null if the pattern
537
+ * doesn't match.
538
+ *
539
+ * Used to extract the theme access part from fallback patterns like
540
+ * `props.theme.color.labelBase ?? "black"` for resolution. Callers are
541
+ * responsible for re-wrapping the resolved value with the original fallback.
542
+ */
543
+ function unwrapLogicalFallback(expr) {
544
+ if (isLogicalExpressionNode(expr) && (expr.operator === "??" || expr.operator === "||") && isMemberOrOptionalMemberExpression(expr.left)) return expr.left;
545
+ return null;
546
+ }
547
+ /**
534
548
  * Type guard for ConditionalExpression nodes.
535
549
  */
536
550
  function isConditionalExpressionNode(node) {
@@ -569,6 +583,11 @@ function isJsxIdentifierNode(node) {
569
583
  const typed = node;
570
584
  return typed.type === "JSXIdentifier" && typeof typed.name === "string";
571
585
  }
586
+ function isMemberOrOptionalMemberExpression(node) {
587
+ if (!node || typeof node !== "object") return false;
588
+ const t = node.type;
589
+ return t === "MemberExpression" || t === "OptionalMemberExpression";
590
+ }
572
591
 
573
592
  //#endregion
574
593
  //#region src/internal/transform-css-vars.ts
@@ -715,6 +734,16 @@ var TransformContext = class {
715
734
  newImportLocalNames;
716
735
  newImportSourcesByLocal;
717
736
  needsReactImport;
737
+ /** Cross-file selector usages where this file is the consumer */
738
+ crossFileSelectorUsages;
739
+ /** Component names in this file that need a global selector bridge className */
740
+ bridgeComponentNames;
741
+ /** Marker variable names generated for cross-file parent components (parentStyleKey → markerName) */
742
+ crossFileMarkers;
743
+ /** Content for the sidecar .stylex.ts file (defineMarker declarations), populated by emitStylesStep */
744
+ sidecarStylexContent;
745
+ /** Bridge components emitted for unconverted consumer selectors. */
746
+ bridgeResults;
718
747
  /** Inline @keyframes extracted from styled component templates: JS identifier name → frame objects */
719
748
  inlineKeyframes;
720
749
  /** Maps CSS @keyframes names to sanitized JS identifier names (e.g. "fade-in" → "fadeIn") */
@@ -767,6 +796,10 @@ var TransformContext = class {
767
796
  this.styledLocalNames = /* @__PURE__ */ new Set();
768
797
  this.isStyledTag = () => false;
769
798
  this.keyframesNames = /* @__PURE__ */ new Set();
799
+ if (options.crossFileInfo) {
800
+ this.crossFileSelectorUsages = options.crossFileInfo.selectorUsages;
801
+ this.bridgeComponentNames = options.crossFileInfo.bridgeComponentNames;
802
+ }
770
803
  }
771
804
  markChanged() {
772
805
  this.hasChanges = true;
@@ -1080,6 +1113,42 @@ function hasAttrsAsOverride(attrsInfo) {
1080
1113
  return !!(attrsInfo?.attrsAsTag || attrsInfo?.staticAttrs?.as && typeof attrsInfo.staticAttrs.as === "string");
1081
1114
  }
1082
1115
 
1116
+ //#endregion
1117
+ //#region src/internal/utilities/bridge-classname.ts
1118
+ /**
1119
+ * Utilities for generating deterministic bridge classNames and export names.
1120
+ * Used when a converted component needs to remain targetable by unconverted
1121
+ * styled-components consumers via CSS selectors.
1122
+ */
1123
+ /**
1124
+ * Simple FNV-1a hash producing an 8-char hex string.
1125
+ * Not cryptographic — just deterministic and collision-resistant enough
1126
+ * for generating stable CSS class names. Works in both Node.js and browsers.
1127
+ */
1128
+ function fnv1aHex(input) {
1129
+ let hash = 2166136261;
1130
+ for (let i = 0; i < input.length; i++) {
1131
+ hash ^= input.charCodeAt(i);
1132
+ hash = Math.imul(hash, 16777619);
1133
+ }
1134
+ return (hash >>> 0).toString(16).padStart(8, "0");
1135
+ }
1136
+ /**
1137
+ * Generate a deterministic, stable CSS class name for a bridge component.
1138
+ * The hash is derived from the file path + component name so it's consistent
1139
+ * across runs but unique across different components.
1140
+ */
1141
+ function generateBridgeClassName(filePath, componentName) {
1142
+ return `sc2sx-${componentName}-${fnv1aHex(`${filePath}:${componentName}`)}`;
1143
+ }
1144
+ /**
1145
+ * Generate the export variable name for a bridge component's global selector.
1146
+ * E.g., "Foo" → "FooGlobalSelector"
1147
+ */
1148
+ function bridgeExportName(componentName) {
1149
+ return `${componentName}GlobalSelector`;
1150
+ }
1151
+
1083
1152
  //#endregion
1084
1153
  //#region src/internal/transform-steps/analyze-before-emit.ts
1085
1154
  /**
@@ -1163,6 +1232,15 @@ function analyzeBeforeEmitStep(ctx) {
1163
1232
  if (decl.base.kind === "intrinsic" && decl.withConfig?.componentId) decl.needsWrapperComponent = true;
1164
1233
  if (extendedBy.has(decl.localName) && componentsWithStaticProps.has(decl.localName)) decl.needsWrapperComponent = true;
1165
1234
  if (exportedComponents.has(decl.localName)) decl.needsWrapperComponent = true;
1235
+ if (ctx.bridgeComponentNames?.has(decl.localName) || ctx.bridgeComponentNames?.has("default") && exportedComponents.get(decl.localName)?.isDefault) {
1236
+ decl.bridgeClassName = generateBridgeClassName(resolve(file.path), decl.localName);
1237
+ if (!decl.attrsInfo) decl.attrsInfo = {
1238
+ staticAttrs: {},
1239
+ conditionalAttrs: []
1240
+ };
1241
+ const existing = typeof decl.attrsInfo.staticAttrs.className === "string" ? decl.attrsInfo.staticAttrs.className + " " : "";
1242
+ decl.attrsInfo.staticAttrs.className = existing + decl.bridgeClassName;
1243
+ }
1166
1244
  }
1167
1245
  const isUsedInJsx = (name) => isComponentUsedInJsx(root, j, name);
1168
1246
  const canInlineImportedComponentWrapper = (decl) => {
@@ -1776,7 +1854,7 @@ function parseCssValue(valueRaw, slotByPlaceholder) {
1776
1854
  slotId: directSlot
1777
1855
  }]
1778
1856
  };
1779
- const placeholderPattern = /__SC_EXPR_(\d+)__/g;
1857
+ const placeholderPattern = new RegExp(PLACEHOLDER_RE.source, "g");
1780
1858
  const parts = [];
1781
1859
  let lastIndex = 0;
1782
1860
  let match;
@@ -1897,7 +1975,7 @@ function findSelectorLineOffset(rawCss, selector) {
1897
1975
  const idx = rawCss.indexOf(pattern);
1898
1976
  if (idx !== -1) return countNewlinesBefore(rawCss, idx);
1899
1977
  }
1900
- const exprMatch = selector.match(/__SC_EXPR_\d+__/);
1978
+ const exprMatch = selector.match(PLACEHOLDER_RE);
1901
1979
  if (exprMatch) {
1902
1980
  const idx = rawCss.indexOf(exprMatch[0]);
1903
1981
  if (idx !== -1) return countNewlinesBefore(rawCss, idx);
@@ -1907,6 +1985,11 @@ function findSelectorLineOffset(rawCss, selector) {
1907
1985
  const idx = rawCss.indexOf(classMatch[0]);
1908
1986
  if (idx !== -1) return countNewlinesBefore(rawCss, idx);
1909
1987
  }
1988
+ const stripped = selector.replace(/^&\s+/, "").replace(/^&\s*>\s*/, "");
1989
+ if (stripped !== selector) {
1990
+ const tagMatch = rawCss.match(new RegExp(`(?:^|\\s)${escapeRegExp(stripped)}\\s*[{,]`, "m"));
1991
+ if (tagMatch) return countNewlinesBefore(rawCss, tagMatch.index + 1);
1992
+ }
1910
1993
  return 0;
1911
1994
  }
1912
1995
  /**
@@ -1931,39 +2014,6 @@ function escapeRegExp(str) {
1931
2014
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1932
2015
  }
1933
2016
 
1934
- //#endregion
1935
- //#region src/internal/styled-css.ts
1936
- function parseStyledTemplateLiteral(template) {
1937
- const parts = [];
1938
- const slots = [];
1939
- for (let i = 0; i < template.quasis.length; i++) {
1940
- const quasi = template.quasis[i];
1941
- parts.push(quasi.value.raw);
1942
- const expr = template.expressions[i];
1943
- if (!expr) continue;
1944
- const placeholder = makeInterpolationPlaceholder(i);
1945
- const startOffset = parts.join("").length;
1946
- parts.push(placeholder);
1947
- const endOffset = parts.join("").length;
1948
- slots.push({
1949
- index: i,
1950
- placeholder,
1951
- expression: expr,
1952
- startOffset,
1953
- endOffset
1954
- });
1955
- }
1956
- const rawCss = parts.join("");
1957
- return {
1958
- rawCss,
1959
- slots,
1960
- stylisAst: compile(rawCss)
1961
- };
1962
- }
1963
- function makeInterpolationPlaceholder(index) {
1964
- return `__SC_EXPR_${index}__`;
1965
- }
1966
-
1967
2017
  //#endregion
1968
2018
  //#region src/internal/transform/css-helpers.ts
1969
2019
  function removeInlinedCssHelperFunctions(args) {
@@ -5952,6 +6002,7 @@ function emitStylesStep(ctx) {
5952
6002
  const { emptyStyleKeys } = emitStylesAndImports(ctx);
5953
6003
  ctx.emptyStyleKeys = emptyStyleKeys;
5954
6004
  ctx.markChanged();
6005
+ if (ctx.crossFileMarkers && ctx.crossFileMarkers.size > 0) emitDefineMarkerDeclarations(ctx, ctx.crossFileMarkers);
5955
6006
  if (ctx.resolverImportAliases && ctx.resolverImportAliases.size > 0) {
5956
6007
  const renameIdentifier = (node, parent) => {
5957
6008
  if (!node || typeof node !== "object") return;
@@ -5992,6 +6043,57 @@ function emitStylesStep(ctx) {
5992
6043
  }
5993
6044
  return CONTINUE;
5994
6045
  }
6046
+ /**
6047
+ * Emit defineMarker declarations into a sidecar `.stylex.ts` file and insert
6048
+ * an import for the markers into the main file. StyleX requires defineMarker()
6049
+ * to live in `.stylex.ts` files for the babel plugin's `fileNameForHashing`.
6050
+ */
6051
+ function emitDefineMarkerDeclarations(ctx, crossFileMarkers) {
6052
+ const j = ctx.j;
6053
+ const markerNames = [...crossFileMarkers.values()];
6054
+ ctx.sidecarStylexContent = `import * as stylex from "@stylexjs/stylex";\n\n${markerNames.map((name) => `export const ${name} = stylex.defineMarker();`).join("\n")}\n`;
6055
+ const sidecarImportPath = `./${basename(ctx.file.path).replace(/\.\w+$/, "")}.stylex`;
6056
+ const importDecl = j.importDeclaration(markerNames.map((name) => j.importSpecifier(j.identifier(name))), j.literal(sidecarImportPath));
6057
+ const programBody = ctx.root.get().node.program.body;
6058
+ const lastImportIdx = programBody.reduce((last, node, i) => node?.type === "ImportDeclaration" ? i : last, -1);
6059
+ const insertAt = lastImportIdx >= 0 ? lastImportIdx + 1 : 0;
6060
+ programBody.splice(insertAt, 0, importDecl);
6061
+ }
6062
+
6063
+ //#endregion
6064
+ //#region src/internal/transform-steps/emit-bridge-exports.ts
6065
+ /**
6066
+ * Step: emit bridge global selector exports for components targeted by
6067
+ * unconverted styled-components consumers.
6068
+ *
6069
+ * For each styled component with a `bridgeClassName`, emits:
6070
+ * export const FooGlobalSelector = ".sc2sx-Foo-a1b2c3";
6071
+ */
6072
+ function emitBridgeExportsStep(ctx) {
6073
+ const styledDecls = ctx.styledDecls;
6074
+ if (!styledDecls) return CONTINUE;
6075
+ const bridgeDecls = styledDecls.filter((d) => !!d.bridgeClassName);
6076
+ if (bridgeDecls.length === 0) return CONTINUE;
6077
+ const { j, root } = ctx;
6078
+ const bridgeResults = [];
6079
+ for (const decl of bridgeDecls) {
6080
+ const varName = bridgeExportName(decl.localName);
6081
+ const className = decl.bridgeClassName;
6082
+ const declaration = j.variableDeclaration("const", [j.variableDeclarator(j.identifier(varName), j.stringLiteral(`.${className}`))]);
6083
+ const exportDecl = j.exportNamedDeclaration(declaration);
6084
+ exportDecl.comments = [j.commentBlock("* @deprecated Migrate consumer to stylex.defineMarker() — bridge className for unconverted styled-components consumers ", true, false)];
6085
+ root.find(j.Program).get().node.body.push(exportDecl);
6086
+ bridgeResults.push({
6087
+ componentName: decl.localName,
6088
+ exportName: ctx.exportedComponents?.get(decl.localName)?.exportName,
6089
+ className,
6090
+ globalSelectorVarName: varName
6091
+ });
6092
+ }
6093
+ ctx.bridgeResults = bridgeResults;
6094
+ ctx.markChanged();
6095
+ return CONTINUE;
6096
+ }
5995
6097
 
5996
6098
  //#endregion
5997
6099
  //#region src/internal/emit-wrappers/style-merger.ts
@@ -6011,7 +6113,7 @@ function emitStylesStep(ctx) {
6011
6113
  */
6012
6114
  function emitStyleMerging(args) {
6013
6115
  const { j, emitter, styleArgs: rawStyleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps = [], staticClassNameExpr } = args;
6014
- const { styleMerger, emptyStyleKeys, stylesIdentifier, ancestorSelectorParents } = emitter;
6116
+ const { styleMerger, emptyStyleKeys, stylesIdentifier, ancestorSelectorParents, emitTypes } = emitter;
6015
6117
  const styleArgs = filterEmptyStyleArgs({
6016
6118
  styleArgs: rawStyleArgs,
6017
6119
  emptyStyleKeys,
@@ -6019,7 +6121,7 @@ function emitStyleMerging(args) {
6019
6121
  ancestorSelectorParents
6020
6122
  });
6021
6123
  if (ancestorSelectorParents && ancestorSelectorParents.size > 0) {
6022
- if (styleArgs.some((arg) => arg?.type === "MemberExpression" && arg.object?.type === "Identifier" && arg.object.name === stylesIdentifier && arg.property?.type === "Identifier" && ancestorSelectorParents.has(arg.property.name))) styleArgs.push(j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("defaultMarker")), []));
6124
+ if (styleArgs.some((arg) => hasStyleArgKey(arg, stylesIdentifier, ancestorSelectorParents))) styleArgs.push(j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("defaultMarker")), []));
6023
6125
  }
6024
6126
  if (styleArgs.length === 0) return emitWithoutStylex({
6025
6127
  j,
@@ -6028,7 +6130,8 @@ function emitStyleMerging(args) {
6028
6130
  allowClassNameProp,
6029
6131
  allowStyleProp,
6030
6132
  inlineStyleProps,
6031
- staticClassNameExpr
6133
+ staticClassNameExpr,
6134
+ emitTypes
6032
6135
  });
6033
6136
  if (!allowClassNameProp && !allowStyleProp && inlineStyleProps.length === 0 && !staticClassNameExpr) return {
6034
6137
  needsSxVar: false,
@@ -6046,7 +6149,8 @@ function emitStyleMerging(args) {
6046
6149
  styleId,
6047
6150
  allowClassNameProp,
6048
6151
  allowStyleProp,
6049
- inlineStyleProps
6152
+ inlineStyleProps,
6153
+ emitTypes
6050
6154
  });
6051
6155
  return emitVerbosePattern({
6052
6156
  j,
@@ -6056,13 +6160,17 @@ function emitStyleMerging(args) {
6056
6160
  allowClassNameProp,
6057
6161
  allowStyleProp,
6058
6162
  inlineStyleProps,
6059
- staticClassNameExpr
6163
+ staticClassNameExpr,
6164
+ emitTypes
6060
6165
  });
6061
6166
  }
6062
6167
  function filterEmptyStyleArgs(args) {
6063
6168
  const { styleArgs, emptyStyleKeys, stylesIdentifier = "styles", ancestorSelectorParents } = args;
6064
6169
  if (!emptyStyleKeys || emptyStyleKeys.size === 0) return styleArgs;
6065
- const isEmptyStyleRef = (node) => !!(node && node.type === "MemberExpression" && node.object?.type === "Identifier" && node.object.name === stylesIdentifier && node.property?.type === "Identifier" && emptyStyleKeys.has(node.property.name) && !ancestorSelectorParents?.has(node.property.name));
6170
+ const isEmptyStyleRef = (node) => {
6171
+ const key = getStyleArgKey(node, stylesIdentifier);
6172
+ return !!(key && emptyStyleKeys.has(key) && !ancestorSelectorParents?.has(key));
6173
+ };
6066
6174
  const isEmptyStyleArg = (node) => {
6067
6175
  if (isEmptyStyleRef(node)) return true;
6068
6176
  if (node?.type === "LogicalExpression" && node.operator === "&&") return isEmptyStyleRef(node.right);
@@ -6071,12 +6179,12 @@ function filterEmptyStyleArgs(args) {
6071
6179
  return styleArgs.filter((arg) => !isEmptyStyleArg(arg));
6072
6180
  }
6073
6181
  function emitWithoutStylex(args) {
6074
- const { j, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr } = args;
6182
+ const { j, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr, emitTypes } = args;
6075
6183
  const classNameAttr = allowClassNameProp ? classNameId : staticClassNameExpr ?? null;
6076
6184
  let styleAttr = null;
6077
- if (allowStyleProp) if (inlineStyleProps.length > 0) styleAttr = j.objectExpression([j.spreadElement(styleId), ...inlineStyleProps.map((p) => j.property("init", j.identifier(p.prop), p.expr))]);
6185
+ if (allowStyleProp) if (inlineStyleProps.length > 0) styleAttr = maybeCastStyleForCustomProps(j, j.objectExpression([j.spreadElement(styleId), ...inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))]), inlineStyleProps, emitTypes);
6078
6186
  else styleAttr = styleId;
6079
- else if (inlineStyleProps.length > 0) styleAttr = j.objectExpression(inlineStyleProps.map((p) => j.property("init", j.identifier(p.prop), p.expr)));
6187
+ else if (inlineStyleProps.length > 0) styleAttr = maybeCastStyleForCustomProps(j, j.objectExpression(inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))), inlineStyleProps, emitTypes);
6080
6188
  return {
6081
6189
  needsSxVar: false,
6082
6190
  sxDecl: null,
@@ -6090,17 +6198,17 @@ function emitWithoutStylex(args) {
6090
6198
  * Generates the merger function call pattern.
6091
6199
  */
6092
6200
  function emitWithMerger(args) {
6093
- const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps } = args;
6201
+ const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, emitTypes } = args;
6094
6202
  const firstStyleArg = styleArgs[0];
6095
6203
  const mergerArgs = [styleArgs.length === 1 && firstStyleArg ? firstStyleArg : j.arrayExpression(styleArgs)];
6096
6204
  if (allowClassNameProp || allowStyleProp) {
6097
6205
  if (allowClassNameProp) mergerArgs.push(classNameId);
6098
6206
  else if (allowStyleProp) mergerArgs.push(j.identifier("undefined"));
6099
- if (allowStyleProp) if (inlineStyleProps.length > 0) mergerArgs.push(j.objectExpression([j.spreadElement(styleId), ...inlineStyleProps.map((p) => j.property("init", j.identifier(p.prop), p.expr))]));
6207
+ if (allowStyleProp) if (inlineStyleProps.length > 0) mergerArgs.push(maybeCastStyleForCustomProps(j, j.objectExpression([j.spreadElement(styleId), ...inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))]), inlineStyleProps, emitTypes));
6100
6208
  else mergerArgs.push(styleId);
6101
6209
  else if (inlineStyleProps.length > 0) {
6102
6210
  mergerArgs.push(j.identifier("undefined"));
6103
- mergerArgs.push(j.objectExpression(inlineStyleProps.map((p) => j.property("init", j.identifier(p.prop), p.expr))));
6211
+ mergerArgs.push(maybeCastStyleForCustomProps(j, j.objectExpression(inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))), inlineStyleProps, emitTypes));
6104
6212
  }
6105
6213
  }
6106
6214
  return {
@@ -6116,7 +6224,7 @@ function emitWithMerger(args) {
6116
6224
  * Generates the verbose className/style merging pattern.
6117
6225
  */
6118
6226
  function emitVerbosePattern(args) {
6119
- const { j, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr } = args;
6227
+ const { j, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr, emitTypes } = args;
6120
6228
  const stylexPropsCall = j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), styleArgs);
6121
6229
  const sxDecl = j.variableDeclaration("const", [j.variableDeclarator(j.identifier("sx"), stylexPropsCall)]);
6122
6230
  let classNameAttr = null;
@@ -6133,9 +6241,9 @@ function emitVerbosePattern(args) {
6133
6241
  const spreads = [
6134
6242
  j.spreadElement(j.memberExpression(j.identifier("sx"), j.identifier("style"))),
6135
6243
  ...allowStyleProp ? [j.spreadElement(styleId)] : [],
6136
- ...inlineStyleProps.map((p) => j.property("init", j.identifier(p.prop), p.expr))
6244
+ ...inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))
6137
6245
  ];
6138
- styleAttr = j.objectExpression(spreads);
6246
+ styleAttr = maybeCastStyleForCustomProps(j, j.objectExpression(spreads), inlineStyleProps, emitTypes);
6139
6247
  }
6140
6248
  return {
6141
6249
  needsSxVar: true,
@@ -6146,6 +6254,31 @@ function emitVerbosePattern(args) {
6146
6254
  styleAttr
6147
6255
  };
6148
6256
  }
6257
+ /** Returns a string literal key for CSS custom properties (--foo), identifier otherwise. */
6258
+ function inlineStylePropKey(j, prop) {
6259
+ return prop.startsWith("--") ? j.literal(prop) : j.identifier(prop);
6260
+ }
6261
+ /** Wraps an object expression with `as React.CSSProperties` when it contains CSS custom properties (TypeScript only). */
6262
+ function maybeCastStyleForCustomProps(j, styleExpr, inlineStyleProps, emitTypes) {
6263
+ if (!emitTypes || !inlineStyleProps.some((p) => p.prop.startsWith("--"))) return styleExpr;
6264
+ return j.tsAsExpression(styleExpr, j.tsTypeReference(j.tsQualifiedName(j.identifier("React"), j.identifier("CSSProperties"))));
6265
+ }
6266
+ /**
6267
+ * If `node` is `<stylesIdentifier>.<key>`, returns the key name; otherwise `null`.
6268
+ * Centralises the MemberExpression pattern check used across the style-merger module.
6269
+ */
6270
+ function getStyleArgKey(node, stylesIdentifier) {
6271
+ const n = node;
6272
+ if (n?.type === "MemberExpression" && n.object?.type === "Identifier" && n.object.name === stylesIdentifier && n.property?.type === "Identifier") return n.property.name;
6273
+ return null;
6274
+ }
6275
+ /**
6276
+ * Returns `true` when `node` is `<stylesIdentifier>.<key>` and the key is in the given set.
6277
+ */
6278
+ function hasStyleArgKey(node, stylesIdentifier, keys) {
6279
+ const key = getStyleArgKey(node, stylesIdentifier);
6280
+ return !!(key && keys.has(key));
6281
+ }
6149
6282
 
6150
6283
  //#endregion
6151
6284
  //#region src/internal/emit-wrappers/comments.ts
@@ -6463,6 +6596,116 @@ function makeConditionalStyleExpr(j, args) {
6463
6596
  if (isBoolean) return j.logicalExpression("&&", cond, expr);
6464
6597
  return j.conditionalExpression(cond, expr, j.identifier("undefined"));
6465
6598
  }
6599
+ /**
6600
+ * Builds style expressions from extraStylexPropsArgs entries, merging
6601
+ * complementary boolean condition pairs into single ternary expressions.
6602
+ *
6603
+ * Two entries with `when: "prop"` and `when: "!prop"` are merged into
6604
+ * `prop ? trueExpr : falseExpr` instead of emitting separate conditionals.
6605
+ */
6606
+ function buildExtraStylexPropsExprs(j, args) {
6607
+ const { entries, destructureProps } = args;
6608
+ const result = [];
6609
+ const consumed = /* @__PURE__ */ new Set();
6610
+ for (let i = 0; i < entries.length; i++) {
6611
+ if (consumed.has(i)) continue;
6612
+ const entry = entries[i];
6613
+ if (!entry.when) {
6614
+ result.push(entry.expr);
6615
+ continue;
6616
+ }
6617
+ const complementIndex = findComplementaryEntry(entries, i, consumed);
6618
+ if (complementIndex !== null) {
6619
+ consumed.add(complementIndex);
6620
+ const other = entries[complementIndex];
6621
+ const positiveWhenRaw = getPositiveWhen(entry.when, other.when);
6622
+ const { cond } = collectConditionProps(j, {
6623
+ when: positiveWhenRaw,
6624
+ destructureProps
6625
+ });
6626
+ const isEntryPositive = areEquivalentWhen(entry.when, positiveWhenRaw);
6627
+ const trueExpr = isEntryPositive ? entry.expr : other.expr;
6628
+ const falseExpr = isEntryPositive ? other.expr : entry.expr;
6629
+ result.push(j.conditionalExpression(cond, trueExpr, falseExpr));
6630
+ continue;
6631
+ }
6632
+ const { cond, isBoolean } = collectConditionProps(j, {
6633
+ when: entry.when,
6634
+ destructureProps
6635
+ });
6636
+ result.push(makeConditionalStyleExpr(j, {
6637
+ cond,
6638
+ expr: entry.expr,
6639
+ isBoolean
6640
+ }));
6641
+ }
6642
+ return result;
6643
+ }
6644
+ /**
6645
+ * Finds the immediately next unconsumed entry that has a complementary "when"
6646
+ * condition to the entry at `index`. Only checks the adjacent unconsumed entry
6647
+ * to preserve style precedence ordering — merging non-adjacent pairs would
6648
+ * reorder styles relative to entries between them.
6649
+ */
6650
+ function findComplementaryEntry(entries, index, consumed) {
6651
+ const when = entries[index]?.when;
6652
+ if (!when) return null;
6653
+ let next = index + 1;
6654
+ while (next < entries.length && consumed.has(next)) next++;
6655
+ if (next >= entries.length) return null;
6656
+ const otherWhen = entries[next]?.when;
6657
+ if (otherWhen && getPositiveWhen(when, otherWhen) !== null) return next;
6658
+ return null;
6659
+ }
6660
+ /**
6661
+ * If two "when" strings represent complementary conditions (e.g., "prop" and
6662
+ * "!prop"), returns the positive (non-negated) condition string. Returns null
6663
+ * otherwise.
6664
+ */
6665
+ function getPositiveWhen(whenA, whenB) {
6666
+ const a = whenA.trim();
6667
+ const b = whenB.trim();
6668
+ if (isNegationOf(b, a)) return a;
6669
+ if (isNegationOf(a, b)) return b;
6670
+ return null;
6671
+ }
6672
+ function areEquivalentWhen(left, right) {
6673
+ return normalizeWhenForComparison(left) === normalizeWhenForComparison(right);
6674
+ }
6675
+ function isNegationOf(candidate, base) {
6676
+ const candidateNormalized = normalizeWhenForComparison(candidate);
6677
+ const baseNormalized = normalizeWhenForComparison(base);
6678
+ if (isAtomicWhenExpression(baseNormalized) && candidateNormalized === `!${baseNormalized}`) return true;
6679
+ if (!candidateNormalized.startsWith("!(") || !candidateNormalized.endsWith(")")) return false;
6680
+ if (!hasEnclosingParens(candidateNormalized.slice(1))) return false;
6681
+ return normalizeWhenForComparison(candidateNormalized.slice(2, -1)) === baseNormalized;
6682
+ }
6683
+ function normalizeWhenForComparison(when) {
6684
+ return stripOuterParens(String(when ?? "").replace(/\s+/g, ""));
6685
+ }
6686
+ function stripOuterParens(expr) {
6687
+ let current = expr;
6688
+ while (current.startsWith("(") && current.endsWith(")") && hasEnclosingParens(current)) current = current.slice(1, -1);
6689
+ return current;
6690
+ }
6691
+ function hasEnclosingParens(expr) {
6692
+ let depth = 0;
6693
+ for (let i = 0; i < expr.length; i++) {
6694
+ const ch = expr[i];
6695
+ if (ch === "(") {
6696
+ depth++;
6697
+ continue;
6698
+ }
6699
+ if (ch !== ")") continue;
6700
+ depth--;
6701
+ if (depth < 0) return false;
6702
+ if (depth === 0 && i !== expr.length - 1) return false;
6703
+ }
6704
+ return depth === 0;
6705
+ }
6706
+ function isAtomicWhenExpression(expr) {
6707
+ return /^[A-Za-z_$][0-9A-Za-z_$]*(?:\.[A-Za-z_$][0-9A-Za-z_$]*)*$/.test(expr);
6708
+ }
6466
6709
 
6467
6710
  //#endregion
6468
6711
  //#region src/internal/emit-wrappers/emit-intrinsic-simple.ts
@@ -6785,17 +7028,10 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
6785
7028
  ];
6786
7029
  const destructureProps = [];
6787
7030
  const propDefaults = /* @__PURE__ */ new Map();
6788
- if (d.extraStylexPropsArgs) for (const extra of d.extraStylexPropsArgs) if (extra.when) {
6789
- const { cond, isBoolean } = emitter.collectConditionProps({
6790
- when: extra.when,
6791
- destructureProps
6792
- });
6793
- styleArgs.push(emitter.makeConditionalStyleExpr({
6794
- cond,
6795
- expr: extra.expr,
6796
- isBoolean
6797
- }));
6798
- } else styleArgs.push(extra.expr);
7031
+ if (d.extraStylexPropsArgs) styleArgs.push(...emitter.buildExtraStylexPropsExprs({
7032
+ entries: d.extraStylexPropsArgs,
7033
+ destructureProps
7034
+ }));
6799
7035
  const needsUseTheme = appendThemeBooleanStyleArgs(d.needsUseThemeHook, styleArgs, j, stylesIdentifier, ctx);
6800
7036
  for (const gp of appendPseudoAliasStyleArgs(d.pseudoAliasSelectors, styleArgs, j, stylesIdentifier)) if (!destructureProps.includes(gp)) destructureProps.push(gp);
6801
7037
  const compoundVariantKeys = /* @__PURE__ */ new Set();
@@ -6845,6 +7081,12 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
6845
7081
  if (d.attrsInfo?.invertedBoolAttrs?.length) {
6846
7082
  for (const inv of d.attrsInfo.invertedBoolAttrs) if (inv?.jsxProp && !destructureProps.includes(inv.jsxProp)) destructureProps.push(inv.jsxProp);
6847
7083
  }
7084
+ maybeApplySafeTruthyDefaultFromExtraStyleConditionals({
7085
+ j,
7086
+ d,
7087
+ styleArgs,
7088
+ propDefaults
7089
+ });
6848
7090
  const explicitTransientProps = [];
6849
7091
  const explicit = d.propsType;
6850
7092
  if (explicit?.type === "TSTypeLiteral" && explicit.members) {
@@ -6969,6 +7211,115 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
6969
7211
  }), d));
6970
7212
  }
6971
7213
  }
7214
+ function maybeApplySafeTruthyDefaultFromExtraStyleConditionals(args) {
7215
+ const { j, d, styleArgs, propDefaults } = args;
7216
+ if (!d.extraStylexPropsArgs || d.extraStylexPropsArgs.length === 0) return;
7217
+ const blockedProps = collectPropsUsedOutsideExtraStyleConditionals(j, d);
7218
+ const candidateIndicesByProp = /* @__PURE__ */ new Map();
7219
+ for (let i = 0; i < styleArgs.length; i++) {
7220
+ const expr = styleArgs[i];
7221
+ if (!expr || expr.type !== "ConditionalExpression") continue;
7222
+ const propName = extractTruthyUndefinedConditionalProp(expr.test);
7223
+ if (!propName) continue;
7224
+ const existing = candidateIndicesByProp.get(propName) ?? [];
7225
+ existing.push(i);
7226
+ candidateIndicesByProp.set(propName, existing);
7227
+ }
7228
+ for (const [propName, indices] of candidateIndicesByProp.entries()) {
7229
+ if (blockedProps.has(propName)) continue;
7230
+ const existingDefault = propDefaults.get(propName);
7231
+ if (existingDefault !== void 0 && existingDefault !== true) continue;
7232
+ if (isPropUsedOutsideCandidateConditionals(styleArgs, propName, new Set(indices))) continue;
7233
+ propDefaults.set(propName, true);
7234
+ for (const idx of indices) {
7235
+ const expr = styleArgs[idx];
7236
+ if (!expr || expr.type !== "ConditionalExpression") continue;
7237
+ expr.test = j.identifier(propName);
7238
+ }
7239
+ }
7240
+ }
7241
+ function collectPropsUsedOutsideExtraStyleConditionals(j, d) {
7242
+ const used = /* @__PURE__ */ new Set();
7243
+ const add = (name) => {
7244
+ if (name) used.add(name);
7245
+ };
7246
+ for (const [when] of Object.entries(d.variantStyleKeys ?? {})) {
7247
+ const parsed = parseVariantWhenToAst(j, when);
7248
+ for (const prop of parsed.props) add(prop);
7249
+ }
7250
+ for (const dim of d.variantDimensions ?? []) {
7251
+ add(dim.propName);
7252
+ add(dim.namespaceBooleanProp);
7253
+ }
7254
+ for (const cv of d.compoundVariants ?? []) {
7255
+ add(cv.outerProp);
7256
+ add(cv.innerProp);
7257
+ }
7258
+ for (const pair of d.styleFnFromProps ?? []) if (pair.jsxProp !== "__props") add(pair.jsxProp);
7259
+ for (const prop of collectInlineStylePropNames(d.inlineStyleProps ?? [])) add(prop);
7260
+ for (const inlineProp of d.inlineStyleProps ?? []) {
7261
+ if (!inlineProp.expr || typeof inlineProp.expr !== "object") continue;
7262
+ collectPropsFromPropsMemberAccess(inlineProp.expr, used);
7263
+ }
7264
+ for (const attr of d.attrsInfo?.defaultAttrs ?? []) add(attr.jsxProp);
7265
+ for (const attr of d.attrsInfo?.conditionalAttrs ?? []) add(attr.jsxProp);
7266
+ for (const attr of d.attrsInfo?.invertedBoolAttrs ?? []) add(attr.jsxProp);
7267
+ return used;
7268
+ }
7269
+ function isPropUsedOutsideCandidateConditionals(styleArgs, propName, candidateIndices) {
7270
+ for (let i = 0; i < styleArgs.length; i++) {
7271
+ if (candidateIndices.has(i)) continue;
7272
+ const expr = styleArgs[i];
7273
+ if (!expr) continue;
7274
+ const identifiers = /* @__PURE__ */ new Set();
7275
+ collectIdentifiers(expr, identifiers);
7276
+ if (identifiers.has(propName)) return true;
7277
+ }
7278
+ return false;
7279
+ }
7280
+ function extractTruthyUndefinedConditionalProp(test) {
7281
+ if (test.type !== "LogicalExpression" || test.operator !== "||") return null;
7282
+ const leftProp = extractPropEqUndefined(test.left);
7283
+ if (leftProp && isIdentifierNamed(test.right, leftProp)) return leftProp;
7284
+ const rightProp = extractPropEqUndefined(test.right);
7285
+ if (rightProp && isIdentifierNamed(test.left, rightProp)) return rightProp;
7286
+ return null;
7287
+ }
7288
+ function extractPropEqUndefined(expr) {
7289
+ if (expr.type !== "BinaryExpression" || expr.operator !== "===") return null;
7290
+ if (isIdentifierNamed(expr.left, "undefined")) return identifierName(expr.right);
7291
+ if (isIdentifierNamed(expr.right, "undefined")) return identifierName(expr.left);
7292
+ return null;
7293
+ }
7294
+ function isIdentifierNamed(expr, name) {
7295
+ return expr.type === "Identifier" && expr.name === name;
7296
+ }
7297
+ function identifierName(expr) {
7298
+ return expr.type === "Identifier" ? expr.name : null;
7299
+ }
7300
+ function collectPropsFromPropsMemberAccess(node, out) {
7301
+ const visit = (value) => {
7302
+ if (!value || typeof value !== "object") return;
7303
+ if (Array.isArray(value)) {
7304
+ for (const child of value) visit(child);
7305
+ return;
7306
+ }
7307
+ const typed = value;
7308
+ if ((typed.type === "MemberExpression" || typed.type === "OptionalMemberExpression") && typed.object && typed.property && typed.computed === false) {
7309
+ const object = typed.object;
7310
+ const property = typed.property;
7311
+ if (object.type === "Identifier" && object.name === "props") {
7312
+ if (property.type === "Identifier" && property.name) out.add(property.name);
7313
+ }
7314
+ }
7315
+ const record = value;
7316
+ for (const key of Object.keys(record)) {
7317
+ if (key === "loc" || key === "comments") continue;
7318
+ visit(record[key]);
7319
+ }
7320
+ };
7321
+ visit(node);
7322
+ }
6972
7323
  /** Appends theme boolean conditional style args (e.g., `theme.isDark ? styles.boxDark : styles.boxLight`) to `styleArgs`. */
6973
7324
  function appendThemeBooleanStyleArgs(hooks, styleArgs, j, stylesIdentifier, ctx) {
6974
7325
  if (!hooks || hooks.length === 0) return false;
@@ -7163,17 +7514,10 @@ function emitComponentWrappers(emitter) {
7163
7514
  propDefaults,
7164
7515
  namespaceBooleanProps
7165
7516
  });
7166
- if (d.extraStylexPropsArgs) for (const extra of d.extraStylexPropsArgs) if (extra.when) {
7167
- const { cond, isBoolean } = emitter.collectConditionProps({
7168
- when: extra.when,
7169
- destructureProps
7170
- });
7171
- styleArgs.push(emitter.makeConditionalStyleExpr({
7172
- cond,
7173
- expr: extra.expr,
7174
- isBoolean
7175
- }));
7176
- } else styleArgs.push(extra.expr);
7517
+ if (d.extraStylexPropsArgs) styleArgs.push(...emitter.buildExtraStylexPropsExprs({
7518
+ entries: d.extraStylexPropsArgs,
7519
+ destructureProps
7520
+ }));
7177
7521
  for (const gp of appendPseudoAliasStyleArgs(d.pseudoAliasSelectors, styleArgs, j, stylesIdentifier)) if (!destructureProps.includes(gp)) destructureProps.push(gp);
7178
7522
  for (const prop of collectInlineStylePropNames(d.inlineStyleProps ?? [])) if (!destructureProps.includes(prop)) destructureProps.push(prop);
7179
7523
  emitter.buildStyleFnExpressions({
@@ -8110,6 +8454,7 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
8110
8454
  const intrinsicPolymorphicWrapperDecls = wrapperDecls.filter((d) => {
8111
8455
  if (d.base.kind !== "intrinsic") return false;
8112
8456
  if (d.attrWrapper) return false;
8457
+ if (d.shouldForwardProp) return false;
8113
8458
  if (propsTypeHasExistingPolymorphicAs(d)) return false;
8114
8459
  return wrapperNames.has(d.localName) || (d.supportsAsProp ?? false);
8115
8460
  });
@@ -8172,17 +8517,10 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
8172
8517
  propDefaults
8173
8518
  });
8174
8519
  if (d.compoundVariants) buildCompoundVariantExpressions(d.compoundVariants, styleArgs, destructureProps);
8175
- if (d.extraStylexPropsArgs) for (const extra of d.extraStylexPropsArgs) if (extra.when) {
8176
- const { cond, isBoolean } = emitter.collectConditionProps({
8177
- when: extra.when,
8178
- destructureProps
8179
- });
8180
- styleArgs.push(emitter.makeConditionalStyleExpr({
8181
- cond,
8182
- expr: extra.expr,
8183
- isBoolean
8184
- }));
8185
- } else styleArgs.push(extra.expr);
8520
+ if (d.extraStylexPropsArgs) styleArgs.push(...emitter.buildExtraStylexPropsExprs({
8521
+ entries: d.extraStylexPropsArgs,
8522
+ destructureProps
8523
+ }));
8186
8524
  for (const gp of appendPseudoAliasStyleArgs(d.pseudoAliasSelectors, styleArgs, j, stylesIdentifier)) if (!destructureProps.includes(gp)) destructureProps.push(gp);
8187
8525
  for (const prop of collectInlineStylePropNames(d.inlineStyleProps ?? [])) if (!destructureProps.includes(prop)) destructureProps.push(prop);
8188
8526
  emitter.buildStyleFnExpressions({
@@ -8263,12 +8601,12 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
8263
8601
  if (merging.sxDecl) fnBodyStmts.push(merging.sxDecl);
8264
8602
  fnBodyStmts.push(j.returnStatement(jsx));
8265
8603
  const polymorphicFnTypeParams = emitTypes && allowAsProp ? j(`function _<C extends React.ElementType = "${tagName}">() { return null }`).get().node.program.body[0].typeParameters : void 0;
8266
- emitted.push(emitter.buildWrapperFunction({
8604
+ emitted.push(withLeadingComments(emitter.buildWrapperFunction({
8267
8605
  localName: d.localName,
8268
8606
  params: [propsParamId],
8269
8607
  bodyStmts: fnBodyStmts,
8270
8608
  typeParameters: polymorphicFnTypeParams
8271
- }));
8609
+ }), d));
8272
8610
  }
8273
8611
  }
8274
8612
 
@@ -8388,14 +8726,8 @@ function emitShouldForwardPropWrappers(ctx) {
8388
8726
  j.memberExpression(j.identifier(stylesIdentifier), j.identifier(d.styleKey)),
8389
8727
  ...extraStyleArgsAfterBase
8390
8728
  ];
8391
- if (d.extraStylexPropsArgs) for (const extra of d.extraStylexPropsArgs) if (extra.when) {
8392
- const { cond, isBoolean } = emitter.collectConditionProps({ when: extra.when });
8393
- styleArgs.push(emitter.makeConditionalStyleExpr({
8394
- cond,
8395
- expr: extra.expr,
8396
- isBoolean
8397
- }));
8398
- } else styleArgs.push(extra.expr);
8729
+ const propDefaults = /* @__PURE__ */ new Map();
8730
+ if (d.extraStylexPropsArgs) styleArgs.push(...emitter.buildExtraStylexPropsExprs({ entries: d.extraStylexPropsArgs }));
8399
8731
  const pseudoGuardProps = appendPseudoAliasStyleArgs(d.pseudoAliasSelectors, styleArgs, j, stylesIdentifier);
8400
8732
  const compoundVariantKeys = /* @__PURE__ */ new Set();
8401
8733
  for (const cv of d.compoundVariants ?? []) {
@@ -8424,7 +8756,6 @@ function emitShouldForwardPropWrappers(ctx) {
8424
8756
  }
8425
8757
  const dropPrefix = d.shouldForwardProp?.dropPrefix;
8426
8758
  const destructureParts = [];
8427
- const propDefaults = /* @__PURE__ */ new Map();
8428
8759
  for (const p of dropProps) destructureParts.push(p);
8429
8760
  if (d.extraStylexPropsArgs) {
8430
8761
  for (const extra of d.extraStylexPropsArgs) if (extra.when) {
@@ -9689,7 +10020,7 @@ var WrapperEmitter = class {
9689
10020
  const collectCondIdentifiers = (node) => {
9690
10021
  if (!node) return;
9691
10022
  if (isIdentifierNode(node)) {
9692
- expandedDestructureProps.add(node.name);
10023
+ if (node.name !== "undefined") expandedDestructureProps.add(node.name);
9693
10024
  return;
9694
10025
  }
9695
10026
  if (node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && node.object.name === "props" && node.property.type === "Identifier") {
@@ -9817,6 +10148,9 @@ var WrapperEmitter = class {
9817
10148
  makeConditionalStyleExpr(args) {
9818
10149
  return makeConditionalStyleExpr(this.j, args);
9819
10150
  }
10151
+ buildExtraStylexPropsExprs(args) {
10152
+ return buildExtraStylexPropsExprs(this.j, args);
10153
+ }
9820
10154
  literalExpr(value) {
9821
10155
  return literalExpr(this.j, value);
9822
10156
  }
@@ -10069,7 +10403,9 @@ function finalize(ctx) {
10069
10403
  }
10070
10404
  return {
10071
10405
  code,
10072
- warnings: ctx.warnings
10406
+ warnings: ctx.warnings,
10407
+ sidecarContent: ctx.sidecarStylexContent,
10408
+ bridgeResults: ctx.bridgeResults
10073
10409
  };
10074
10410
  }
10075
10411
 
@@ -10080,6 +10416,16 @@ function finalize(ctx) {
10080
10416
  * Core concepts: pseudo/attribute parsing and selector normalization.
10081
10417
  */
10082
10418
  /**
10419
+ * CSS2 pseudo-elements that browsers accept with single-colon syntax.
10420
+ * These must be normalized to double-colon for StyleX compatibility.
10421
+ */
10422
+ const CSS2_PSEUDO_ELEMENTS = new Set([
10423
+ ":before",
10424
+ ":after",
10425
+ ":first-line",
10426
+ ":first-letter"
10427
+ ]);
10428
+ /**
10083
10429
  * Parse a CSS selector using postcss-selector-parser and determine
10084
10430
  * if it's compatible with StyleX (only pseudo-classes/elements on &).
10085
10431
  */
@@ -10096,14 +10442,26 @@ function parseSelector(selector) {
10096
10442
  if (selectors.length === 0) return { kind: "base" };
10097
10443
  if (selectors.length > 1) {
10098
10444
  const pseudos = [];
10445
+ const pseudoElementValues = [];
10099
10446
  for (const sel of selectors) {
10100
10447
  const result = parseSingleSelector(sel);
10101
- const firstPseudo = result.kind === "pseudo" ? result.pseudos[0] : void 0;
10102
- if (result.kind !== "pseudo" || result.pseudos.length !== 1 || !firstPseudo) return {
10448
+ if (result.kind === "pseudo" && result.pseudos.length === 1 && result.pseudos[0]) pseudos.push(result.pseudos[0]);
10449
+ else if (result.kind === "pseudoElement") pseudoElementValues.push(result.element);
10450
+ else return {
10103
10451
  kind: "unsupported",
10104
- reason: "comma-separated selectors must all be simple pseudos"
10452
+ reason: "comma-separated selectors must all be simple pseudos or pseudo-elements"
10453
+ };
10454
+ }
10455
+ if (pseudos.length > 0 && pseudoElementValues.length > 0) return {
10456
+ kind: "unsupported",
10457
+ reason: "mixed pseudo-classes and pseudo-elements in comma-separated selector"
10458
+ };
10459
+ if (pseudoElementValues.length > 0) {
10460
+ pseudoElementValues.sort();
10461
+ return {
10462
+ kind: "pseudoElements",
10463
+ elements: pseudoElementValues
10105
10464
  };
10106
- pseudos.push(firstPseudo);
10107
10465
  }
10108
10466
  return {
10109
10467
  kind: "pseudo",
@@ -10155,7 +10513,7 @@ function parseSingleSelector(selector) {
10155
10513
  hasUniversal = true;
10156
10514
  break;
10157
10515
  case "pseudo":
10158
- if (node.value.startsWith("::")) pseudoElements.push(node);
10516
+ if (node.value.startsWith("::") || CSS2_PSEUDO_ELEMENTS.has(node.value)) pseudoElements.push(node);
10159
10517
  else pseudoClasses.push(node);
10160
10518
  break;
10161
10519
  case "attribute":
@@ -10213,7 +10571,7 @@ function parseSingleSelector(selector) {
10213
10571
  };
10214
10572
  return {
10215
10573
  kind: "pseudoElement",
10216
- element: firstPseudoEl.value
10574
+ element: normalizePseudoElementColon(firstPseudoEl.value)
10217
10575
  };
10218
10576
  }
10219
10577
  if (pseudoClasses.length > 0) return {
@@ -10287,50 +10645,116 @@ function parseAttributeSelectorInternal(selector) {
10287
10645
  };
10288
10646
  return null;
10289
10647
  }
10648
+ /** Tags on which `[disabled]` is equivalent to `:disabled`. */
10649
+ const DISABLEABLE_TAGS = new Set([
10650
+ "button",
10651
+ "input",
10652
+ "select",
10653
+ "textarea",
10654
+ "fieldset"
10655
+ ]);
10656
+ /** Tags on which `[checked]` is equivalent to `:checked`. */
10657
+ const CHECKABLE_TAGS = new Set(["input"]);
10658
+ /** Tags on which `[required]` is equivalent to `:required`. */
10659
+ const REQUIRABLE_TAGS = new Set([
10660
+ "input",
10661
+ "select",
10662
+ "textarea"
10663
+ ]);
10664
+ /**
10665
+ * Maps an HTML boolean attribute selector to its CSS pseudo-class equivalent,
10666
+ * restricted to tags where the mapping is provably equivalent.
10667
+ *
10668
+ * `[readonly]` is intentionally excluded — CSS `:read-only` matches much more
10669
+ * broadly (disabled inputs, checkbox/radio, inherently non-editable elements),
10670
+ * so the mapping would be a behavioral change.
10671
+ *
10672
+ * Returns null if the attribute has no safe pseudo-class mapping for this tag.
10673
+ */
10674
+ function mapAttributeToPseudo(attr, tagName) {
10675
+ const normalized = attr.replace(/\s+/g, "").toLowerCase();
10676
+ const tag = tagName.toLowerCase();
10677
+ if (normalized === "disabled" && DISABLEABLE_TAGS.has(tag)) return ":disabled";
10678
+ if (normalized === "checked" && CHECKABLE_TAGS.has(tag)) return ":checked";
10679
+ if (normalized === "required" && REQUIRABLE_TAGS.has(tag)) return ":required";
10680
+ return null;
10681
+ }
10290
10682
  /**
10291
10683
  * Parses selectors like "& svg", "& > button", "&:hover svg", "& svg:hover",
10292
- * "&:focus > button:disabled".
10684
+ * "&:focus > button:disabled", "& > button[disabled]".
10293
10685
  *
10294
10686
  * Both descendant (space) and child (>) combinators are mapped the same way
10295
10687
  * because `stylex.when.ancestor()` matches ANY ancestor, not just a direct parent.
10296
10688
  * The child combinator is therefore less strict in the output than the original CSS.
10297
10689
  *
10690
+ * Attribute selectors on child elements (e.g., `button[disabled]`) are mapped to
10691
+ * their pseudo-class equivalents (`:disabled`) for StyleX compatibility.
10692
+ *
10298
10693
  * Returns null if the selector doesn't match an element selector pattern.
10299
10694
  */
10300
10695
  function parseElementSelectorPattern(selector) {
10301
10696
  const trimmed = selector.trim();
10302
- const m = trimmed.match(/^&((?::[\w-]+(?:\([^)]*\))?)*)\s*(>?\s*)([a-zA-Z][a-zA-Z0-9]*)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10697
+ const m = trimmed.match(/^&((?::[\w-]+(?:\([^)]*\))?)*)\s*(>?\s*)([a-zA-Z][a-zA-Z0-9]*)((?:\[[^\]]+\])?)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10303
10698
  if (m) {
10304
10699
  const ancestorPseudoRaw = m[1] ?? "";
10305
10700
  const tagName = m[3];
10306
- const childPseudoRaw = m[4] ?? "";
10701
+ const attrRaw = m[4] ?? "";
10702
+ const childPseudo = resolveChildPseudoWithAttr(m[5] ?? "", attrRaw, tagName);
10703
+ if (childPseudo === void 0) return null;
10307
10704
  return {
10308
10705
  tagName,
10309
10706
  ancestorPseudo: ancestorPseudoRaw || null,
10310
- childPseudo: childPseudoRaw || null
10707
+ childPseudo
10311
10708
  };
10312
10709
  }
10313
- const bareM = trimmed.match(/^([a-zA-Z][a-zA-Z0-9]*)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10710
+ const bareM = trimmed.match(/^([a-zA-Z][a-zA-Z0-9]*)((?:\[[^\]]+\])?)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10314
10711
  if (bareM) {
10315
- const childPseudoRaw = bareM[2] ?? "";
10712
+ const bareTagName = bareM[1];
10713
+ const attrRaw = bareM[2] ?? "";
10714
+ const childPseudo = resolveChildPseudoWithAttr(bareM[3] ?? "", attrRaw, bareTagName);
10715
+ if (childPseudo === void 0) return null;
10316
10716
  return {
10317
- tagName: bareM[1],
10717
+ tagName: bareTagName,
10318
10718
  ancestorPseudo: null,
10319
- childPseudo: childPseudoRaw || null
10719
+ childPseudo
10320
10720
  };
10321
10721
  }
10322
- const childCombM = trimmed.match(/^>\s*([a-zA-Z][a-zA-Z0-9]*)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10722
+ const childCombM = trimmed.match(/^>\s*([a-zA-Z][a-zA-Z0-9]*)((?:\[[^\]]+\])?)((?::[\w-]+(?:\([^)]*\))?)*)$/);
10323
10723
  if (childCombM) {
10324
- const childPseudoRaw = childCombM[2] ?? "";
10724
+ const combTagName = childCombM[1];
10725
+ const attrRaw = childCombM[2] ?? "";
10726
+ const childPseudo = resolveChildPseudoWithAttr(childCombM[3] ?? "", attrRaw, combTagName);
10727
+ if (childPseudo === void 0) return null;
10325
10728
  return {
10326
- tagName: childCombM[1],
10729
+ tagName: combTagName,
10327
10730
  ancestorPseudo: null,
10328
- childPseudo: childPseudoRaw || null
10731
+ childPseudo
10329
10732
  };
10330
10733
  }
10331
10734
  return null;
10332
10735
  }
10333
10736
  /**
10737
+ * Combines an explicit pseudo-class string with an optional attribute selector.
10738
+ * Returns the combined pseudo string, null if neither is present,
10739
+ * or undefined if the attribute can't be safely mapped for this tag.
10740
+ */
10741
+ function resolveChildPseudoWithAttr(pseudoRaw, attrRaw, tagName) {
10742
+ const pseudo = pseudoRaw || null;
10743
+ if (!attrRaw) return pseudo;
10744
+ const attrPseudo = mapAttributeToPseudo(attrRaw.slice(1, -1), tagName);
10745
+ if (!attrPseudo) return;
10746
+ return pseudo ? `${attrPseudo}${pseudo}` : attrPseudo;
10747
+ }
10748
+ /**
10749
+ * Normalize CSS2 single-colon pseudo-element values to double-colon.
10750
+ * E.g., ":before" → "::before", ":after" → "::after".
10751
+ * Already-double-colon values pass through unchanged.
10752
+ */
10753
+ function normalizePseudoElementColon(value) {
10754
+ if (CSS2_PSEUDO_ELEMENTS.has(value)) return `:${value}`;
10755
+ return value;
10756
+ }
10757
+ /**
10334
10758
  * Normalize double-ampersand specificity hacks (`&&`) by collapsing to a single `&`.
10335
10759
  * Only handles `&&` (exactly two). Higher tiers (`&&&`, `&&&&`) are flagged as
10336
10760
  * `hasHigherTier` because flattening them can change cascade precedence.
@@ -10361,8 +10785,8 @@ function normalizeSpecificityHacks(selector) {
10361
10785
  };
10362
10786
  }
10363
10787
  function normalizeInterpolatedSelector(selectorRaw) {
10364
- if (!/__SC_EXPR_\d+__/.test(selectorRaw)) return selectorRaw;
10365
- return selectorRaw.replace(/__SC_EXPR_\d+__/g, "&").replace(/\s+/g, " ").trim().replace(/&\s*&:/g, "&:").replace(/&\s*:/g, "&:");
10788
+ if (!PLACEHOLDER_RE.test(selectorRaw)) return selectorRaw;
10789
+ return selectorRaw.replace(new RegExp(PLACEHOLDER_RE.source, "g"), "&").replace(/\s+/g, " ").trim().replace(/&\s*&:/g, "&:").replace(/&\s*:/g, "&:");
10366
10790
  }
10367
10791
  function normalizeSelectorForInputAttributePseudos(selector, isInput) {
10368
10792
  if (!isInput) return selector;
@@ -10791,11 +11215,11 @@ function createCssHelperResolver(args) {
10791
11215
  return null;
10792
11216
  };
10793
11217
  const resolveCssHelperTemplate = (template, paramName, loc) => {
10794
- const bail = (type, context) => {
11218
+ const bail = (type, context, exprLoc) => {
10795
11219
  warnings.push({
10796
11220
  severity: "warning",
10797
11221
  type,
10798
- loc,
11222
+ loc: exprLoc ?? loc,
10799
11223
  context
10800
11224
  });
10801
11225
  return null;
@@ -10805,36 +11229,33 @@ function createCssHelperResolver(args) {
10805
11229
  const dynamicProps = [];
10806
11230
  const dynamicPropKeys = /* @__PURE__ */ new Set();
10807
11231
  const conditionalVariants = [];
10808
- const normalizePseudoElement = (pseudo) => {
10809
- if (!pseudo) return null;
10810
- if (pseudo === ":before" || pseudo === ":after") return `::${pseudo.slice(1)}`;
10811
- return pseudo.startsWith("::") ? pseudo : null;
10812
- };
10813
11232
  for (const rule of rules) {
10814
11233
  if (rule.atRuleStack.length > 0) return bail("Conditional `css` block: @-rules (e.g., @media, @supports) are not supported");
10815
11234
  const selector = (rule.selector ?? "").trim();
10816
11235
  const allowDynamicValues = selector === "&";
10817
11236
  let target = out;
10818
11237
  let currentPseudoClass = null;
11238
+ let pseudoElementTargets = null;
10819
11239
  if (selector !== "&") {
10820
11240
  const parsed = parseSelector(selector);
10821
11241
  if (parsed.kind === "pseudoElement") {
10822
- const normalizedPseudoElement = normalizePseudoElement(parsed.element);
10823
- if (normalizedPseudoElement) {
10824
- const nested = out[normalizedPseudoElement] ?? {};
10825
- out[normalizedPseudoElement] = nested;
10826
- target = nested;
10827
- } else return bail("Conditional `css` block: unsupported selector");
10828
- } else if (parsed.kind === "pseudo" && parsed.pseudos.length === 1) {
10829
- const simplePseudo = parsed.pseudos[0];
10830
- const normalizedPseudoElement = normalizePseudoElement(simplePseudo === ":before" || simplePseudo === ":after" ? simplePseudo : null);
10831
- if (normalizedPseudoElement) {
10832
- const nested = out[normalizedPseudoElement] ?? {};
10833
- out[normalizedPseudoElement] = nested;
10834
- target = nested;
10835
- } else currentPseudoClass = simplePseudo;
10836
- } else return bail("Conditional `css` block: unsupported selector");
11242
+ const nested = out[parsed.element] ?? {};
11243
+ out[parsed.element] = nested;
11244
+ target = nested;
11245
+ } else if (parsed.kind === "pseudoElements") {
11246
+ pseudoElementTargets = parsed.elements.map((el) => {
11247
+ const nested = out[el] ?? {};
11248
+ out[el] = nested;
11249
+ return {
11250
+ key: el,
11251
+ obj: nested
11252
+ };
11253
+ });
11254
+ target = pseudoElementTargets[0]?.obj ?? out;
11255
+ } else if (parsed.kind === "pseudo" && parsed.pseudos.length === 1) currentPseudoClass = parsed.pseudos[0];
11256
+ else return bail("Conditional `css` block: unsupported selector");
10837
11257
  }
11258
+ const preRuleKeys = pseudoElementTargets && pseudoElementTargets.length > 1 ? new Set(Object.keys(pseudoElementTargets[0].obj)) : null;
10838
11259
  for (const d of rule.declarations) {
10839
11260
  if (!d.property) return bail("Conditional `css` block: missing CSS property name");
10840
11261
  if (d.value.kind === "static") {
@@ -10857,9 +11278,10 @@ function createCssHelperResolver(args) {
10857
11278
  const slotId = slotParts[0].slotId;
10858
11279
  const expr = slotExprById.get(slotId);
10859
11280
  if (!expr) return bail("Conditional `css` block: missing interpolation expression", { property: d.property });
10860
- if (hasCallExpressionInExpr(expr)) return bail("Conditional `css` block: failed to parse expression", { property: d.property });
11281
+ const exprLoc = expr.loc?.start;
11282
+ if (hasCallExpressionInExpr(expr)) return bail("Conditional `css` block: failed to parse expression", { property: d.property }, exprLoc);
10861
11283
  const resolved = resolveHelperExprToAst(expr, paramName);
10862
- if (!resolved && hasThemeAccessInExpr(expr, paramName)) return bail("Conditional `css` block: failed to parse expression", { property: d.property });
11284
+ if (!resolved && hasThemeAccessInExpr(expr, paramName)) return bail("Conditional `css` block: failed to parse expression", { property: d.property }, exprLoc);
10863
11285
  if (resolved) if (hasStaticParts) {
10864
11286
  const { prefix, suffix } = extractPrefixSuffix(parts);
10865
11287
  const templateAst = parseExpr(wrapExprWithStaticParts(resolved.exprString, prefix, suffix));
@@ -10867,7 +11289,7 @@ function createCssHelperResolver(args) {
10867
11289
  for (const mapped of cssDeclarationToStylexDeclarations(d)) target[mapped.prop] = mergeIntoPseudoContext(templateAst, currentPseudoClass, target[mapped.prop]);
10868
11290
  continue;
10869
11291
  }
10870
- return bail("Conditional `css` block: failed to parse expression", { property: d.property });
11292
+ return bail("Conditional `css` block: failed to parse expression", { property: d.property }, exprLoc);
10871
11293
  } else {
10872
11294
  for (const mapped of cssDeclarationToStylexDeclarations(d)) target[mapped.prop] = mergeIntoPseudoContext(resolved.ast, currentPseudoClass, target[mapped.prop]);
10873
11295
  continue;
@@ -10911,6 +11333,10 @@ function createCssHelperResolver(args) {
10911
11333
  }
10912
11334
  }
10913
11335
  }
11336
+ if (pseudoElementTargets && pseudoElementTargets.length > 1 && preRuleKeys) {
11337
+ const source = pseudoElementTargets[0].obj;
11338
+ for (const key of Object.keys(source)) if (!preRuleKeys.has(key)) for (let i = 1; i < pseudoElementTargets.length; i++) pseudoElementTargets[i].obj[key] = source[key];
11339
+ }
10914
11340
  }
10915
11341
  return {
10916
11342
  style: out,
@@ -10998,8 +11424,9 @@ function createThemeResolvers(args) {
10998
11424
  if (!expr || expr.type !== "ArrowFunctionExpression" && expr.type !== "FunctionExpression") return null;
10999
11425
  const bodyExpr = getFunctionBodyExpr(expr);
11000
11426
  if (!bodyExpr) return null;
11001
- const direct = resolveThemeValue(bodyExpr);
11002
- if (direct) return direct;
11427
+ const themeAccessExpr = unwrapLogicalFallback(bodyExpr) ?? bodyExpr;
11428
+ const direct = resolveThemeValue(themeAccessExpr);
11429
+ if (direct) return wrapWithLogicalFallback(direct, bodyExpr, j);
11003
11430
  const paramName = expr.params?.[0]?.type === "Identifier" ? expr.params[0].name : null;
11004
11431
  const unwrap = (node) => {
11005
11432
  let cur = node;
@@ -11020,7 +11447,7 @@ function createThemeResolvers(args) {
11020
11447
  }
11021
11448
  return cur;
11022
11449
  };
11023
- const unwrapped = unwrap(bodyExpr);
11450
+ const unwrapped = unwrap(themeAccessExpr);
11024
11451
  if (!unwrapped || unwrapped.type !== "MemberExpression" && unwrapped.type !== "OptionalMemberExpression") return null;
11025
11452
  let themePath = null;
11026
11453
  const directPath = getMemberPathFromIdentifier(unwrapped, "theme");
@@ -11038,7 +11465,7 @@ function createThemeResolvers(args) {
11038
11465
  });
11039
11466
  if (!resolved) return null;
11040
11467
  for (const imp of resolved.imports ?? []) resolverImports.set(JSON.stringify(imp), imp);
11041
- return parseExpr(resolved.expr);
11468
+ return wrapWithLogicalFallback(parseExpr(resolved.expr), bodyExpr, j);
11042
11469
  };
11043
11470
  return {
11044
11471
  hasLocalThemeBinding,
@@ -11046,6 +11473,20 @@ function createThemeResolvers(args) {
11046
11473
  resolveThemeValueFromFn
11047
11474
  };
11048
11475
  }
11476
+ /**
11477
+ * If `originalExpr` was a logical fallback (`X ?? "default"` / `X || "default"`),
11478
+ * wraps the resolved AST node in a LogicalExpression preserving the original
11479
+ * operator and fallback value.
11480
+ *
11481
+ * Returns null if the fallback (RHS) is not a static literal, because dynamic
11482
+ * references (e.g., `props.fallbackColor`) would be invalid in a static
11483
+ * `stylex.create()` context where `props` is not in scope.
11484
+ */
11485
+ function wrapWithLogicalFallback(resolved, originalExpr, j) {
11486
+ if (!isLogicalExpressionNode(originalExpr) || originalExpr.operator !== "??" && originalExpr.operator !== "||") return resolved;
11487
+ if (literalToStaticValue(originalExpr.right) === null) return null;
11488
+ return j.logicalExpression(originalExpr.operator, resolved, originalExpr.right);
11489
+ }
11049
11490
 
11050
11491
  //#endregion
11051
11492
  //#region src/internal/lower-rules/precompute.ts
@@ -11443,6 +11884,7 @@ function createLowerRulesState(ctx) {
11443
11884
  const filePath = file.path;
11444
11885
  const resolveValue = ctx.resolveValueSafe;
11445
11886
  const resolveCall = ctx.resolveCallSafe;
11887
+ const resolveCallOptional = ctx.adapter.resolveCall.bind(ctx.adapter);
11446
11888
  const resolveSelector = ctx.resolveSelectorSafe;
11447
11889
  const importMap = ctx.importMap ?? /* @__PURE__ */ new Map();
11448
11890
  const styledDecls = ctx.styledDecls;
@@ -11552,6 +11994,8 @@ function createLowerRulesState(ctx) {
11552
11994
  j,
11553
11995
  importMap
11554
11996
  });
11997
+ const crossFileSelectorsByLocal = /* @__PURE__ */ new Map();
11998
+ if (ctx.crossFileSelectorUsages) for (const usage of ctx.crossFileSelectorUsages) crossFileSelectorsByLocal.set(usage.localName, usage);
11555
11999
  const state = {
11556
12000
  api,
11557
12001
  j,
@@ -11565,6 +12009,7 @@ function createLowerRulesState(ctx) {
11565
12009
  rewriteCssVarsInStyleObject,
11566
12010
  resolveValue,
11567
12011
  resolveCall,
12012
+ resolveCallOptional,
11568
12013
  resolveSelector,
11569
12014
  importMap,
11570
12015
  styledDecls,
@@ -11591,6 +12036,7 @@ function createLowerRulesState(ctx) {
11591
12036
  resolveCssHelperTemplate,
11592
12037
  resolveImportInScope,
11593
12038
  resolveImportForExpr,
12039
+ crossFileSelectorsByLocal,
11594
12040
  bail: false,
11595
12041
  markBail: () => {
11596
12042
  state.bail = true;
@@ -12581,23 +13027,23 @@ function extractIndexedThemeLookupInfo(node, paramName) {
12581
13027
  indexPropName
12582
13028
  };
12583
13029
  }
12584
- function resolveImportedHelperCall(callExpr, ctx, propsParamName, cssProperty) {
13030
+ function resolveImportedHelperCall(callExpr, ctx, propsParamName, cssProperty, themeBindingName) {
12585
13031
  const callee = callExpr.callee;
12586
13032
  if (!callee || typeof callee !== "object") return { kind: "keepOriginal" };
12587
- if (callee.type !== "Identifier") return { kind: "keepOriginal" };
12588
- const calleeIdent = callee.name;
12589
- if (typeof calleeIdent !== "string") return { kind: "keepOriginal" };
12590
- const imp = ctx.resolveImport(calleeIdent, callee);
13033
+ const calleeInfo = extractRootAndPath(callee);
13034
+ if (!calleeInfo) return { kind: "keepOriginal" };
13035
+ const imp = ctx.resolveImport(calleeInfo.rootName, calleeInfo.rootNode);
12591
13036
  const calleeImportedName = imp?.importedName;
12592
13037
  const calleeSource = imp?.source;
12593
13038
  if (!calleeImportedName || !calleeSource) return { kind: "keepOriginal" };
12594
- const args = callArgsFromNode(callExpr.arguments, propsParamName);
13039
+ const args = callArgsFromNode(callExpr.arguments, propsParamName, themeBindingName);
12595
13040
  const loc = callExpr.loc?.start;
12596
13041
  const resolveCallContext = {
12597
13042
  callSiteFilePath: ctx.filePath,
12598
13043
  calleeImportedName,
12599
13044
  calleeSource,
12600
13045
  args,
13046
+ ...calleeInfo.path.length > 0 ? { calleeMemberPath: calleeInfo.path } : {},
12601
13047
  ...loc ? { loc: {
12602
13048
  line: loc.line,
12603
13049
  column: loc.column
@@ -12657,15 +13103,24 @@ function getCalleeIdentName(callee) {
12657
13103
  if (callee.type !== "Identifier") return null;
12658
13104
  return callee.name ?? null;
12659
13105
  }
12660
- function callArgFromNode(node, propsParamName) {
13106
+ function callArgFromNode(node, propsParamName, themeBindingName) {
12661
13107
  if (!node || typeof node !== "object") return { kind: "unknown" };
12662
13108
  const type = node.type;
12663
- if (type === "MemberExpression" && typeof propsParamName === "string" && propsParamName) {
12664
- const parts = getMemberPathFromIdentifier(node, propsParamName);
12665
- if (parts && parts[0] === "theme" && parts.length > 1) return {
12666
- kind: "theme",
12667
- path: parts.slice(1).join(".")
12668
- };
13109
+ if (type === "MemberExpression") {
13110
+ if (typeof propsParamName === "string" && propsParamName) {
13111
+ const parts = getMemberPathFromIdentifier(node, propsParamName);
13112
+ if (parts && parts[0] === "theme" && parts.length > 1) return {
13113
+ kind: "theme",
13114
+ path: parts.slice(1).join(".")
13115
+ };
13116
+ }
13117
+ if (typeof themeBindingName === "string" && themeBindingName) {
13118
+ const parts = getMemberPathFromIdentifier(node, themeBindingName);
13119
+ if (parts && parts.length > 0) return {
13120
+ kind: "theme",
13121
+ path: parts.join(".")
13122
+ };
13123
+ }
12669
13124
  }
12670
13125
  if (type === "StringLiteral") return {
12671
13126
  kind: "literal",
@@ -12692,9 +13147,9 @@ function callArgFromNode(node, propsParamName) {
12692
13147
  }
12693
13148
  return { kind: "unknown" };
12694
13149
  }
12695
- function callArgsFromNode(args, propsParamName) {
13150
+ function callArgsFromNode(args, propsParamName, themeBindingName) {
12696
13151
  if (!Array.isArray(args)) return [];
12697
- return args.map((arg) => callArgFromNode(arg, propsParamName));
13152
+ return args.map((arg) => callArgFromNode(arg, propsParamName, themeBindingName));
12698
13153
  }
12699
13154
 
12700
13155
  //#endregion
@@ -13526,62 +13981,95 @@ function replaceThemeRefsWithHookVar(expr, paramName, info) {
13526
13981
  //#endregion
13527
13982
  //#region src/internal/builtin-handlers.ts
13528
13983
  /**
13529
- * Built-in resolution handlers for dynamic interpolations.
13530
- * Core concepts: adapter hooks, conditional splitting, and StyleX emission.
13531
- */
13532
- /**
13533
13984
  * Internal dynamic resolution pipeline.
13534
13985
  * Order matters: more-specific transforms first, then fall back to prop-access emission.
13535
13986
  */
13536
13987
  function resolveDynamicNode(node, ctx) {
13537
- return tryResolveThemeAccess(node, ctx) ?? tryResolveCallExpression(node, ctx) ?? tryResolveArrowFnHelperCallWithThemeArg(node, ctx) ?? tryResolveConditionalValue(node, ctx) ?? tryResolveIndexedThemeWithPropFallback(node, ctx) ?? tryResolveConditionalCssBlockTernary(node, ctx) ?? tryResolveConditionalCssBlock(node, ctx) ?? tryResolveArrowFnCallWithSinglePropArg(node) ?? tryResolveThemeDependentTemplateLiteral(node) ?? tryResolveStyleFunctionFromTemplateLiteral(node) ?? tryResolveInlineStyleValueForNestedPropAccess(node) ?? tryResolvePropAccess(node) ?? tryResolveInlineStyleValueForConditionalExpression(node) ?? tryResolveInlineStyleValueForLogicalExpression(node) ?? tryResolveInlineStyleValueFromArrowFn(node);
13988
+ 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) ?? tryResolveInlineStyleValueForConditionalExpression(node) ?? tryResolveInlineStyleValueForLogicalExpression(node) ?? tryResolveInlineStyleValueFromArrowFn(node);
13538
13989
  }
13539
13990
  function tryResolveThemeAccess(node, ctx) {
13540
13991
  const expr = node.expr;
13541
13992
  if (!isArrowFunctionExpression(expr)) return null;
13542
13993
  const info = getArrowFnThemeParamInfo(expr);
13543
13994
  if (!info) return null;
13544
- const body = expr.body;
13545
- if (body.type !== "MemberExpression") return null;
13546
- const path = (() => {
13547
- if (info.kind === "propsParam") {
13548
- const parts = getMemberPathFromIdentifier(body, info.propsName);
13549
- if (!parts || parts[0] !== "theme") return null;
13550
- return parts.slice(1).join(".");
13551
- }
13552
- const parts = getMemberPathFromIdentifier(body, info.themeName);
13553
- if (!parts) return null;
13554
- return parts.join(".");
13555
- })();
13995
+ const themeExpr = extractThemeMemberExpression(expr.body);
13996
+ if (!themeExpr) return null;
13997
+ const path = extractThemePath(themeExpr, info);
13556
13998
  if (!path) return null;
13557
13999
  const res = ctx.resolveValue({
13558
14000
  kind: "theme",
13559
14001
  path,
13560
14002
  filePath: ctx.filePath,
13561
- loc: getNodeLocStart(body) ?? void 0
14003
+ loc: getNodeLocStart(themeExpr) ?? void 0
13562
14004
  });
13563
14005
  if (!res) return null;
14006
+ const resultExpr = appendLogicalFallback(expr.body, res.expr);
14007
+ if (resultExpr === null) return null;
13564
14008
  return {
13565
14009
  type: "resolvedValue",
13566
- expr: res.expr,
14010
+ expr: resultExpr,
13567
14011
  imports: res.imports
13568
14012
  };
13569
14013
  }
14014
+ /**
14015
+ * Extracts the theme member expression from an arrow function body.
14016
+ *
14017
+ * Supports:
14018
+ * - Direct: `props.theme.color.labelBase` (MemberExpression)
14019
+ * - Logical fallback: `props.theme.color.labelBase ?? "black"` or `|| "default"`
14020
+ * (LogicalExpression with `??` or `||` and theme access on the left)
14021
+ *
14022
+ * Returns the MemberExpression node, or null if the pattern doesn't match.
14023
+ * The fallback (right side of `??`/`||`) is preserved by the caller via
14024
+ * `appendLogicalFallback` so users can review and delete it.
14025
+ */
14026
+ function extractThemeMemberExpression(body) {
14027
+ if (!body || typeof body !== "object") return null;
14028
+ const unwrapped = unwrapLogicalFallback(body);
14029
+ if (unwrapped && unwrapped.type === "MemberExpression") return unwrapped;
14030
+ if (body.type === "MemberExpression") return body;
14031
+ return null;
14032
+ }
14033
+ /**
14034
+ * Extracts the theme path (e.g., "color.labelBase") from a member expression,
14035
+ * accounting for whether the arrow function uses `props.theme.X` or `{ theme }` destructuring.
14036
+ */
14037
+ function extractThemePath(memberExpr, info) {
14038
+ const exprAsAny = memberExpr;
14039
+ if (info.kind === "propsParam") {
14040
+ const parts = getMemberPathFromIdentifier(exprAsAny, info.propsName);
14041
+ if (!parts || parts[0] !== "theme") return null;
14042
+ return parts.slice(1).join(".");
14043
+ }
14044
+ const parts = getMemberPathFromIdentifier(exprAsAny, info.themeName);
14045
+ if (!parts) return null;
14046
+ return parts.join(".");
14047
+ }
13570
14048
  function tryResolveArrowFnHelperCallWithThemeArg(node, ctx) {
13571
14049
  if (!node.css.property) return null;
13572
14050
  const expr = node.expr;
13573
14051
  if (!isArrowFunctionExpression(expr)) return null;
13574
- const propsParamName = getArrowFnSingleParamName(expr);
13575
- if (!propsParamName) return null;
13576
- const body = expr.body;
14052
+ const info = getArrowFnThemeParamInfo(expr);
14053
+ if (!info) return null;
14054
+ const body = getFunctionBodyExpr(expr);
13577
14055
  if (!isCallExpressionNode(body)) return null;
13578
14056
  const args = body.arguments ?? [];
13579
- if (args.length !== 1) return null;
13580
- const arg0 = args[0];
13581
- if (!arg0 || arg0.type !== "MemberExpression") return null;
13582
- const parts = getMemberPathFromIdentifier(arg0, propsParamName);
13583
- if (!parts || parts[0] !== "theme" || parts.length <= 1) return null;
13584
- const simple = resolveImportedHelperCall(body, ctx, propsParamName, node.css.property);
14057
+ if (args.length === 0) return null;
14058
+ const propsParamName = info.kind === "propsParam" ? info.propsName : void 0;
14059
+ const themeBindingName = info.kind === "themeBinding" ? info.themeName : void 0;
14060
+ if (!args.some((arg) => {
14061
+ if (!arg || arg.type !== "MemberExpression") return false;
14062
+ if (propsParamName) {
14063
+ const parts = getMemberPathFromIdentifier(arg, propsParamName);
14064
+ return parts !== null && parts[0] === "theme" && parts.length > 1;
14065
+ }
14066
+ if (themeBindingName) {
14067
+ const parts = getMemberPathFromIdentifier(arg, themeBindingName);
14068
+ return parts !== null && parts.length > 0;
14069
+ }
14070
+ return false;
14071
+ })) return null;
14072
+ const simple = resolveImportedHelperCall(body, ctx, propsParamName, node.css.property, themeBindingName);
13585
14073
  if (simple.kind === "resolved") return buildResolvedHandlerResult(simple.result, node.css.property, {
13586
14074
  resolveCallContext: simple.resolveCallContext,
13587
14075
  resolveCallResult: simple.resolveCallResult
@@ -13589,7 +14077,145 @@ function tryResolveArrowFnHelperCallWithThemeArg(node, ctx) {
13589
14077
  if (simple.kind === "unresolved") return buildUnresolvedHelperResult(body.callee, ctx);
13590
14078
  return null;
13591
14079
  }
13592
- function tryResolveArrowFnCallWithSinglePropArg(node) {
14080
+ /**
14081
+ * Handles arrow functions whose body is a call expression with one conditional argument.
14082
+ *
14083
+ * Pattern: `({ $oneLine }) => truncateMultiline($oneLine ? 1 : 2)`
14084
+ *
14085
+ * The conditional argument must have a prop-based test and literal branches.
14086
+ * All other arguments must be literals. Each branch is resolved independently
14087
+ * via the adapter's resolveCall, and the result is split into conditional variants.
14088
+ */
14089
+ function tryResolveArrowFnCallWithConditionalArgs(node, ctx) {
14090
+ const expr = node.expr;
14091
+ if (!isArrowFunctionExpression(expr)) return null;
14092
+ const body = getFunctionBodyExpr(expr);
14093
+ if (!isCallExpressionNode(body)) return null;
14094
+ const callee = body.callee;
14095
+ if (!callee || callee.type !== "Identifier" || typeof callee.name !== "string") return null;
14096
+ const calleeIdent = callee.name;
14097
+ const bindings = getArrowFnParamBindings(expr);
14098
+ if (!bindings) return null;
14099
+ const args = body.arguments ?? [];
14100
+ if (args.length === 0) return null;
14101
+ let conditionalArgIndex = -1;
14102
+ let propName = null;
14103
+ let consequentValue;
14104
+ let alternateValue;
14105
+ let conditionalDefaultTruthy = null;
14106
+ const staticArgValues = /* @__PURE__ */ new Map();
14107
+ for (let i = 0; i < args.length; i++) {
14108
+ const arg = args[i];
14109
+ if (!arg) continue;
14110
+ if (arg.type === "ConditionalExpression") {
14111
+ if (conditionalArgIndex !== -1) return null;
14112
+ conditionalArgIndex = i;
14113
+ propName = extractPropNameFromCondTest(arg.test, bindings);
14114
+ if (!propName) return null;
14115
+ if (propName === "theme") return null;
14116
+ if (bindings.kind === "destructured" && bindings.defaults && bindings.defaults.has(propName)) {
14117
+ const defaultValue = extractStaticLiteralValue(bindings.defaults.get(propName));
14118
+ if (defaultValue === void 0) return null;
14119
+ conditionalDefaultTruthy = Boolean(defaultValue);
14120
+ }
14121
+ consequentValue = extractStaticLiteralValue(arg.consequent);
14122
+ alternateValue = extractStaticLiteralValue(arg.alternate);
14123
+ if (consequentValue === void 0 || alternateValue === void 0) return null;
14124
+ } else {
14125
+ const v = extractStaticLiteralValue(arg);
14126
+ if (v === void 0) return null;
14127
+ staticArgValues.set(i, v);
14128
+ }
14129
+ }
14130
+ if (conditionalArgIndex === -1 || !propName || consequentValue === void 0 || alternateValue === void 0) return null;
14131
+ const imp = ctx.resolveImport(calleeIdent, callee);
14132
+ if (!imp) return null;
14133
+ const consValue = consequentValue;
14134
+ const altValue = alternateValue;
14135
+ const buildBranchContext = (branchValue) => {
14136
+ const syntheticArgs = args.map((_arg, i) => {
14137
+ if (i === conditionalArgIndex) return {
14138
+ kind: "literal",
14139
+ value: branchValue
14140
+ };
14141
+ const v = staticArgValues.get(i);
14142
+ return v !== void 0 ? {
14143
+ kind: "literal",
14144
+ value: v
14145
+ } : { kind: "unknown" };
14146
+ });
14147
+ const loc = body.loc?.start;
14148
+ return {
14149
+ callSiteFilePath: ctx.filePath,
14150
+ calleeImportedName: imp.importedName,
14151
+ calleeSource: imp.source,
14152
+ args: syntheticArgs,
14153
+ ...loc ? { loc: {
14154
+ line: loc.line,
14155
+ column: loc.column
14156
+ } } : {},
14157
+ ...node.css.property ? { cssProperty: node.css.property } : {}
14158
+ };
14159
+ };
14160
+ const consResult = ctx.resolveCall(buildBranchContext(consValue));
14161
+ if (!consResult) return null;
14162
+ const altResult = ctx.resolveCall(buildBranchContext(altValue));
14163
+ if (!altResult) return null;
14164
+ const consIsCss = isAdapterResultCssValue(consResult, node.css.property);
14165
+ if (consIsCss !== isAdapterResultCssValue(altResult, node.css.property)) return null;
14166
+ const truthyWhen = conditionalDefaultTruthy === true ? `${propName} === undefined || ${propName}` : propName;
14167
+ const falsyWhen = conditionalDefaultTruthy === true ? `!(${truthyWhen})` : `!${propName}`;
14168
+ const variants = [{
14169
+ nameHint: "truthy",
14170
+ when: truthyWhen,
14171
+ expr: consResult.expr,
14172
+ imports: consResult.imports
14173
+ }, {
14174
+ nameHint: "falsy",
14175
+ when: falsyWhen,
14176
+ expr: altResult.expr,
14177
+ imports: altResult.imports
14178
+ }];
14179
+ return consIsCss ? {
14180
+ type: "splitVariantsResolvedValue",
14181
+ variants
14182
+ } : {
14183
+ type: "splitVariantsResolvedStyles",
14184
+ variants
14185
+ };
14186
+ }
14187
+ /**
14188
+ * Extracts a static literal value from an AST node, distinguishing null literals
14189
+ * from extraction failure. Returns `undefined` when the node is not a recognized
14190
+ * static literal, and the actual value (including `null`) otherwise.
14191
+ */
14192
+ function extractStaticLiteralValue(node) {
14193
+ if (!node || typeof node !== "object") return;
14194
+ const type = node.type;
14195
+ if (type === "ArrowFunctionExpression" || type === "FunctionExpression") return;
14196
+ if (type === "NullLiteral") return null;
14197
+ if (type === "Literal" && node.value === null) return null;
14198
+ const v = literalToStaticValue(node);
14199
+ return v !== null ? v : void 0;
14200
+ }
14201
+ /**
14202
+ * Extracts the prop name from a conditional expression's test node.
14203
+ *
14204
+ * Supports:
14205
+ * - Simple param: `props.$oneLine` → `$oneLine`
14206
+ * - Destructured param: `$oneLine` (identifier) → `$oneLine`
14207
+ */
14208
+ function extractPropNameFromCondTest(test, bindings) {
14209
+ if (!test || typeof test !== "object" || !bindings) return null;
14210
+ const resolved = resolveIdentifierToPropName(test, bindings);
14211
+ if (resolved !== null) return resolved;
14212
+ if (bindings.kind === "simple" && test.type === "MemberExpression") {
14213
+ const path = getMemberPathFromIdentifier(test, bindings.paramName);
14214
+ if (path && path.length === 1 && path[0]) return path[0];
14215
+ }
14216
+ return null;
14217
+ }
14218
+ function tryResolveArrowFnCallWithSinglePropArg(node, ctx) {
13593
14219
  if (!node.css.property) return null;
13594
14220
  const expr = node.expr;
13595
14221
  if (!isArrowFunctionExpression(expr)) return null;
@@ -13606,6 +14232,7 @@ function tryResolveArrowFnCallWithSinglePropArg(node) {
13606
14232
  const path = getMemberPathFromIdentifier(arg0, paramName);
13607
14233
  const propName = path?.[0];
13608
14234
  if (!path || path.length !== 1 || !propName) return null;
14235
+ const adapterResolution = tryResolveCalleeViaAdapter(calleeIdent, body.callee, node, ctx);
13609
14236
  return {
13610
14237
  type: "emitStyleFunction",
13611
14238
  nameHint: `${sanitizeIdentifier(node.css.property)}FromProp`,
@@ -13614,10 +14241,39 @@ function tryResolveArrowFnCallWithSinglePropArg(node) {
13614
14241
  call: propName,
13615
14242
  valueTransform: {
13616
14243
  kind: "call",
13617
- calleeIdent
14244
+ calleeIdent,
14245
+ ...adapterResolution
13618
14246
  }
13619
14247
  };
13620
14248
  }
14249
+ /**
14250
+ * Attempts to resolve a callee identifier through the adapter's resolveCall hook.
14251
+ * Uses `resolveCallOptional` (non-bailing) so that an unhandled helper does NOT
14252
+ * trigger the global bail flag — the caller falls back to preserving the original call.
14253
+ * Returns resolved expression and imports if the adapter handles it,
14254
+ * or an empty object to fall back to preserving the original helper call.
14255
+ */
14256
+ function tryResolveCalleeViaAdapter(calleeIdent, calleeNode, node, ctx) {
14257
+ const resolveCall = ctx.resolveCallOptional;
14258
+ if (!resolveCall) return {};
14259
+ const imp = ctx.resolveImport(calleeIdent, calleeNode);
14260
+ if (!imp) return {};
14261
+ try {
14262
+ const result = resolveCall({
14263
+ callSiteFilePath: ctx.filePath,
14264
+ calleeImportedName: imp.importedName,
14265
+ calleeSource: imp.source,
14266
+ args: [{ kind: "unknown" }],
14267
+ cssProperty: node.css.property
14268
+ });
14269
+ if (result) return {
14270
+ resolvedExpr: result.expr,
14271
+ resolvedImports: result.imports,
14272
+ ...result.dynamicArgUsage ? { resolvedUsage: result.dynamicArgUsage } : {}
14273
+ };
14274
+ } catch {}
14275
+ return {};
14276
+ }
13621
14277
  function tryResolveInlineStyleValueForConditionalExpression(node) {
13622
14278
  if (!node.css.property) return null;
13623
14279
  const expr = node.expr;
@@ -13662,17 +14318,26 @@ function tryResolveInlineStyleValueForLogicalExpression(node) {
13662
14318
  };
13663
14319
  return { type: "emitInlineStyleValueFromProps" };
13664
14320
  }
13665
- function tryResolveThemeDependentTemplateLiteral(node) {
14321
+ function tryResolveThemeDependentTemplateLiteral(node, ctx) {
13666
14322
  if (!node.css.property) return null;
13667
14323
  const expr = node.expr;
13668
14324
  if (!isArrowFunctionExpression(expr)) return null;
13669
14325
  const body = getFunctionBodyExpr(expr);
13670
14326
  if (!body || body.type !== "TemplateLiteral") return null;
13671
- if (hasThemeAccessInArrowFn(expr)) return {
14327
+ if (!hasThemeAccessInArrowFn(expr)) return null;
14328
+ const paramName = getArrowFnSingleParamName(expr);
14329
+ if (paramName) {
14330
+ const resolved = resolveTemplateLiteralWithTheme(body, paramName, ctx);
14331
+ if (resolved) return {
14332
+ type: "resolvedValue",
14333
+ expr: resolved.expr,
14334
+ imports: resolved.imports
14335
+ };
14336
+ }
14337
+ return {
13672
14338
  type: "keepOriginal",
13673
14339
  reason: "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())"
13674
14340
  };
13675
- return null;
13676
14341
  }
13677
14342
  function tryResolveStyleFunctionFromTemplateLiteral(node) {
13678
14343
  if (!node.css.property) return null;
@@ -13797,6 +14462,23 @@ function tryResolvePropAccess(node) {
13797
14462
  call: propName
13798
14463
  };
13799
14464
  }
14465
+ /**
14466
+ * If `body` is a logical fallback expression (`X ?? "default"` / `X || "default"`),
14467
+ * appends the operator and fallback literal to the resolved expression string.
14468
+ *
14469
+ * Returns `null` when the body IS a logical expression but the fallback is non-literal
14470
+ * (e.g., `props.fallbackColor`, `null`, `undefined`), signalling to the caller that
14471
+ * it's unsafe to drop the fallback — the caller should bail instead of resolving.
14472
+ *
14473
+ * Returns `resolvedExpr` unchanged when the body is NOT a logical expression (no
14474
+ * fallback to preserve).
14475
+ */
14476
+ function appendLogicalFallback(body, resolvedExpr) {
14477
+ if (!isLogicalExpressionNode(body) || body.operator !== "??" && body.operator !== "||") return resolvedExpr;
14478
+ const fallback = literalToStaticValue(body.right);
14479
+ if (fallback === null) return null;
14480
+ return `${resolvedExpr} ${body.operator} ${JSON.stringify(fallback)}`;
14481
+ }
13800
14482
 
13801
14483
  //#endregion
13802
14484
  //#region src/internal/lower-rules/template-literals.ts
@@ -15697,7 +16379,7 @@ const createValuePatternHandlers = (ctx) => {
15697
16379
  * Core concepts: per-component style buckets, helper factories, and resolver wiring.
15698
16380
  */
15699
16381
  function createDeclProcessingState(state, decl) {
15700
- const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveCall, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, markBail } = state;
16382
+ const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveCall, resolveCallOptional, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, markBail } = state;
15701
16383
  const styleObj = {};
15702
16384
  const perPropPseudo = {};
15703
16385
  const perPropMedia = {};
@@ -15771,6 +16453,7 @@ function createDeclProcessingState(state, decl) {
15771
16453
  filePath,
15772
16454
  resolveValue,
15773
16455
  resolveCall,
16456
+ resolveCallOptional,
15774
16457
  resolveImport: resolveImportInScope,
15775
16458
  hasImportIgnoringShadowing: (localName) => importMap.has(localName)
15776
16459
  };
@@ -15979,7 +16662,88 @@ function tryHandleInterpolatedBorder(ctx, args) {
15979
16662
  const direction = directionRaw ? directionRaw.slice(1).charAt(0).toUpperCase() + directionRaw.slice(2) : "";
15980
16663
  if (d.value.kind !== "interpolated") return false;
15981
16664
  if (typeof d.valueRaw !== "string") return false;
15982
- const slotTok = d.valueRaw.trim().split(/\s+/).filter(Boolean).find((t) => /^__SC_EXPR_(\d+)__$/.test(t));
16665
+ const tokens = d.valueRaw.trim().split(/\s+/).filter(Boolean);
16666
+ const callResolveDynamicNode = (slotId, expr, loc) => {
16667
+ return resolveDynamicNode({
16668
+ slotId,
16669
+ expr,
16670
+ css: {
16671
+ kind: "declaration",
16672
+ selector: "&",
16673
+ atRuleStack: [],
16674
+ property: prop,
16675
+ valueRaw: d.valueRaw
16676
+ },
16677
+ component: decl.base.kind === "intrinsic" ? {
16678
+ localName: decl.localName,
16679
+ base: "intrinsic",
16680
+ tagOrIdent: decl.base.tagName
16681
+ } : {
16682
+ localName: decl.localName,
16683
+ base: "component",
16684
+ tagOrIdent: decl.base.ident
16685
+ },
16686
+ usage: {
16687
+ jsxUsages: 0,
16688
+ hasPropsSpread: false
16689
+ },
16690
+ ...loc ? { loc } : {}
16691
+ }, {
16692
+ api,
16693
+ filePath,
16694
+ resolveValue,
16695
+ resolveCall,
16696
+ resolveImport: (localName) => importMap.get(localName) ?? null
16697
+ });
16698
+ };
16699
+ {
16700
+ const slotIndices = [];
16701
+ for (let i = 0; i < tokens.length; i++) {
16702
+ const m = tokens[i].match(/^__SC_EXPR_(\d+)__$/);
16703
+ if (m?.[1]) slotIndices.push({
16704
+ idx: i,
16705
+ slotId: Number(m[1])
16706
+ });
16707
+ }
16708
+ if (slotIndices.length === 2 && tokens.length === 3) {
16709
+ const [slot0, slot1] = slotIndices;
16710
+ if (slot0.idx === 0 && slot1.idx === 2) {
16711
+ const middleStyle = tokens[1].trim();
16712
+ if (BORDER_STYLES.has(middleStyle)) {
16713
+ const widthProp = `border${direction}Width`;
16714
+ const styleProp = `border${direction}Style`;
16715
+ const colorProp = `border${direction}Color`;
16716
+ const slot0Expr = decl.templateExpressions[slot0.slotId];
16717
+ const slot1Expr = decl.templateExpressions[slot1.slotId];
16718
+ const slot0Res = callResolveDynamicNode(slot0.slotId, slot0Expr);
16719
+ const slot1Res = callResolveDynamicNode(slot1.slotId, slot1Expr);
16720
+ if (slot0Res?.type === "resolvedValue" && slot1Res?.type === "resolvedValue") {
16721
+ for (const imp of [...slot0Res.imports ?? [], ...slot1Res.imports ?? []]) resolverImports.set(JSON.stringify(imp), imp);
16722
+ const slot0Ast = parseExpr(slot0Res.expr);
16723
+ const slot1Ast = parseExpr(slot1Res.expr);
16724
+ if (slot0Ast && slot1Ast) {
16725
+ const slot0Role = classifyBorderSlotRole(slot0Ast);
16726
+ const slot1Role = classifyBorderSlotRole(slot1Ast);
16727
+ if (slot0Role && slot1Role && slot0Role === slot1Role) {
16728
+ bailUnsupportedWithContext("Multi-slot border interpolation could not be resolved", { property: prop }, getNodeLocStart(slot0Expr) ?? getNodeLocStart(slot1Expr));
16729
+ return true;
16730
+ }
16731
+ const shouldSwap = slot0Role === "color" || slot1Role === "width";
16732
+ const widthAst = shouldSwap ? slot1Ast : slot0Ast;
16733
+ const colorAst = shouldSwap ? slot0Ast : slot1Ast;
16734
+ applyResolvedPropValue(widthProp, widthAst);
16735
+ applyResolvedPropValue(styleProp, middleStyle);
16736
+ applyResolvedPropValue(colorProp, colorAst);
16737
+ return true;
16738
+ }
16739
+ }
16740
+ bailUnsupportedWithContext("Multi-slot border interpolation could not be resolved", { property: prop }, getNodeLocStart(slot0Expr) ?? getNodeLocStart(slot1Expr));
16741
+ return true;
16742
+ }
16743
+ }
16744
+ }
16745
+ }
16746
+ const slotTok = tokens.find((t) => /^__SC_EXPR_(\d+)__$/.test(t));
15983
16747
  if (!slotTok) return false;
15984
16748
  const slotMatch = slotTok.match(/^__SC_EXPR_(\d+)__$/);
15985
16749
  if (!slotMatch || !slotMatch[1]) return false;
@@ -16083,40 +16847,7 @@ function tryHandleInterpolatedBorder(ctx, args) {
16083
16847
  const unresolvedCallWarning = (callIdent ? importMap.has(callIdent) : false) ? "Adapter helper call in border interpolation did not resolve to a single CSS value" : "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))";
16084
16848
  const resolveBorderExpr = (node) => {
16085
16849
  const loc = getNodeLocStart(node);
16086
- const res = resolveDynamicNode({
16087
- slotId,
16088
- expr: node,
16089
- css: {
16090
- kind: "declaration",
16091
- selector: "&",
16092
- atRuleStack: [],
16093
- property: prop,
16094
- valueRaw: d.valueRaw
16095
- },
16096
- component: decl.base.kind === "intrinsic" ? {
16097
- localName: decl.localName,
16098
- base: "intrinsic",
16099
- tagOrIdent: decl.base.tagName
16100
- } : {
16101
- localName: decl.localName,
16102
- base: "component",
16103
- tagOrIdent: decl.base.ident
16104
- },
16105
- usage: {
16106
- jsxUsages: 0,
16107
- hasPropsSpread: false
16108
- },
16109
- ...loc ? { loc } : {}
16110
- }, {
16111
- api,
16112
- filePath,
16113
- resolveValue,
16114
- resolveCall,
16115
- resolveImport: (localName, _identNode) => {
16116
- const v = importMap.get(localName);
16117
- return v ? v : null;
16118
- }
16119
- });
16850
+ const res = callResolveDynamicNode(slotId, node, loc);
16120
16851
  if (!res) return {
16121
16852
  kind: "warn",
16122
16853
  warning: unresolvedCallWarning
@@ -16333,6 +17064,17 @@ function tryHandleInterpolatedBorder(ctx, args) {
16333
17064
  }
16334
17065
  return false;
16335
17066
  }
17067
+ /**
17068
+ * Classify a resolved border slot expression as "width" or "color" when possible.
17069
+ * Only string literals can be reliably classified: `looksLikeLength("0.5px")` → "width",
17070
+ * anything else → "color". Returns null for opaque expressions (variables, member
17071
+ * expressions) that cannot be classified at compile time.
17072
+ */
17073
+ function classifyBorderSlotRole(ast) {
17074
+ const node = ast;
17075
+ if ((node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string") return looksLikeLength(node.value) ? "width" : "color";
17076
+ return null;
17077
+ }
16336
17078
 
16337
17079
  //#endregion
16338
17080
  //#region src/internal/lower-rules/interpolated-variant-resolvers.ts
@@ -16829,7 +17571,8 @@ function handleInterpolatedDeclaration(args) {
16829
17571
  keyframesNames,
16830
17572
  styleObj
16831
17573
  })) continue;
16832
- if (isUnsupportedDynamicPseudoElement(pseudoElement)) {
17574
+ if (isPseudoElementSelector(pseudoElement)) {
17575
+ if (tryHandleDynamicPseudoElementViaCustomProperty(args)) continue;
16833
17576
  warnings.push({
16834
17577
  severity: "error",
16835
17578
  type: "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396",
@@ -17620,7 +18363,15 @@ function handleInterpolatedDeclaration(args) {
17620
18363
  const buildValueExpr = () => {
17621
18364
  const transformed = (() => {
17622
18365
  const vt = res.valueTransform;
17623
- if (vt?.kind === "call" && typeof vt.calleeIdent === "string") return j.callExpression(j.identifier(vt.calleeIdent), [valueId]);
18366
+ if (vt?.kind === "call" && typeof vt.calleeIdent === "string") {
18367
+ if (vt.resolvedImports) for (const imp of vt.resolvedImports) resolverImports.set(JSON.stringify(imp), imp);
18368
+ if (vt.resolvedExpr) {
18369
+ const resolvedCallee = parseExpr(vt.resolvedExpr);
18370
+ if (vt.resolvedUsage === "memberAccess") return j.memberExpression(resolvedCallee, valueId, true);
18371
+ return j.callExpression(resolvedCallee, [valueId]);
18372
+ }
18373
+ return j.callExpression(j.identifier(vt.calleeIdent), [valueId]);
18374
+ }
17624
18375
  return valueId;
17625
18376
  })();
17626
18377
  const transformedValue = !!res.wrapValueInTemplateLiteral ? j.templateLiteral([j.templateElement({
@@ -17838,9 +18589,69 @@ function handleInterpolatedDeclaration(args) {
17838
18589
  if (state.bail) bail = true;
17839
18590
  if (bail) state.markBail();
17840
18591
  }
17841
- function isUnsupportedDynamicPseudoElement(pseudoElement) {
17842
- return pseudoElement === "::before" || pseudoElement === "::after" || pseudoElement === ":before" || pseudoElement === ":after";
18592
+ function isPseudoElementSelector(pseudoElement) {
18593
+ return pseudoElement === "::before" || pseudoElement === "::after";
17843
18594
  }
18595
+ /**
18596
+ * Handles dynamic interpolations inside ::before/::after pseudo-elements by emitting
18597
+ * CSS custom properties on the parent element and referencing them with var() in the
18598
+ * pseudo-element's static StyleX styles.
18599
+ *
18600
+ * Example transform:
18601
+ * Input: `&::after { background-color: ${(props) => props.$badgeColor}; }`
18602
+ * Output: StyleX → `"::after": { backgroundColor: "var(--Badge-after-backgroundColor)" }`
18603
+ * Inline → `style={{ "--Badge-after-backgroundColor": $badgeColor }}`
18604
+ *
18605
+ * Bails (returns false) for unsupported shapes: multi-slot interpolations or CSS shorthands.
18606
+ */
18607
+ function tryHandleDynamicPseudoElementViaCustomProperty(args) {
18608
+ const { ctx, d, pseudoElement, applyResolvedPropValue } = args;
18609
+ const { state, decl, inlineStyleProps } = ctx;
18610
+ const { j } = state;
18611
+ if (!d.property || d.value.kind !== "interpolated") return false;
18612
+ const slotParts = (d.value.parts ?? []).filter((p) => p.kind === "slot");
18613
+ if (slotParts.length !== 1) return false;
18614
+ const slotPart = slotParts[0];
18615
+ const expr = decl.templateExpressions[slotPart.slotId];
18616
+ if (!expr || expr.type !== "ArrowFunctionExpression" && expr.type !== "FunctionExpression") return false;
18617
+ if (hasThemeAccessInArrowFn(expr)) return false;
18618
+ const unwrapped = unwrapArrowFunctionToPropsExpr(j, expr);
18619
+ if (!unwrapped) return false;
18620
+ const { expr: inlineExpr, propsUsed } = unwrapped;
18621
+ const { prefix, suffix } = extractStaticParts(d.value);
18622
+ const valueExpr = prefix || suffix ? buildTemplateWithStaticParts(j, inlineExpr, prefix, suffix) : inlineExpr;
18623
+ const stylexDecls = cssDeclarationToStylexDeclarations(d);
18624
+ if (stylexDecls.some((out) => UNSUPPORTED_CUSTOM_PROP_SHORTHANDS.has(out.prop))) return false;
18625
+ const pseudoLabel = pseudoElement ? pseudoElement.replace(/^:+/, "") : "";
18626
+ const existingPropNames = new Set(inlineStyleProps.map((p) => p.prop));
18627
+ for (const out of stylexDecls) {
18628
+ if (!out.prop) continue;
18629
+ const customPropName = pseudoLabel ? `--${decl.localName}-${pseudoLabel}-${out.prop}` : `--${decl.localName}-${out.prop}`;
18630
+ if (existingPropNames.has(customPropName)) return false;
18631
+ applyResolvedPropValue(out.prop, `var(${customPropName})`, null);
18632
+ inlineStyleProps.push({
18633
+ prop: customPropName,
18634
+ expr: valueExpr
18635
+ });
18636
+ }
18637
+ for (const propName of propsUsed) ensureShouldForwardPropDrop(decl, propName);
18638
+ decl.needsWrapperComponent = true;
18639
+ return true;
18640
+ }
18641
+ /** CSS shorthand properties that cannot be represented as a single var() custom property. */
18642
+ const UNSUPPORTED_CUSTOM_PROP_SHORTHANDS = new Set([
18643
+ "border",
18644
+ "margin",
18645
+ "padding",
18646
+ "background",
18647
+ "flex",
18648
+ "overflow",
18649
+ "outline",
18650
+ "borderTop",
18651
+ "borderRight",
18652
+ "borderBottom",
18653
+ "borderLeft"
18654
+ ]);
17844
18655
 
17845
18656
  //#endregion
17846
18657
  //#region src/internal/lower-rules/process-rule-declarations.ts
@@ -17899,7 +18710,7 @@ function processRuleDeclarations(args) {
17899
18710
  //#endregion
17900
18711
  //#region src/internal/lower-rules/process-rules.ts
17901
18712
  function processDeclRules(ctx) {
17902
- const { state, decl, styleObj, perPropPseudo, perPropMedia, perPropComputedMedia, nestedSelectors, attrBuckets, localVarValues, cssHelperPropValues, getComposedDefaultValue } = ctx;
18713
+ const { state, decl, styleObj, perPropPseudo, perPropMedia, nestedSelectors, attrBuckets, localVarValues, cssHelperPropValues, getComposedDefaultValue } = ctx;
17903
18714
  const { j, root, warnings, resolverImports, resolveSelector, parseExpr, cssHelperNames, declByLocalName, styledDecls, relationOverridePseudoBuckets, relationOverrides, ancestorSelectorParents, childPseudoMarkers, resolveThemeValue, resolveThemeValueFromFn, resolveImportInScope } = state;
17904
18715
  /**
17905
18716
  * Attempts to resolve an element selector (e.g., `& svg`, `& > button`) to a
@@ -18003,7 +18814,7 @@ function processDeclRules(ctx) {
18003
18814
  continue;
18004
18815
  }
18005
18816
  const selectorTrimmed = selectorForAnalysis.trim();
18006
- const isHandledComponentPattern = hasComponentExpr && (/^__SC_EXPR_\d+__:[a-z][a-z0-9()-]*\s+&\s*$/.test(selectorTrimmed) || selectorTrimmed.startsWith("&") || /^__SC_EXPR_\d+__\s*\{/.test(selectorTrimmed));
18817
+ const isHandledComponentPattern = hasComponentExpr && (/^__SC_EXPR_\d+__:[a-z][a-z0-9()-]*\s+&\s*$/.test(selectorTrimmed) || isCommaGroupedReverseSelectorPattern(selectorTrimmed) || selectorTrimmed.startsWith("&") || /^__SC_EXPR_\d+__\s*\{/.test(selectorTrimmed));
18007
18818
  if (/&\s+:/.test(rule.selector)) {
18008
18819
  state.markBail();
18009
18820
  warnings.push({
@@ -18014,11 +18825,12 @@ function processDeclRules(ctx) {
18014
18825
  break;
18015
18826
  }
18016
18827
  if (s.includes(",") && !isHandledComponentPattern) {
18017
- if (parseSelector(s).kind !== "pseudo") {
18828
+ const parsed = parseSelector(s);
18829
+ if (parsed.kind !== "pseudo" && parsed.kind !== "pseudoElements") {
18018
18830
  state.markBail();
18019
18831
  warnings.push({
18020
18832
  severity: "warning",
18021
- type: "Unsupported selector: comma-separated selectors must all be simple pseudos",
18833
+ type: "Unsupported selector: comma-separated selectors must all be simple pseudos or pseudo-elements",
18022
18834
  loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
18023
18835
  });
18024
18836
  break;
@@ -18032,6 +18844,10 @@ function processDeclRules(ctx) {
18032
18844
  });
18033
18845
  break;
18034
18846
  } else if (/[+~]/.test(s) && !isHandledComponentPattern) {
18847
+ if (/^&\s*[+~]\s*&$/.test(s)) {
18848
+ if (handleSiblingSelector(s, rule, ctx) === "break") break;
18849
+ continue;
18850
+ }
18035
18851
  state.markBail();
18036
18852
  warnings.push({
18037
18853
  severity: "warning",
@@ -18053,17 +18869,43 @@ function processDeclRules(ctx) {
18053
18869
  }
18054
18870
  }
18055
18871
  if (typeof rule.selector === "string" && rule.selector.includes("__SC_EXPR_")) {
18056
- const slotMatch = rule.selector.match(/__SC_EXPR_(\d+)__/);
18872
+ const slotMatch = rule.selector.match(PLACEHOLDER_RE);
18057
18873
  const slotId = slotMatch ? Number(slotMatch[1]) : null;
18058
18874
  const slotExpr = slotId !== null ? decl.templateExpressions[slotId] : null;
18059
18875
  const otherLocal = slotExpr?.type === "Identifier" ? slotExpr.name : null;
18060
18876
  const isCssHelperPlaceholder = !!otherLocal && cssHelperNames.has(otherLocal);
18061
18877
  const selTrim2 = rule.selector.trim();
18062
- const isReverseSelectorPattern = selTrim2.startsWith("__SC_EXPR_") && !selTrim2.includes(",") && /^__SC_EXPR_\d+__:[a-z][a-z0-9()-]*\s+&\s*$/.test(selTrim2);
18063
- if (otherLocal && !isCssHelperPlaceholder && isReverseSelectorPattern) {
18064
- const ancestorPseudo = rule.selector.match(/__SC_EXPR_\d+__(:[a-z-]+(?:\([^)]*\))?)/i)?.[1] ?? null;
18878
+ const isReverseSelectorPattern = selTrim2.startsWith("__SC_EXPR_") && /^__SC_EXPR_\d+__:[a-z][a-z0-9()-]*\s+&\s*$/.test(selTrim2);
18879
+ const isGroupedReverseSelectorPattern = !isReverseSelectorPattern && selTrim2.startsWith("__SC_EXPR_") && isCommaGroupedReverseSelectorPattern(selTrim2);
18880
+ if (otherLocal && !isCssHelperPlaceholder && (isReverseSelectorPattern || isGroupedReverseSelectorPattern)) {
18881
+ if (isGroupedReverseSelectorPattern) {
18882
+ if ([...selTrim2.matchAll(new RegExp(PLACEHOLDER_RE.source, "g"))].map((m) => {
18883
+ const id = Number(m[1]);
18884
+ const expr = decl.templateExpressions[id];
18885
+ return expr?.type === "Identifier" ? expr.name : null;
18886
+ }).some((name) => name !== otherLocal)) {
18887
+ state.markBail();
18888
+ warnings.push({
18889
+ severity: "warning",
18890
+ type: "Unsupported selector: grouped reverse selector references different components",
18891
+ loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
18892
+ });
18893
+ break;
18894
+ }
18895
+ }
18065
18896
  const parentDecl = declByLocalName.get(otherLocal);
18066
- if (!parentDecl) {
18897
+ const crossFileParent = !parentDecl ? state.crossFileSelectorsByLocal.get(otherLocal) : void 0;
18898
+ if (!parentDecl && !crossFileParent) {
18899
+ state.markBail();
18900
+ warnings.push({
18901
+ severity: "warning",
18902
+ type: "Unsupported selector: unknown component selector",
18903
+ loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
18904
+ });
18905
+ break;
18906
+ }
18907
+ const ancestorPseudos = extractReverseSelectorPseudos(rule.selector);
18908
+ if (ancestorPseudos.length === 0) {
18067
18909
  state.markBail();
18068
18910
  warnings.push({
18069
18911
  severity: "warning",
@@ -18072,9 +18914,16 @@ function processDeclRules(ctx) {
18072
18914
  });
18073
18915
  break;
18074
18916
  }
18075
- const overrideStyleKey = `${toStyleKey(decl.localName)}In${otherLocal}`;
18076
- ancestorSelectorParents.add(parentDecl.styleKey);
18077
- if (processDeclarationsIntoBucket(rule, getOrCreateRelationOverrideBucket(overrideStyleKey, parentDecl.styleKey, decl.styleKey, ancestorPseudo, relationOverrides, relationOverridePseudoBuckets, decl.extraStyleKeys), j, decl, resolveThemeValue, resolveThemeValueFromFn, { bailOnUnresolved: true }) === "bail") {
18917
+ const jsxParentName = crossFileParent?.bridgeComponentLocalName ?? otherLocal;
18918
+ const parentStyleKey = parentDecl ? parentDecl.styleKey : toStyleKey(jsxParentName);
18919
+ const overrideStyleKey = `${toStyleKey(decl.localName)}In${jsxParentName}`;
18920
+ ancestorSelectorParents.add(parentStyleKey);
18921
+ const reverseMarkerVarName = crossFileParent ? `${jsxParentName}Marker` : void 0;
18922
+ const overrideCountBeforeReverse = relationOverrides.length;
18923
+ const firstBucket = getOrCreateRelationOverrideBucket(overrideStyleKey, parentStyleKey, decl.styleKey, ancestorPseudos[0], relationOverrides, relationOverridePseudoBuckets, decl.extraStyleKeys);
18924
+ tagCrossFileOverride(relationOverrides, overrideCountBeforeReverse, reverseMarkerVarName, jsxParentName);
18925
+ const result = processDeclarationsIntoBucket(rule, firstBucket, j, decl, resolveThemeValue, resolveThemeValueFromFn, { bailOnUnresolved: true });
18926
+ if (result === "bail") {
18078
18927
  state.markBail();
18079
18928
  warnings.push({
18080
18929
  severity: "warning",
@@ -18083,13 +18932,19 @@ function processDeclRules(ctx) {
18083
18932
  });
18084
18933
  break;
18085
18934
  }
18935
+ const writtenProps = result;
18936
+ for (let i = 1; i < ancestorPseudos.length; i++) {
18937
+ const bucket = getOrCreateRelationOverrideBucket(overrideStyleKey, parentStyleKey, decl.styleKey, ancestorPseudos[i], relationOverrides, relationOverridePseudoBuckets, decl.extraStyleKeys);
18938
+ for (const key of writtenProps) bucket[key] = firstBucket[key];
18939
+ }
18086
18940
  continue;
18087
18941
  }
18088
18942
  const isComponentSelectorPattern = selTrim2.startsWith("&") || /^__SC_EXPR_\d+__$/.test(selTrim2);
18089
18943
  if (otherLocal && !isCssHelperPlaceholder && isComponentSelectorPattern) {
18090
18944
  const childDecl = declByLocalName.get(otherLocal);
18945
+ const crossFileUsage = !childDecl ? state.crossFileSelectorsByLocal.get(otherLocal) : void 0;
18091
18946
  const ancestorPseudo = rule.selector.match(/&(:[a-z-]+(?:\([^)]*\))?)/i)?.[1] ?? null;
18092
- if (!childDecl) {
18947
+ if (!childDecl && !crossFileUsage) {
18093
18948
  state.markBail();
18094
18949
  warnings.push({
18095
18950
  severity: "warning",
@@ -18098,18 +18953,22 @@ function processDeclRules(ctx) {
18098
18953
  });
18099
18954
  break;
18100
18955
  }
18101
- if (childDecl) {
18102
- const overrideStyleKey = `${toStyleKey(otherLocal)}In${decl.localName}`;
18103
- ancestorSelectorParents.add(decl.styleKey);
18104
- if (processDeclarationsIntoBucket(rule, getOrCreateRelationOverrideBucket(overrideStyleKey, decl.styleKey, childDecl.styleKey, ancestorPseudo, relationOverrides, relationOverridePseudoBuckets), j, decl, resolveThemeValue, resolveThemeValueFromFn, { bailOnUnresolved: true }) === "bail") {
18105
- state.markBail();
18106
- warnings.push({
18107
- severity: "warning",
18108
- type: "Unsupported selector: unresolved interpolation in descendant component selector",
18109
- loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
18110
- });
18111
- break;
18112
- }
18956
+ const jsxLocalName = crossFileUsage?.bridgeComponentLocalName ?? otherLocal;
18957
+ const childStyleKey = childDecl ? childDecl.styleKey : toStyleKey(jsxLocalName);
18958
+ const overrideStyleKey = `${toStyleKey(jsxLocalName)}In${decl.localName}`;
18959
+ ancestorSelectorParents.add(decl.styleKey);
18960
+ const markerVarName = crossFileUsage ? `${decl.localName}Marker` : void 0;
18961
+ const overrideCountBefore = relationOverrides.length;
18962
+ const bucket = getOrCreateRelationOverrideBucket(overrideStyleKey, decl.styleKey, childStyleKey, ancestorPseudo, relationOverrides, relationOverridePseudoBuckets);
18963
+ tagCrossFileOverride(relationOverrides, overrideCountBefore, markerVarName, jsxLocalName);
18964
+ if (processDeclarationsIntoBucket(rule, bucket, j, decl, resolveThemeValue, resolveThemeValueFromFn, { bailOnUnresolved: true }) === "bail") {
18965
+ state.markBail();
18966
+ warnings.push({
18967
+ severity: "warning",
18968
+ type: crossFileUsage ? "Unsupported selector: unresolved interpolation in cross-file component selector" : "Unsupported selector: unresolved interpolation in descendant component selector",
18969
+ loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
18970
+ });
18971
+ break;
18113
18972
  }
18114
18973
  continue;
18115
18974
  }
@@ -18188,6 +19047,7 @@ function processDeclRules(ctx) {
18188
19047
  }
18189
19048
  const pseudos = parsedSelector.kind === "pseudo" ? parsedSelector.pseudos : null;
18190
19049
  const pseudoElement = parsedSelector.kind === "pseudoElement" ? parsedSelector.element : null;
19050
+ const pseudoElementsList = parsedSelector.kind === "pseudoElements" ? parsedSelector.elements : null;
18191
19051
  const attrSel = parsedSelector.kind === "attribute" ? {
18192
19052
  kind: parsedSelector.attr.type,
18193
19053
  suffix: parsedSelector.attr.suffix,
@@ -18276,16 +19136,7 @@ function processDeclRules(ctx) {
18276
19136
  return;
18277
19137
  }
18278
19138
  if (resolvedSelectorMedia) {
18279
- let entry = perPropComputedMedia.get(prop);
18280
- if (!entry) {
18281
- const existingVal = styleObj[prop];
18282
- entry = {
18283
- defaultValue: existingVal !== void 0 ? existingVal : cssHelperPropValues.has(prop) ? getComposedDefaultValue(prop) : null,
18284
- entries: []
18285
- };
18286
- perPropComputedMedia.set(prop, entry);
18287
- }
18288
- entry.entries.push({
19139
+ getOrCreateComputedMediaEntry(prop, ctx).entries.push({
18289
19140
  keyExpr: resolvedSelectorMedia.keyExpr,
18290
19141
  value
18291
19142
  });
@@ -18303,15 +19154,18 @@ function processDeclRules(ctx) {
18303
19154
  for (const ps of pseudos) existing[ps] = value;
18304
19155
  return;
18305
19156
  }
18306
- if (pseudoElement) {
18307
- nestedSelectors[pseudoElement] ??= {};
18308
- const pseudoSelector = nestedSelectors[pseudoElement];
18309
- if (pseudoSelector) {
18310
- pseudoSelector[prop] = value;
18311
- if (commentSource) addPropComments(pseudoSelector, prop, {
18312
- leading: commentSource.leading,
18313
- trailingLine: commentSource.trailingLine
18314
- });
19157
+ const pseudoElementsToApply = pseudoElement ? [pseudoElement] : pseudoElementsList;
19158
+ if (pseudoElementsToApply) {
19159
+ for (const pe of pseudoElementsToApply) {
19160
+ nestedSelectors[pe] ??= {};
19161
+ const pseudoSelector = nestedSelectors[pe];
19162
+ if (pseudoSelector) {
19163
+ pseudoSelector[prop] = value;
19164
+ if (commentSource) addPropComments(pseudoSelector, prop, {
19165
+ leading: commentSource.leading,
19166
+ trailingLine: commentSource.trailingLine
19167
+ });
19168
+ }
18315
19169
  }
18316
19170
  return;
18317
19171
  }
@@ -18326,7 +19180,7 @@ function processDeclRules(ctx) {
18326
19180
  rule,
18327
19181
  media,
18328
19182
  pseudos,
18329
- pseudoElement,
19183
+ pseudoElement: pseudoElement ?? (pseudoElementsList ? pseudoElementsList[0] ?? null : null),
18330
19184
  attrTarget,
18331
19185
  resolvedSelectorMedia,
18332
19186
  applyResolvedPropValue
@@ -18580,6 +19434,18 @@ function handlePseudoAlias(result, rule, ctx) {
18580
19434
  decl.needsWrapperComponent = true;
18581
19435
  }
18582
19436
  /**
19437
+ * If a new relation override was created (array grew), tag it with cross-file metadata.
19438
+ * Used for both forward and reverse cross-file selector patterns.
19439
+ */
19440
+ function tagCrossFileOverride(relationOverrides, countBefore, markerVarName, componentLocalName) {
19441
+ if (!markerVarName || relationOverrides.length <= countBefore) return;
19442
+ const created = relationOverrides.at(-1);
19443
+ if (!created) return;
19444
+ created.crossFile = true;
19445
+ created.markerVarName = markerVarName;
19446
+ created.crossFileComponentLocalName = componentLocalName;
19447
+ }
19448
+ /**
18583
19449
  * Recovers standalone conditional interpolations from inside a pseudo-alias block.
18584
19450
  *
18585
19451
  * When Stylis drops standalone placeholders at brace depth > 0, the pseudo-alias
@@ -18589,13 +19455,13 @@ function handlePseudoAlias(result, rule, ctx) {
18589
19455
  function recoverStandaloneInterpolationsInPseudoBlock(rule, decl) {
18590
19456
  const { rawCss, templateExpressions } = decl;
18591
19457
  if (!rawCss) return null;
18592
- const pseudoSlotMatch = rule.selector.match(/__SC_EXPR_(\d+)__/);
19458
+ const pseudoSlotMatch = rule.selector.match(PLACEHOLDER_RE);
18593
19459
  if (!pseudoSlotMatch) return null;
18594
19460
  const pseudoSlotId = pseudoSlotMatch[1];
18595
19461
  const blockRegex = new RegExp(`&&?:\\s*__SC_EXPR_${pseudoSlotId}__\\s*\\{([^}]*)\\}`);
18596
19462
  const blockMatch = rawCss.match(blockRegex);
18597
19463
  if (!blockMatch?.[1]) return null;
18598
- const standaloneSlotRegex = /__SC_EXPR_(\d+)__/g;
19464
+ const standaloneSlotRegex = new RegExp(PLACEHOLDER_RE.source, "g");
18599
19465
  const slots = [];
18600
19466
  let slotMatch;
18601
19467
  while ((slotMatch = standaloneSlotRegex.exec(blockMatch[1])) !== null) slots.push(Number(slotMatch[1]));
@@ -18651,6 +19517,113 @@ function extractCssTextFromNode(node) {
18651
19517
  if (n.type === "TemplateLiteral" && (!n.expressions || n.expressions.length === 0)) return (n.quasis ?? []).map((q) => q.value?.raw ?? "").join("");
18652
19518
  return null;
18653
19519
  }
19520
+ /** Reverse selector pattern for a single part: `__SC_EXPR_N__:pseudo &` */
19521
+ const REVERSE_SELECTOR_PART_RE = /^__SC_EXPR_\d+__:[a-z][a-z0-9()-]*\s+&$/;
19522
+ /**
19523
+ * Checks if a comma-separated selector has all parts matching the reverse
19524
+ * component selector pattern (`__SC_EXPR_N__:pseudo &`). Different slot IDs
19525
+ * are allowed since Stylis assigns each `${Component}` reference its own slot.
19526
+ *
19527
+ * Example: `__SC_EXPR_0__:focus-visible &, __SC_EXPR_1__:active &`
19528
+ */
19529
+ function isCommaGroupedReverseSelectorPattern(selector) {
19530
+ if (!selector.includes(",")) return false;
19531
+ const parts = selector.split(",").map((p) => p.trim());
19532
+ if (parts.length < 2) return false;
19533
+ for (const part of parts) if (!REVERSE_SELECTOR_PART_RE.test(part)) return false;
19534
+ return true;
19535
+ }
19536
+ /**
19537
+ * Extracts all pseudo-classes from a (possibly comma-separated) reverse
19538
+ * component selector. Each part is expected to match `__SC_EXPR_N__:pseudo &`.
19539
+ *
19540
+ * Returns e.g. [":focus-visible", ":active"] for
19541
+ * `__SC_EXPR_0__:focus-visible &, __SC_EXPR_0__:active &`.
19542
+ */
19543
+ function extractReverseSelectorPseudos(selector) {
19544
+ const parts = selector.split(",").map((p) => p.trim());
19545
+ const pseudos = [];
19546
+ for (const part of parts) {
19547
+ const match = part.match(/__SC_EXPR_\d+__(:[a-z-]+(?:\([^)]*\))?)/i);
19548
+ if (match?.[1]) pseudos.push(match[1]);
19549
+ }
19550
+ return pseudos;
19551
+ }
19552
+ /**
19553
+ * Returns the computed-media entry for `prop`, creating it on first access.
19554
+ * Centralises the get-or-create + default-value logic that both
19555
+ * resolvedSelectorMedia handling and sibling-selector handling need.
19556
+ */
19557
+ function getOrCreateComputedMediaEntry(prop, ctx) {
19558
+ const { perPropComputedMedia, styleObj, cssHelperPropValues, getComposedDefaultValue } = ctx;
19559
+ let entry = perPropComputedMedia.get(prop);
19560
+ if (!entry) {
19561
+ const existingVal = styleObj[prop];
19562
+ entry = {
19563
+ defaultValue: existingVal !== void 0 ? existingVal : cssHelperPropValues.has(prop) ? getComposedDefaultValue(prop) : null,
19564
+ entries: []
19565
+ };
19566
+ perPropComputedMedia.set(prop, entry);
19567
+ }
19568
+ return entry;
19569
+ }
19570
+ /**
19571
+ * Handles a self-referencing sibling selector (`& + &` or `& ~ &`) by processing
19572
+ * declarations and storing them as computed keys using `stylex.when.siblingBefore(':is(*)')`.
19573
+ *
19574
+ * **Semantic approximation:** CSS `& + &` targets only the *immediately adjacent*
19575
+ * sibling, while `stylex.when.siblingBefore()` generates a general sibling rule
19576
+ * (`~`) which matches *any* preceding sibling. This means the styles may apply
19577
+ * even when a non-matching element sits between two instances of the component.
19578
+ * In practice this rarely matters because `& + &` is almost always used with
19579
+ * consecutive homogeneous lists. For `& ~ &`, the mapping is semantically exact.
19580
+ *
19581
+ * Uses `defaultMarker()` (via `ancestorSelectorParents`) instead of per-component
19582
+ * `defineMarker()`, avoiding the `.stylex` file requirement. Because the marker
19583
+ * is file-global rather than component-scoped, styles from one component could
19584
+ * theoretically leak to another if both use sibling selectors and appear as
19585
+ * siblings in the same render tree.
19586
+ *
19587
+ * **`:is(*)` workaround:** The StyleX Babel plugin mandates a pseudo argument
19588
+ * starting with `:` for `siblingBefore()`. `:is(*)` is a universal match with
19589
+ * no effect on specificity or matching.
19590
+ * TODO: Remove the `:is(*)` workaround if the StyleX Babel plugin adds support
19591
+ * for no-arg `siblingBefore()` calls (currently crashes in `validatePseudoSelector`).
19592
+ *
19593
+ * Returns "break" on error to bail, otherwise the caller should `continue`.
19594
+ */
19595
+ function handleSiblingSelector(selector, rule, ctx) {
19596
+ const { state, decl } = ctx;
19597
+ const { j, warnings, resolveThemeValue, resolveThemeValueFromFn, ancestorSelectorParents } = state;
19598
+ if (/\+/.test(selector)) warnings.push({
19599
+ severity: "info",
19600
+ type: "Sibling selector broadened: & + & (adjacent) becomes general sibling (~) in StyleX — interleaved non-matching elements will no longer block the match",
19601
+ loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
19602
+ });
19603
+ ancestorSelectorParents.add(decl.styleKey);
19604
+ const bucket = {};
19605
+ if (processDeclarationsIntoBucket(rule, bucket, j, decl, resolveThemeValue, resolveThemeValueFromFn, { bailOnUnresolved: true }) === "bail") {
19606
+ state.markBail();
19607
+ warnings.push({
19608
+ severity: "warning",
19609
+ type: "Unsupported selector: unresolved interpolation in sibling selector",
19610
+ loc: computeSelectorWarningLoc(decl.loc, decl.rawCss, rule.selector)
19611
+ });
19612
+ return "break";
19613
+ }
19614
+ const makeSiblingKeyExpr = () => j.callExpression(j.memberExpression(j.memberExpression(j.identifier("stylex"), j.identifier("when")), j.identifier("siblingBefore")), [j.literal(":is(*)")]);
19615
+ const media = rule.atRuleStack.find((a) => a.startsWith("@media"));
19616
+ for (const [prop, value] of Object.entries(bucket)) {
19617
+ const siblingValue = media ? {
19618
+ default: null,
19619
+ [media]: value
19620
+ } : value;
19621
+ getOrCreateComputedMediaEntry(prop, ctx).entries.push({
19622
+ keyExpr: makeSiblingKeyExpr(),
19623
+ value: siblingValue
19624
+ });
19625
+ }
19626
+ }
18654
19627
 
18655
19628
  //#endregion
18656
19629
  //#region src/internal/lower-rules/variants.ts
@@ -18920,7 +19893,7 @@ function finalizeDeclProcessing(ctx) {
18920
19893
  if (!m || !m[1] || !m[2]) continue;
18921
19894
  const prop = m[1].trim();
18922
19895
  const value = m[2].trim();
18923
- if (/__SC_EXPR_\d+__/.test(value)) continue;
19896
+ if (PLACEHOLDER_RE.test(value)) continue;
18924
19897
  const outProp = cssPropertyToStylexProp(prop === "background" ? resolveBackgroundStylexProp(value) : prop);
18925
19898
  bucket[outProp] = cssValueToJs({
18926
19899
  kind: "static",
@@ -19344,15 +20317,21 @@ const finalizeRelationOverrides = (args) => {
19344
20317
  const { j, relationOverridePseudoBuckets, relationOverrides, resolvedStyleObjects, makeCssPropKey, childPseudoMarkers } = args;
19345
20318
  if (relationOverridePseudoBuckets.size === 0) return;
19346
20319
  const overrideToChildKeys = /* @__PURE__ */ new Map();
20320
+ const overrideToMarker = /* @__PURE__ */ new Map();
19347
20321
  for (const o of relationOverrides) {
19348
- const keys = [o.childStyleKey, ...o.childExtraStyleKeys ?? []];
19349
- overrideToChildKeys.set(o.overrideStyleKey, keys);
20322
+ overrideToChildKeys.set(o.overrideStyleKey, [o.childStyleKey, ...o.childExtraStyleKeys ?? []]);
20323
+ if (o.crossFile && o.markerVarName) overrideToMarker.set(o.overrideStyleKey, o.markerVarName);
19350
20324
  }
19351
- const makeAncestorKey = (pseudo) => j.callExpression(j.memberExpression(j.memberExpression(j.identifier("stylex"), j.identifier("when")), j.identifier("ancestor")), [j.literal(pseudo)]);
20325
+ const makeAncestorKey = (pseudo, markerVarName) => {
20326
+ const callArgs = [j.literal(pseudo)];
20327
+ if (markerVarName) callArgs.push(j.identifier(markerVarName));
20328
+ return j.callExpression(j.memberExpression(j.memberExpression(j.identifier("stylex"), j.identifier("when")), j.identifier("ancestor")), callArgs);
20329
+ };
19352
20330
  const isExpressionNode = (v) => isAstNode(v);
19353
20331
  for (const [overrideKey, pseudoBuckets] of relationOverridePseudoBuckets.entries()) {
19354
20332
  const baseBucket = pseudoBuckets.get(null) ?? {};
19355
20333
  const props = [];
20334
+ const markerVarName = overrideToMarker.get(overrideKey);
19356
20335
  const childKeys = overrideToChildKeys.get(overrideKey) ?? [];
19357
20336
  const childStyleObjects = [];
19358
20337
  for (const key of childKeys) {
@@ -19387,7 +20366,7 @@ const finalizeRelationOverrides = (args) => {
19387
20366
  const valExpr = isExpressionNode(value) ? value : literalToAst(j, value);
19388
20367
  if (childPseudos?.has(pseudo)) objProps.push(j.property("init", j.literal(pseudo), valExpr));
19389
20368
  else {
19390
- const ancestorKey = makeAncestorKey(pseudo);
20369
+ const ancestorKey = makeAncestorKey(pseudo, markerVarName);
19391
20370
  const propNode = Object.assign(j.property("init", ancestorKey, valExpr), { computed: true });
19392
20371
  objProps.push(propNode);
19393
20372
  }
@@ -19448,11 +20427,14 @@ function lowerRules(ctx) {
19448
20427
  makeCssPropKey,
19449
20428
  childPseudoMarkers: state.childPseudoMarkers
19450
20429
  });
20430
+ const crossFileMarkers = /* @__PURE__ */ new Map();
20431
+ for (const o of state.relationOverrides) if (o.crossFile && o.markerVarName) crossFileMarkers.set(o.parentStyleKey, o.markerVarName);
19451
20432
  return {
19452
20433
  resolvedStyleObjects: state.resolvedStyleObjects,
19453
20434
  relationOverrides: state.relationOverrides,
19454
20435
  ancestorSelectorParents: state.ancestorSelectorParents,
19455
20436
  usedCssHelperFunctions: state.usedCssHelperFunctions,
20437
+ crossFileMarkers,
19456
20438
  bail: state.bail
19457
20439
  };
19458
20440
  }
@@ -19472,6 +20454,7 @@ function lowerRulesStep(ctx) {
19472
20454
  ctx.resolvedStyleObjects = lowered.resolvedStyleObjects;
19473
20455
  ctx.relationOverrides = lowered.relationOverrides;
19474
20456
  ctx.ancestorSelectorParents = lowered.ancestorSelectorParents;
20457
+ ctx.crossFileMarkers = lowered.crossFileMarkers;
19475
20458
  if (lowered.bail || ctx.resolveValueBailRef.value) return returnResult({
19476
20459
  code: null,
19477
20460
  warnings: ctx.warnings
@@ -19488,7 +20471,7 @@ function lowerRulesStep(ctx) {
19488
20471
  //#endregion
19489
20472
  //#region src/internal/rewrite-jsx.ts
19490
20473
  function postProcessTransformedAst(args) {
19491
- const { root, j, relationOverrides, ancestorSelectorParents, componentNameToStyleKey, emptyStyleKeys, preserveReactImport, newImportLocalNames, newImportSourcesByLocal } = args;
20474
+ const { root, j, relationOverrides, ancestorSelectorParents, componentNameToStyleKey, emptyStyleKeys, preserveReactImport, newImportLocalNames, newImportSourcesByLocal, stylesIdentifier = "styles", crossFileMarkers } = args;
19492
20475
  let changed = false;
19493
20476
  root.find(j.VariableDeclaration).forEach((p) => {
19494
20477
  if (p.node.declarations.length === 0) {
@@ -19498,6 +20481,12 @@ function postProcessTransformedAst(args) {
19498
20481
  });
19499
20482
  if (relationOverrides.length > 0 || ancestorSelectorParents.size > 0) {
19500
20483
  const makeDefaultMarkerCall = () => j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("defaultMarker")), []);
20484
+ const crossFileChildOverrides = /* @__PURE__ */ new Map();
20485
+ const crossFileParentMarkers = /* @__PURE__ */ new Map();
20486
+ for (const o of relationOverrides) {
20487
+ if (!o.crossFile || !o.crossFileComponentLocalName) continue;
20488
+ appendToMapList(componentNameToStyleKey?.get(o.crossFileComponentLocalName) === o.parentStyleKey || o.parentStyleKey === toStyleKey(o.crossFileComponentLocalName) ? crossFileParentMarkers : crossFileChildOverrides, o.crossFileComponentLocalName, o);
20489
+ }
19501
20490
  const isStylexPropsCall = (n) => n?.type === "CallExpression" && n.callee?.type === "MemberExpression" && n.callee.object?.type === "Identifier" && n.callee.object.name === "stylex" && n.callee.property?.type === "Identifier" && n.callee.property.name === "props";
19502
20491
  const getStylexPropsCallFromAttrs = (attrs) => {
19503
20492
  for (const a of attrs ?? []) {
@@ -19506,13 +20495,19 @@ function postProcessTransformedAst(args) {
19506
20495
  }
19507
20496
  };
19508
20497
  const hasStyleKeyArg = (call, key) => {
19509
- return (call.arguments ?? []).some((a) => a?.type === "MemberExpression" && a.object?.type === "Identifier" && a.object.name === "styles" && a.property?.type === "Identifier" && a.property.name === key);
20498
+ return (call.arguments ?? []).some((a) => a?.type === "MemberExpression" && a.object?.type === "Identifier" && a.object.name === stylesIdentifier && a.property?.type === "Identifier" && a.property.name === key);
19510
20499
  };
20500
+ const hasIdentifierArg = (call, name) => (call.arguments ?? []).some((a) => a?.type === "Identifier" && a.name === name);
19511
20501
  const hasDefaultMarker = (call) => {
19512
20502
  return (call.arguments ?? []).some((a) => a?.type === "CallExpression" && a.callee?.type === "MemberExpression" && a.callee.object?.type === "Identifier" && a.callee.object.name === "stylex" && a.callee.property?.type === "Identifier" && a.callee.property.name === "defaultMarker");
19513
20503
  };
19514
20504
  const overridesByChild = /* @__PURE__ */ new Map();
19515
20505
  for (const o of relationOverrides) overridesByChild.set(o.childStyleKey, [...overridesByChild.get(o.childStyleKey) ?? [], o]);
20506
+ /** Check if any ancestor in the JSX tree contains the given parent style key. */
20507
+ const ancestorHasParentKey = (ancestors, parentStyleKey) => {
20508
+ const markerVarName = crossFileMarkers?.get(parentStyleKey);
20509
+ return ancestors.some((a) => a?.call && hasStyleKeyArg(a.call, parentStyleKey) || a?.elementStyleKey && a.elementStyleKey === parentStyleKey || markerVarName && a?.markerVarName === markerVarName);
20510
+ };
19516
20511
  const pendingEmptyKeyRemovals = [];
19517
20512
  const visit = (node, ancestors) => {
19518
20513
  if (!node || node.type !== "JSXElement") return;
@@ -19526,7 +20521,13 @@ function postProcessTransformedAst(args) {
19526
20521
  call,
19527
20522
  key: parentKey
19528
20523
  });
19529
- if (!hasDefaultMarker(call)) {
20524
+ const markerVarName = crossFileMarkers?.get(parentKey);
20525
+ if (markerVarName) {
20526
+ if (!hasIdentifierArg(call, markerVarName)) {
20527
+ call.arguments = [...call.arguments ?? [], j.identifier(markerVarName)];
20528
+ changed = true;
20529
+ }
20530
+ } else if (!hasDefaultMarker(call)) {
19530
20531
  call.arguments = [...call.arguments ?? [], makeDefaultMarkerCall()];
19531
20532
  changed = true;
19532
20533
  }
@@ -19535,26 +20536,61 @@ function postProcessTransformedAst(args) {
19535
20536
  if (call) for (const [childKey, list] of overridesByChild.entries()) {
19536
20537
  if (!hasStyleKeyArg(call, childKey)) continue;
19537
20538
  for (const o of list) {
19538
- if (!ancestors.some((a) => a?.call && hasStyleKeyArg(a.call, o.parentStyleKey) || a?.elementStyleKey && a.elementStyleKey === o.parentStyleKey)) continue;
20539
+ if (!ancestorHasParentKey(ancestors, o.parentStyleKey)) continue;
19539
20540
  if (hasStyleKeyArg(call, o.overrideStyleKey)) continue;
19540
- const overrideArg = j.memberExpression(j.identifier("styles"), j.identifier(o.overrideStyleKey));
20541
+ const overrideArg = j.memberExpression(j.identifier(stylesIdentifier), j.identifier(o.overrideStyleKey));
19541
20542
  call.arguments = [...call.arguments ?? [], overrideArg];
19542
20543
  changed = true;
19543
20544
  }
19544
20545
  }
20546
+ const childOverrides = elementName ? crossFileChildOverrides.get(elementName) : void 0;
20547
+ if (childOverrides) {
20548
+ const overrideArgs = [];
20549
+ for (const o of childOverrides) {
20550
+ if (!ancestorHasParentKey(ancestors, o.parentStyleKey)) continue;
20551
+ overrideArgs.push(j.memberExpression(j.identifier(stylesIdentifier), j.identifier(o.overrideStyleKey)));
20552
+ }
20553
+ if (overrideArgs.length > 0) {
20554
+ const existingCall = getStylexPropsCallFromAttrs(opening.attributes ?? []);
20555
+ if (existingCall) existingCall.arguments = [...existingCall.arguments ?? [], ...overrideArgs];
20556
+ else {
20557
+ const newCall = j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), overrideArgs);
20558
+ opening.attributes = [...opening.attributes ?? [], j.jsxSpreadAttribute(newCall)];
20559
+ }
20560
+ changed = true;
20561
+ }
20562
+ }
20563
+ let addedMarkerVarName;
20564
+ const parentMarkers = elementName ? crossFileParentMarkers.get(elementName) : void 0;
20565
+ if (parentMarkers) for (const o of parentMarkers) {
20566
+ if (!o.markerVarName) continue;
20567
+ addedMarkerVarName = o.markerVarName;
20568
+ const markerIdent = j.identifier(o.markerVarName);
20569
+ if (call) {
20570
+ if (!hasIdentifierArg(call, o.markerVarName)) {
20571
+ call.arguments = [...call.arguments ?? [], markerIdent];
20572
+ changed = true;
20573
+ }
20574
+ } else {
20575
+ const markerCall = j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), [markerIdent]);
20576
+ opening.attributes = [...opening.attributes ?? [], j.jsxSpreadAttribute(markerCall)];
20577
+ changed = true;
20578
+ }
20579
+ }
19545
20580
  const nextAncestors = [...ancestors, {
19546
20581
  call,
19547
- elementStyleKey
20582
+ elementStyleKey,
20583
+ markerVarName: addedMarkerVarName
19548
20584
  }];
19549
20585
  for (const c of node.children ?? []) if (c?.type === "JSXElement") visit(c, nextAncestors);
19550
20586
  };
19551
20587
  root.find(j.JSXElement).forEach((p) => {
19552
- if (j(p).closest(j.JSXElement).size() > 1) return;
20588
+ if (j(p).closest(j.JSXElement).size() > 0) return;
19553
20589
  visit(p.node, []);
19554
20590
  });
19555
20591
  for (const { call, key } of pendingEmptyKeyRemovals) {
19556
20592
  const originalLength = (call.arguments ?? []).length;
19557
- call.arguments = (call.arguments ?? []).filter((a) => !(a?.type === "MemberExpression" && a.object?.type === "Identifier" && a.object.name === "styles" && a.property?.type === "Identifier" && a.property.name === key));
20593
+ call.arguments = (call.arguments ?? []).filter((a) => !(a?.type === "MemberExpression" && a.object?.type === "Identifier" && a.object.name === stylesIdentifier && a.property?.type === "Identifier" && a.property.name === key));
19558
20594
  if ((call.arguments ?? []).length !== originalLength) changed = true;
19559
20595
  }
19560
20596
  }
@@ -19643,6 +20679,11 @@ function postProcessTransformedAst(args) {
19643
20679
  needsReactImport: usesReactIdent && !hasReactImport
19644
20680
  };
19645
20681
  }
20682
+ function appendToMapList(map, key, value) {
20683
+ const list = map.get(key);
20684
+ if (list) list.push(value);
20685
+ else map.set(key, [value]);
20686
+ }
19646
20687
 
19647
20688
  //#endregion
19648
20689
  //#region src/internal/transform-steps/post-process.ts
@@ -19692,7 +20733,9 @@ function postProcessStep(ctx) {
19692
20733
  emptyStyleKeys: ctx.emptyStyleKeys ?? /* @__PURE__ */ new Set(),
19693
20734
  preserveReactImport: ctx.preserveReactImport,
19694
20735
  newImportLocalNames,
19695
- newImportSourcesByLocal
20736
+ newImportSourcesByLocal,
20737
+ stylesIdentifier: ctx.stylesIdentifier,
20738
+ crossFileMarkers: ctx.crossFileMarkers
19696
20739
  });
19697
20740
  if (post.changed) ctx.markChanged();
19698
20741
  ctx.needsReactImport = post.needsReactImport;
@@ -20147,10 +21190,24 @@ function transform(file, api, options) {
20147
21190
  try {
20148
21191
  const result = transformWithWarnings(file, api, options);
20149
21192
  Logger.logWarnings(result.warnings, file.path);
21193
+ if (result.sidecarContent) {
21194
+ const sidecarFiles = options.sidecarFiles;
21195
+ if (sidecarFiles) {
21196
+ const dir = dirname(file.path);
21197
+ const fileBase = basename(file.path).replace(/\.\w+$/, "");
21198
+ sidecarFiles.set(join(dir, `${fileBase}.stylex.ts`), result.sidecarContent);
21199
+ }
21200
+ }
21201
+ if (result.bridgeResults && result.bridgeResults.length > 0) {
21202
+ const bridgeResultsMap = options.bridgeResults;
21203
+ if (bridgeResultsMap) bridgeResultsMap.set(toRealPath(file.path), result.bridgeResults);
21204
+ }
20150
21205
  return result.code;
20151
21206
  } catch (e) {
20152
- const msg = `Transform failed: ${e instanceof Error ? e.message : String(e)}`;
20153
- Logger.logError(msg, file.path);
21207
+ if (!Logger.isErrorLogged(e)) {
21208
+ const msg = `Transform failed: ${e instanceof Error ? e.message : String(e)}`;
21209
+ Logger.logError(msg, file.path);
21210
+ }
20154
21211
  throw e;
20155
21212
  }
20156
21213
  }
@@ -20158,7 +21215,7 @@ function transform(file, api, options) {
20158
21215
  * Transform with detailed warnings returned (for testing)
20159
21216
  */
20160
21217
  function transformWithWarnings(file, api, options) {
20161
- const ctx = new TransformContext(file, api, options);
21218
+ const ctx = new TransformContext(file, api, extractCrossFileInfoForFile(file.path, options));
20162
21219
  const pipeline = [
20163
21220
  preflight,
20164
21221
  applyPolicyGates,
@@ -20176,6 +21233,7 @@ function transformWithWarnings(file, api, options) {
20176
21233
  collectStaticPropsStep,
20177
21234
  rewriteJsxStep,
20178
21235
  emitWrappersStep,
21236
+ emitBridgeExportsStep,
20179
21237
  upgradePolymorphicAsPropTypesStep,
20180
21238
  ensureMergerImportStep,
20181
21239
  reinsertStaticPropsStep,
@@ -20189,6 +21247,52 @@ function transformWithWarnings(file, api, options) {
20189
21247
  }
20190
21248
  return finalize(ctx);
20191
21249
  }
21250
+ /**
21251
+ * Extract per-file cross-file info from the global prepass result stored in jscodeshift options.
21252
+ * The prepass result (if any) is passed via `options.crossFilePrepassResult` from runTransform.
21253
+ */
21254
+ function extractCrossFileInfoForFile(filePath, options) {
21255
+ const prepass = options.crossFilePrepassResult;
21256
+ if (!prepass) return options;
21257
+ const absPath = resolveToPrepassKey(filePath, prepass);
21258
+ const selectorUsages = prepass.selectorUsages.get(absPath);
21259
+ const bridgeComponentNames = prepass.componentsNeedingGlobalSelectorBridge?.get(absPath);
21260
+ if ((!selectorUsages || selectorUsages.length === 0) && !bridgeComponentNames) return options;
21261
+ const crossFileInfo = {
21262
+ selectorUsages: selectorUsages ?? [],
21263
+ bridgeComponentNames
21264
+ };
21265
+ return {
21266
+ ...options,
21267
+ crossFileInfo
21268
+ };
21269
+ }
21270
+ /**
21271
+ * Resolve a file path to the key form used by the prepass maps.
21272
+ * Tries pathResolve first; falls back to realpathSync if the key isn't found
21273
+ * (handles macOS /var → /private/var and similar symlink divergences).
21274
+ */
21275
+ function resolveToPrepassKey(filePath, prepass) {
21276
+ const resolved = resolve(filePath);
21277
+ if (prepass.selectorUsages.has(resolved) || prepass.componentsNeedingGlobalSelectorBridge?.has(resolved)) return resolved;
21278
+ try {
21279
+ const real = realpathSync(resolved);
21280
+ if (real !== resolved) return real;
21281
+ } catch {}
21282
+ return resolved;
21283
+ }
21284
+ /**
21285
+ * Resolve a file path to its real (symlink-resolved) absolute path.
21286
+ * Falls back to pathResolve if realpathSync fails.
21287
+ */
21288
+ function toRealPath(filePath) {
21289
+ const resolved = resolve(filePath);
21290
+ try {
21291
+ return realpathSync(resolved);
21292
+ } catch {
21293
+ return resolved;
21294
+ }
21295
+ }
20192
21296
 
20193
21297
  //#endregion
20194
21298
  export { transform as default, transformWithWarnings };