styled-components-to-stylex-codemod 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -119,6 +119,30 @@ const adapter = defineAdapter({
119
119
  };
120
120
  },
121
121
 
122
+ /**
123
+ * Optional: inline styled(ImportedComponent) into an intrinsic element.
124
+ * When the base component can be resolved statically, return the target
125
+ * element, consumed props, and base StyleX declarations. Return undefined
126
+ * to keep normal styled(Component) behavior.
127
+ */
128
+ resolveBaseComponent(ctx) {
129
+ if (ctx.importSource !== "@company/ui" || ctx.importedName !== "Flex") {
130
+ return undefined;
131
+ }
132
+
133
+ const sx: Record<string, string> = { display: "flex" };
134
+ const consumedProps = ["column", "gap", "align"];
135
+
136
+ if (ctx.staticProps.column === true) {
137
+ sx.flexDirection = "column";
138
+ }
139
+ if (typeof ctx.staticProps.gap === "number") {
140
+ sx.gap = `${ctx.staticProps.gap}px`;
141
+ }
142
+
143
+ return { tagName: "div", consumedProps, sx };
144
+ },
145
+
122
146
  /**
123
147
  * Control which exported components accept external className/style
124
148
  * and/or polymorphic `as` prop. Return `{ styles, as }` flags.
@@ -168,10 +192,11 @@ Adapters are the main extension point, see full example above. They let you cont
168
192
 
169
193
  - how theme paths, CSS variables, and imported values are turned into StyleX-compatible JS values (`resolveValue`)
170
194
  - what extra imports to inject into transformed files (returned from `resolveValue`)
171
- - how helper calls are resolved (via `resolveCall({ ... })` returning `{ expr, imports }`; `null`/`undefined` bails the file)
195
+ - how helper calls are resolved (via `resolveCall({ ... })` returning `{ expr, imports }`, or `{ preserveRuntimeCall: true }` to keep only the original helper runtime call; `null`/`undefined` bails the file)
172
196
  - which exported components should support external className/style extension and/or polymorphic `as` prop (`externalInterface`)
173
197
  - how className/style merging is handled for components accepting external styling (`styleMerger`)
174
198
  - which runtime theme hook import/call to use for emitted wrapper theme conditionals (`themeHook`)
199
+ - how `styled(ImportedComponent)` wrapping an external base component can be inlined into an intrinsic element with static StyleX styles (`resolveBaseComponent`)
175
200
 
176
201
  #### Cross-file selectors (`consumerPaths`)
177
202
 
@@ -224,6 +249,50 @@ Troubleshooting prepass failures with `"auto"`:
224
249
  - check resolver inputs (import paths, tsconfig path aliases, and related module resolution config)
225
250
  - if needed, switch to a manual `externalInterface(ctx)` function to continue migration while you fix prepass inputs
226
251
 
252
+ #### Base component resolution (`resolveBaseComponent`)
253
+
254
+ Use this when you want to **replace a base component entirely** by inlining its styles. If your codebase has a layout primitive like `<Flex>` whose behavior is purely CSS, the codemod can eliminate the runtime import and render a plain `<div>` instead.
255
+
256
+ The resolver receives `ctx.importSource`, `ctx.importedName`, and `ctx.staticProps` (from `.attrs()` and JSX call sites). Return `{ tagName, consumedProps, sx }` to inline, or `undefined` to skip.
257
+
258
+ ```tsx
259
+ // Input
260
+ const Container = styled(Flex).attrs({ column: true, gap: 16 })`
261
+ padding: 8px;
262
+ `;
263
+ ```
264
+
265
+ ```ts
266
+ // Adapter
267
+ resolveBaseComponent(ctx) {
268
+ if (ctx.importedName !== "Flex") return undefined;
269
+ const sx: Record<string, string> = { display: "flex" };
270
+ if (ctx.staticProps.column === true) sx.flexDirection = "column";
271
+ if (typeof ctx.staticProps.gap === "number") sx.gap = `${ctx.staticProps.gap}px`;
272
+ return { tagName: "div", consumedProps: ["column", "gap", "align"], sx };
273
+ },
274
+ ```
275
+
276
+ ```tsx
277
+ // Output — Flex is gone, its styles are merged into stylex.create()
278
+ const styles = stylex.create({
279
+ container: { display: "flex", flexDirection: "column", gap: "16px", padding: "8px" },
280
+ });
281
+ ```
282
+
283
+ If the base component's styles already exist as a `stylex.create()` object, return `mixins` instead of (or alongside) `sx`. The codemod imports the mixin and includes it in `stylex.props(...)`:
284
+
285
+ ```ts
286
+ resolveBaseComponent(ctx) {
287
+ return {
288
+ tagName: "div",
289
+ consumedProps: ["column", "gap"],
290
+ mixins: [{ importSource: "./lib/mixins.stylex", importName: "mixins", styleKey: "flex" }],
291
+ };
292
+ },
293
+ // Output: <div {...stylex.props(mixins.flex, styles.container)} />
294
+ ```
295
+
227
296
  #### Dynamic interpolations
228
297
 
229
298
  When the codemod encounters an interpolation inside a styled template literal, it runs an internal dynamic resolution pipeline which covers common cases like:
@@ -235,6 +304,8 @@ When the codemod encounters an interpolation inside a styled template literal, i
235
304
  - With `ctx.cssProperty` (e.g., `color: ${helper()}`) → result used as CSS value in `stylex.create()`
236
305
  - Without `ctx.cssProperty` (e.g., `${helper()}`) → result used as StyleX styles in `stylex.props()`
237
306
  - Use the optional `usage: "create" | "props"` field to override the default inference
307
+ - Use `preserveRuntimeCall: true` to keep the original helper call as a runtime style-function
308
+ override (with or without a static fallback from `expr`)
238
309
  - if `resolveCall` returns `null` or `undefined`, the transform **bails the file** and logs a warning
239
310
  - helper calls applied to prop values (e.g. `shadow(props.shadow)`) by emitting a StyleX style function that calls the helper at runtime
240
311
  - conditional CSS blocks via ternary (e.g. `props.$dim ? "opacity: 0.5;" : ""`)
@@ -1,4 +1,4 @@
1
- import { t as isSelectorContext } from "./selector-context-heuristic-CGwiJ3HL.mjs";
1
+ import { t as isSelectorContext } from "./selector-context-heuristic-Cki9_tTH.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { readFileSync, realpathSync } from "node:fs";
4
4
 
@@ -0,0 +1,91 @@
1
+ import { n as escapeRegex } from "./string-utils-Dmym5IkG.mjs";
2
+ import { resolve } from "node:path";
3
+ import { readFileSync, realpathSync } from "node:fs";
4
+
5
+ //#region src/internal/forwarded-as-consumer-patcher.ts
6
+ /**
7
+ * Post-transform consumer patching for `as` → `forwardedAs`.
8
+ *
9
+ * When a component (e.g., `Flex`) is converted from styled-components to StyleX,
10
+ * unconverted consumer files that wrap it with `styled(Flex)` break when using
11
+ * the `as` prop — styled-components intercepts `as` and replaces `Flex` entirely,
12
+ * losing all StyleX styles.
13
+ *
14
+ * `forwardedAs` tells styled-components to pass the prop through to the wrapped
15
+ * component's own `as` prop, preserving StyleX styles.
16
+ */
17
+ /**
18
+ * Filter prepass consumers to exclude:
19
+ * - consumers that were actually transformed (no longer use styled-components)
20
+ * - entries whose wrapped target bailed and didn't actually transform
21
+ *
22
+ * Returns a map of consumer paths → entries to patch.
23
+ */
24
+ function buildForwardedAsReplacements(prepassConsumers, transformedFiles) {
25
+ const result = /* @__PURE__ */ new Map();
26
+ for (const [consumerPath, entries] of prepassConsumers) {
27
+ if (transformedFiles.has(toRealPath(consumerPath))) continue;
28
+ const surviving = entries.filter((e) => transformedFiles.has(e.targetPath));
29
+ if (surviving.length > 0) result.set(consumerPath, surviving);
30
+ }
31
+ return result;
32
+ }
33
+ /**
34
+ * Patch a single consumer file: replace `as` with `forwardedAs` in JSX props
35
+ * and `.attrs()` calls for the given styled wrapper components.
36
+ *
37
+ * Returns the patched source or `null` if no changes were made.
38
+ */
39
+ function patchConsumerForwardedAs(filePath, entries) {
40
+ let source;
41
+ try {
42
+ source = readFileSync(filePath, "utf-8");
43
+ } catch {
44
+ return null;
45
+ }
46
+ if (entries.length === 0) return null;
47
+ let modified = source;
48
+ for (const { localStyledName } of entries) {
49
+ modified = patchJsxAsProp(modified, localStyledName);
50
+ modified = patchAttrsAsProp(modified, localStyledName);
51
+ }
52
+ return modified !== source ? modified : null;
53
+ }
54
+ /**
55
+ * Replace `as=` with `forwardedAs=` in JSX tags for the given component name.
56
+ * Handles both `as="span"` and `as={expr}` forms.
57
+ * Skips tags that already contain `forwardedAs`.
58
+ */
59
+ function patchJsxAsProp(source, componentName) {
60
+ const tagRegex = new RegExp(`(<${escapeRegex(componentName)}\\b[^<>]*)\\bas(\\s*[={])`, "g");
61
+ return source.replace(tagRegex, (match, before, after) => {
62
+ if (before.includes("forwardedAs") || match.includes("forwardedAs")) return match;
63
+ return `${before}forwardedAs${after}`;
64
+ });
65
+ }
66
+ /**
67
+ * Replace `as:` with `forwardedAs:` in object-form `.attrs({...})` calls on
68
+ * the styled declaration for the given component name.
69
+ * Only matches object-form `.attrs({ as: ... })`, NOT function-form
70
+ * `.attrs(({ as }) => ...)` where the destructuring param would be incorrectly patched.
71
+ * Skips attrs blocks that already contain `forwardedAs`.
72
+ */
73
+ function patchAttrsAsProp(source, componentName) {
74
+ const attrsRegex = new RegExp(`(const\\s+${escapeRegex(componentName)}\\b[^;]*\\.attrs\\s*\\(\\s*\\{[^)]*?)\\bas(\\s*:)`, "g");
75
+ return source.replace(attrsRegex, (match, before, after) => {
76
+ if (before.includes("forwardedAs")) return match;
77
+ return `${before}forwardedAs${after}`;
78
+ });
79
+ }
80
+ /** Resolve symlinks so paths match the keys in transformedFiles. */
81
+ function toRealPath(filePath) {
82
+ const resolved = resolve(filePath);
83
+ try {
84
+ return realpathSync(resolved);
85
+ } catch {
86
+ return resolved;
87
+ }
88
+ }
89
+
90
+ //#endregion
91
+ export { buildForwardedAsReplacements, patchConsumerForwardedAs };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineAdapter, i as AdapterInput, t as CollectedWarning } from "./logger-vL9nn4Bu.mjs";
1
+ import { a as defineAdapter, i as AdapterInput, t as CollectedWarning } from "./logger-DSmZSMxN.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as assertValidAdapterInput, o as describeValue, r as defineAdapter, t as Logger } from "./logger-Cn8OiPdU.mjs";
1
+ import { a as assertValidAdapterInput, o as describeValue, r as defineAdapter, t as Logger } from "./logger-Bi5-MGBs.mjs";
2
2
  import { run } from "jscodeshift/src/Runner.js";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -106,6 +106,17 @@ async function runTransform(options) {
106
106
  throw e;
107
107
  }
108
108
  };
109
+ const resolveBaseComponentWithLogging = (ctx) => {
110
+ if (!adapterInput.resolveBaseComponent) return;
111
+ try {
112
+ return adapterInput.resolveBaseComponent(ctx);
113
+ } catch (e) {
114
+ const msg = `adapter.resolveBaseComponent threw an error: ${e instanceof Error ? e.message : String(e)}`;
115
+ Logger.logError(msg, ctx.filePath, void 0, ctx);
116
+ Logger.markErrorAsLogged(e);
117
+ throw e;
118
+ }
119
+ };
109
120
  const patterns = Array.isArray(files) ? files : [files];
110
121
  const filePaths = [];
111
122
  const cwd = process.cwd();
@@ -130,9 +141,9 @@ async function runTransform(options) {
130
141
  `Pattern(s): ${consumerPatterns.join(", ")}`,
131
142
  "Check that the glob pattern is correct and files exist."
132
143
  ].join("\n"));
133
- const { createModuleResolver } = await import("./resolve-imports-BDk6Ms09.mjs");
144
+ const { createModuleResolver } = await import("./resolve-imports-4bFqrkrQ.mjs");
134
145
  const sharedResolver = createModuleResolver();
135
- const { runPrepass } = await import("./run-prepass-ByjC18e6.mjs");
146
+ const { runPrepass } = await import("./run-prepass-BuYfEK4M.mjs");
136
147
  const absoluteFiles = filePaths.map((f) => resolve(f));
137
148
  const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
138
149
  let prepassResult;
@@ -154,7 +165,8 @@ async function runTransform(options) {
154
165
  componentsNeedingMarkerSidecar: /* @__PURE__ */ new Map(),
155
166
  componentsNeedingGlobalSelectorBridge: /* @__PURE__ */ new Map()
156
167
  },
157
- consumerAnalysis: void 0
168
+ consumerAnalysis: void 0,
169
+ forwardedAsConsumers: /* @__PURE__ */ new Map()
158
170
  };
159
171
  }
160
172
  const crossFilePrepassResult = prepassResult.crossFileInfo;
@@ -172,7 +184,8 @@ async function runTransform(options) {
172
184
  }
173
185
  return analysisMap.get(`${realPath}:${ctx.componentName}`) ?? {
174
186
  styles: false,
175
- as: false
187
+ as: false,
188
+ ref: false
176
189
  };
177
190
  }
178
191
  };
@@ -187,7 +200,8 @@ async function runTransform(options) {
187
200
  },
188
201
  resolveValue: resolveValueWithLogging,
189
202
  resolveCall: resolveCallWithLogging,
190
- resolveSelector: resolveSelectorWithLogging
203
+ resolveSelector: resolveSelectorWithLogging,
204
+ resolveBaseComponent: adapterInput.resolveBaseComponent ? resolveBaseComponentWithLogging : void 0
191
205
  };
192
206
  const transformPath = (() => {
193
207
  const adjacent = join(__dirname, "transform.mjs");
@@ -217,7 +231,7 @@ async function runTransform(options) {
217
231
  });
218
232
  if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
219
233
  if (bridgeResults.size > 0 && !dryRun) {
220
- const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-DhMoL4Od.mjs");
234
+ const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-C2mFaVmd.mjs");
221
235
  const consumerReplacements = buildConsumerReplacements(crossFilePrepassResult.selectorUsages, bridgeResults, transformedFiles);
222
236
  const patchedFiles = [];
223
237
  for (const [consumerPath, replacements] of consumerReplacements) {
@@ -229,6 +243,19 @@ async function runTransform(options) {
229
243
  }
230
244
  if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
231
245
  }
246
+ if (prepassResult.forwardedAsConsumers.size > 0 && !dryRun) {
247
+ const { buildForwardedAsReplacements, patchConsumerForwardedAs } = await import("./forwarded-as-consumer-patcher-GPnjkzc_.mjs");
248
+ const forwardedAsReplacements = buildForwardedAsReplacements(prepassResult.forwardedAsConsumers, transformedFiles);
249
+ const patchedFiles = [];
250
+ for (const [consumerPath, entries] of forwardedAsReplacements) {
251
+ const patched = patchConsumerForwardedAs(consumerPath, entries);
252
+ if (patched !== null) {
253
+ await writeFile(consumerPath, patched, "utf-8");
254
+ patchedFiles.push(consumerPath);
255
+ }
256
+ }
257
+ if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
258
+ }
232
259
  if (formatterCommands && formatterCommands.length > 0 && result.ok > 0 && !dryRun) await runFormatters(formatterCommands, filePaths);
233
260
  const report = Logger.createReport();
234
261
  report.print();
@@ -35,6 +35,7 @@ function assertAdapterShape(candidate, where, allowAutoExtIf) {
35
35
  const resolveValue = obj?.resolveValue;
36
36
  const resolveCall = obj?.resolveCall;
37
37
  const resolveSelector = obj?.resolveSelector;
38
+ const resolveBaseComponent = obj?.resolveBaseComponent;
38
39
  const externalInterface = obj?.externalInterface;
39
40
  if (!candidate || typeof candidate !== "object") throw new Error([
40
41
  `${where}: expected an adapter object.`,
@@ -95,6 +96,19 @@ function assertAdapterShape(candidate, where, allowAutoExtIf) {
95
96
  "",
96
97
  `Docs/examples: ${ADAPTER_DOCS_URL}`
97
98
  ].join("\n"));
99
+ if (resolveBaseComponent !== void 0 && typeof resolveBaseComponent !== "function") throw new Error([
100
+ `${where}: adapter.resolveBaseComponent must be a function when provided.`,
101
+ `Received: resolveBaseComponent=${describeValue(resolveBaseComponent)}`,
102
+ "",
103
+ "Adapter shape:",
104
+ " {",
105
+ " resolveBaseComponent(context) {",
106
+ " return { tagName, consumedProps, sx?, mixins? } | undefined",
107
+ " }",
108
+ " }",
109
+ "",
110
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
111
+ ].join("\n"));
98
112
  if (!(typeof externalInterface === "function" || allowAutoExtIf && externalInterface === "auto")) {
99
113
  const expected = allowAutoExtIf ? "adapter.externalInterface must be a function or \"auto\"." : "adapter.externalInterface must be a function.";
100
114
  throw new Error([`${where}: ${expected}`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
@@ -215,11 +229,11 @@ const DEFAULT_THEME_HOOK = {
215
229
  *
216
230
  * // Configure external interface for exported components
217
231
  * externalInterface(ctx) {
218
- * // Example: Enable styles and `as` for shared components folder
232
+ * // Example: Enable styles, `as`, and `ref` for shared components folder
219
233
  * if (ctx.filePath.includes("/shared/components/")) {
220
- * return { styles: true, as: true };
234
+ * return { styles: true, as: true, ref: true };
221
235
  * }
222
- * return { styles: false, as: false };
236
+ * return { styles: false, as: false, ref: false };
223
237
  * },
224
238
  *
225
239
  * // Optional: provide a custom merger, or use `null` for the default verbose merge output
@@ -164,7 +164,7 @@ type ResolveValueResult = {
164
164
  */
165
165
  usage?: "props";
166
166
  };
167
- type CallResolveResult = {
167
+ type CallResolveResultWithExpr = {
168
168
  /**
169
169
  * JS expression string to inline into generated output.
170
170
  *
@@ -217,7 +217,38 @@ type CallResolveResult = {
217
217
  * Example: `"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"`
218
218
  */
219
219
  cssText?: string;
220
+ /**
221
+ * When true, keeps the original helper call as a runtime style-function override
222
+ * in addition to the resolved static value.
223
+ *
224
+ * This is useful for incremental migrations where you still want to run an
225
+ * existing runtime helper (for example `ColorConverter.cssWithAlpha(...)`) while
226
+ * also emitting a static StyleX fallback.
227
+ *
228
+ * Behavior notes:
229
+ * - In `CallResolveResultWithExpr`, `expr`/`imports` are used as a static fallback in
230
+ * `stylex.create(...)`.
231
+ * - In `CallResolveRuntimeOnlyResult`, no static fallback is emitted.
232
+ * - The runtime override is only emitted for arrow-function helper call interpolations.
233
+ * - Theme access in the original call is rewritten to use the wrapper `useTheme()` value.
234
+ */
235
+ preserveRuntimeCall?: boolean;
236
+ };
237
+ type CallResolveRuntimeOnlyResult = {
238
+ /**
239
+ * Keep the original helper call as a runtime style-function override, without
240
+ * requiring a static fallback expression.
241
+ *
242
+ * This mode is only supported for helper calls used as CSS values (not StyleX
243
+ * style-object references).
244
+ */
245
+ preserveRuntimeCall: true;
246
+ /**
247
+ * Optional usage hint. Runtime-only results are treated as CSS-value usage.
248
+ */
249
+ usage?: "create";
220
250
  };
251
+ type CallResolveResult = CallResolveResultWithExpr | CallResolveRuntimeOnlyResult;
221
252
  type ImportSource = {
222
253
  kind: "absolutePath";
223
254
  value: string;
@@ -232,6 +263,48 @@ type ImportSpec = {
232
263
  local?: string;
233
264
  }>;
234
265
  };
266
+ type ResolveBaseComponentStaticValue = string | number | boolean;
267
+ interface ResolveBaseComponentContext {
268
+ /**
269
+ * Import source for the wrapped base component.
270
+ * - package import: "@linear/orbiter/components/Flex"
271
+ * - relative import: resolved absolute path
272
+ */
273
+ importSource: string;
274
+ /**
275
+ * Imported binding name for the wrapped base component.
276
+ * Example: `import { Flex as OrbiterFlex } ...` -> importedName: "Flex"
277
+ */
278
+ importedName: string;
279
+ /**
280
+ * Static props from `.attrs({...})` and/or JSX call sites.
281
+ * Includes only literal values that can be resolved at codemod time.
282
+ */
283
+ staticProps: Record<string, ResolveBaseComponentStaticValue>;
284
+ /**
285
+ * Absolute path of the file currently being transformed.
286
+ * Useful for resolver logic that branches by caller file.
287
+ */
288
+ filePath: string;
289
+ }
290
+ interface ResolveBaseComponentMixinRef {
291
+ /** Import source for the mixin namespace/object (module specifier or absolute path) */
292
+ importSource: string;
293
+ /** Imported binding name for the mixin namespace/object (e.g., "mixins") */
294
+ importName: string;
295
+ /** Property key on the imported namespace/object (e.g., "flex") */
296
+ styleKey: string;
297
+ }
298
+ interface ResolveBaseComponentResult {
299
+ /** Intrinsic element to render after inlining (e.g., "div", "section") */
300
+ tagName: string;
301
+ /** Props consumed by the resolver and stripped from DOM forwarding */
302
+ consumedProps: string[];
303
+ /** Base StyleX declarations merged into stylex.create() (camelCase, no shorthands) */
304
+ sx?: Record<string, string>;
305
+ /** External StyleX mixin references included in stylex.props(...) */
306
+ mixins?: ResolveBaseComponentMixinRef[];
307
+ }
235
308
  /**
236
309
  * Context for `adapter.resolveSelector(...)`.
237
310
  *
@@ -319,14 +392,19 @@ interface ExternalInterfaceContext {
319
392
  /**
320
393
  * Result type for `adapter.externalInterface(...)`.
321
394
  *
322
- * - `{ styles: true, as: false }` → enable className/style support only
323
- * - `{ styles: true, as: true }` enable className/style support AND polymorphic `as` prop
324
- * - `{ styles: false, as: true }` enable only polymorphic `as` prop (no style merging)
325
- * - `{ styles: false, as: false }` → no external interface support
395
+ * - `styles` accept external className/style props
396
+ * - `as` accept polymorphic `as` prop
397
+ * - `ref` include `ref` in the component's public type
398
+ *
399
+ * Examples:
400
+ * - `{ styles: true, as: false, ref: false }` → className/style support only
401
+ * - `{ styles: true, as: true, ref: true }` → full external interface
402
+ * - `{ styles: false, as: false, ref: false }` → no external interface support
326
403
  */
327
404
  type ExternalInterfaceResult = {
328
405
  styles: boolean;
329
406
  as: boolean;
407
+ ref: boolean;
330
408
  };
331
409
  /**
332
410
  * Configuration for a custom style merger function that combines stylex.props()
@@ -388,6 +466,10 @@ interface Adapter {
388
466
  *
389
467
  * Return:
390
468
  * - `{ expr, imports }` with the resolved expression
469
+ * - `{ preserveRuntimeCall: true }` to keep only the original runtime helper call
470
+ * (no static fallback)
471
+ * - Optional: add `preserveRuntimeCall: true` to also keep the original helper
472
+ * call at runtime as a wrapper style-function override
391
473
  * - `undefined` to bail/skip the file
392
474
  */
393
475
  resolveCall: (context: CallResolveContext) => CallResolveResult | undefined;
@@ -404,14 +486,22 @@ interface Adapter {
404
486
  * - `undefined` to bail/skip the file
405
487
  */
406
488
  resolveSelector: (context: SelectorResolveContext) => SelectorResolveResult | undefined;
489
+ /**
490
+ * Optional resolver for inlining `styled(ImportedBase)` components.
491
+ *
492
+ * Return:
493
+ * - `{ tagName, consumedProps, sx?, mixins? }` to inline the base component
494
+ * - `undefined` to keep normal `styled(Component)` behavior
495
+ */
496
+ resolveBaseComponent?: (context: ResolveBaseComponentContext) => ResolveBaseComponentResult | undefined;
407
497
  /**
408
498
  * Called for exported styled components to determine their external interface.
409
499
  *
410
500
  * Return:
411
- * - `{ styles: false, as: false }` → no external interface
412
- * - `{ styles: true, as: false }` → accept className/style props only
413
- * - `{ styles: true, as: true }` → accept className/style props AND polymorphic `as` prop
414
- * - `{ styles: false, as: true }` → accept only polymorphic `as` prop
501
+ * - `{ styles: false, as: false, ref: false }` → no external interface
502
+ * - `{ styles: true, as: false, ref: false }` → accept className/style props only
503
+ * - `{ styles: true, as: true, ref: true }` → full external interface
504
+ * - `{ styles: false, as: true, ref: false }` → accept only polymorphic `as` prop
415
505
  */
416
506
  externalInterface: (context: ExternalInterfaceContext) => ExternalInterfaceResult;
417
507
  /**
@@ -424,7 +514,7 @@ interface Adapter {
424
514
  * ```typescript
425
515
  * function merger(
426
516
  * styles: StyleXStyles | StyleXStyles[],
427
- * className?: string,
517
+ * className?: string | (string | undefined | false | null)[],
428
518
  * style?: React.CSSProperties
429
519
  * ): { className?: string; style?: React.CSSProperties }
430
520
  * ```
@@ -450,6 +540,7 @@ interface AdapterInput {
450
540
  resolveValue: Adapter["resolveValue"];
451
541
  resolveCall: Adapter["resolveCall"];
452
542
  resolveSelector: Adapter["resolveSelector"];
543
+ resolveBaseComponent?: Adapter["resolveBaseComponent"];
453
544
  /**
454
545
  * Called for exported styled components to determine their external interface.
455
546
  *
@@ -501,11 +592,11 @@ interface AdapterInput {
501
592
  *
502
593
  * // Configure external interface for exported components
503
594
  * externalInterface(ctx) {
504
- * // Example: Enable styles and `as` for shared components folder
595
+ * // Example: Enable styles, `as`, and `ref` for shared components folder
505
596
  * if (ctx.filePath.includes("/shared/components/")) {
506
- * return { styles: true, as: true };
597
+ * return { styles: true, as: true, ref: true };
507
598
  * }
508
- * return { styles: false, as: false };
599
+ * return { styles: false, as: false, ref: false };
509
600
  * },
510
601
  *
511
602
  * // Optional: provide a custom merger, or use `null` for the default verbose merge output
@@ -522,7 +613,7 @@ declare function defineAdapter<T extends AdapterInput>(adapter: T): T;
522
613
  //#endregion
523
614
  //#region src/internal/logger.d.ts
524
615
  type Severity = "info" | "warning" | "error";
525
- type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "CSS block contains unsupported at-rule (only @media is supported; @supports, @container, etc. require manual handling)" | "Conditional `css` block: dynamic interpolation could not be resolved to a single component prop" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "Multi-slot border interpolation could not be resolved" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos or pseudo-elements" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: unresolved interpolation in sibling selector" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in cross-file component selector" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: grouped reverse selector references different components" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Sibling selector broadened: & + & (adjacent) becomes general sibling (~) in StyleX — interleaved non-matching elements will no longer block the match" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead" | "styled(ImportedComponent) wraps a component whose file contains internal styled-components — convert the base component's file first to avoid CSS cascade conflicts";
616
+ type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolveBaseComponent threw an error" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "CSS block contains unsupported at-rule (only @media and @container are supported; @supports, etc. require manual handling)" | "Conditional `css` block: dynamic interpolation could not be resolved to a single component prop" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "Multi-slot border interpolation could not be resolved" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Local helper function returns CSS that cannot be decomposed into individual properties" | "Local helper function computes CSS values that cannot be statically traced to the component prop" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos or pseudo-elements" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: unresolved interpolation in sibling selector" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in cross-file component selector" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: grouped reverse selector references different components" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Sibling selector broadened: & + & (adjacent) becomes general sibling (~) in StyleX — interleaved non-matching elements will no longer block the match" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead" | "styled(ImportedComponent) wraps a component whose file contains internal styled-components — convert the base component's file first to avoid CSS cascade conflicts";
526
617
  interface WarningLog {
527
618
  severity: Severity;
528
619
  type: WarningType;