@bamboocss/vite 1.24.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +129 -11
  2. package/dist/index.mjs +129 -11
  3. package/package.json +9 -9
package/dist/index.cjs CHANGED
@@ -795,6 +795,8 @@ const collectRecipeConfigs = (parserResult) => {
795
795
  };
796
796
  /** The generated binding a lowered dynamic axis calls. Lives in `cx`, which pulls no engine. */
797
797
  const RECIPE_PICK_HELPER = "cvaPick";
798
+ /** What `recipe.splitVariantProps` calls, reached directly once the call is lowered. */
799
+ const SPLIT_PROPS_HELPER = "splitProps";
798
800
  const HELPER = RECIPE_PICK_HELPER;
799
801
  /** Marker for a binding the fold must never resolve — declared twice, or unresolvable. */
800
802
  const AMBIGUOUS = Object.freeze({
@@ -802,6 +804,9 @@ const AMBIGUOUS = Object.freeze({
802
804
  name: "",
803
805
  box: void 0
804
806
  });
807
+ /** `.size`, or `["x-large"]` when the variant is not a valid identifier. */
808
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
809
+ const propertyAccess = (key) => IDENTIFIER.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
805
810
  const LITERAL_KINDS = new Set([
806
811
  ts_morph.SyntaxKind.StringLiteral,
807
812
  ts_morph.SyntaxKind.NoSubstitutionTemplateLiteral,
@@ -845,7 +850,7 @@ const propertyKey = (nameNode) => {
845
850
  * there is nothing to match — the host here is any import of the generated css module, which
846
851
  * a file defining a recipe necessarily has, since `cva` came from it.
847
852
  */
848
- const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
853
+ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
849
854
  const sourceFile = call.getSourceFile();
850
855
  let host;
851
856
  for (const declaration of sourceFile.getImportDeclarations()) {
@@ -853,7 +858,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
853
858
  if (declaration.isTypeOnly()) continue;
854
859
  for (const named of declaration.getNamedImports()) {
855
860
  if (named.isTypeOnly()) continue;
856
- if (named.getNameNode().getText() === "cvaPick") {
861
+ if (named.getNameNode().getText() === imported) {
857
862
  if (!isBambooCssModule(mod)) return void 0;
858
863
  const local = (named.getAliasNode() ?? named.getNameNode()).getText();
859
864
  return isShadowed(call, local) ? void 0 : { name: local };
@@ -862,15 +867,15 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
862
867
  if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
863
868
  }
864
869
  if (!host) return void 0;
865
- if (declaredAtModuleScope(sourceFile).has("cvaPick")) return void 0;
866
- if (isShadowed(call, "cvaPick")) return void 0;
870
+ if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
871
+ if (isShadowed(call, imported)) return void 0;
867
872
  const last = host.getNamedImports().at(-1);
868
873
  if (!last) return void 0;
869
874
  return {
870
- name: RECIPE_PICK_HELPER,
875
+ name: imported,
871
876
  insert: {
872
877
  pos: last.getEnd(),
873
- names: [RECIPE_PICK_HELPER]
878
+ names: [imported]
874
879
  }
875
880
  };
876
881
  };
@@ -916,11 +921,28 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
916
921
  const effectful = [];
917
922
  if (args.length === 1) {
918
923
  const arg = args[0];
919
- if (!arg || !ts_morph.Node.isObjectLiteralExpression(arg)) return {
924
+ if (!arg) return {
920
925
  kind: "decline",
921
926
  reason: "dynamic"
922
927
  };
923
- for (const property of arg.getProperties()) {
928
+ /**
929
+ * `input(variantProps)` — a selection the build cannot see inside.
930
+ *
931
+ * The classes are still knowable: a recipe emits one per declared variant, so the call is
932
+ * one term per variant reading that binding. This is the shape a wrapper component takes,
933
+ * where the variants are the component's public API and cannot be literals by definition.
934
+ *
935
+ * An identifier only. Each variant reads the binding again, and re-reading anything else —
936
+ * a call, a property access — would evaluate it once per axis instead of once.
937
+ */
938
+ if (ts_morph.Node.isIdentifier(arg)) {
939
+ const binding = arg.getText();
940
+ for (const key of Object.keys(config.variants ?? {})) dynamicAxes.set(key, `${binding}${propertyAccess(key)}`);
941
+ } else if (!ts_morph.Node.isObjectLiteralExpression(arg)) return {
942
+ kind: "decline",
943
+ reason: "dynamic"
944
+ };
945
+ else for (const property of arg.getProperties()) {
924
946
  if (ts_morph.Node.isSpreadAssignment(property)) return {
925
947
  kind: "decline",
926
948
  reason: "dynamic"
@@ -1415,7 +1437,7 @@ const hasStyles = (data) => data.length > 0 && data.every((entry) => entry != nu
1415
1437
  const argumentsAccountedFor = (call, boxNode) => {
1416
1438
  if (!ts_morph.Node.isCallExpression(call)) return false;
1417
1439
  const args = call.getArguments();
1418
- if (args.length === 0) return false;
1440
+ if (args.length === 0) return true;
1419
1441
  if (_bamboocss_extractor.box.isArray(boxNode) && boxNode.getNode() === call) {
1420
1442
  if (boxNode.value.length !== args.length) return false;
1421
1443
  return args.every((arg, index) => accountsForSource(arg, boxNode.value[index]));
@@ -1540,6 +1562,8 @@ const foldSource = (options) => {
1540
1562
  * saving real, and it is only correct to claim it once nothing reads the binding.
1541
1563
  */
1542
1564
  const recipeCalls = /* @__PURE__ */ new Map();
1565
+ /** Bindings whose `splitVariantProps` was rewritten, so that access no longer reads them. */
1566
+ const loweredSplitProps = /* @__PURE__ */ new Set();
1543
1567
  /** Ranges already reported as declined, so one call is never counted twice. */
1544
1568
  const reportedRanges = /* @__PURE__ */ new Set();
1545
1569
  const importCache = /* @__PURE__ */ new Map();
@@ -1551,6 +1575,58 @@ const foldSource = (options) => {
1551
1575
  }
1552
1576
  return names;
1553
1577
  };
1578
+ /**
1579
+ * Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
1580
+ *
1581
+ * Read off the declaration rather than the type, so it is the same recipe and the same
1582
+ * destructuring position the source actually wrote.
1583
+ */
1584
+ const isSplitVariantPropsOf = (binding, recipe) => {
1585
+ for (const declaration of binding.getSourceFile().getDescendantsOfKind(ts_morph.SyntaxKind.VariableDeclaration)) {
1586
+ const nameNode = declaration.getNameNode();
1587
+ if (!ts_morph.Node.isArrayBindingPattern(nameNode)) continue;
1588
+ const first = nameNode.getElements()[0];
1589
+ if (!first || !ts_morph.Node.isBindingElement(first)) continue;
1590
+ if (first.getNameNode().getText() !== binding.getText()) continue;
1591
+ const initializer = declaration.getInitializer();
1592
+ if (!initializer || !ts_morph.Node.isCallExpression(initializer)) return false;
1593
+ const callee = initializer.getExpression();
1594
+ if (!ts_morph.Node.isPropertyAccessExpression(callee)) return false;
1595
+ return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
1596
+ }
1597
+ return false;
1598
+ };
1599
+ /**
1600
+ * Lower a config recipe call the same way an inline one lowers.
1601
+ *
1602
+ * The config lives in `ctx.recipes` rather than in the module, and the classes are named
1603
+ * from it identically — so this is the same `lowerRecipeCall`, handed the config from a
1604
+ * different place. Slot recipes are excluded by the caller: they resolve to one class per
1605
+ * slot rather than to a string.
1606
+ */
1607
+ const lowerConfigRecipeCall = (call, name) => {
1608
+ const config = ctx.recipes.getConfig(name);
1609
+ if (!config || config.slots !== void 0) return void 0;
1610
+ const className = config.className;
1611
+ if (!className) return void 0;
1612
+ if (!ts_morph.Node.isCallExpression(call)) return void 0;
1613
+ const argument = call.getArguments()[0];
1614
+ if (!argument || !ts_morph.Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
1615
+ const lowered = lowerRecipeCall(call, {
1616
+ config,
1617
+ name: className,
1618
+ box: void 0
1619
+ }, ctx, isInertExpression);
1620
+ if (lowered.kind !== "expression") return void 0;
1621
+ const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1622
+ if (!helper) return void 0;
1623
+ return {
1624
+ replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
1625
+ className: lowered.staticClasses,
1626
+ classNames: lowered.classNames,
1627
+ insert: helper.insert
1628
+ };
1629
+ };
1554
1630
  for (const item of parserResult.toArray()) {
1555
1631
  const type = item.type ?? "";
1556
1632
  const name = item.name ?? type;
@@ -1681,7 +1757,7 @@ const foldSource = (options) => {
1681
1757
  const entry = recipeConfigs.get(name);
1682
1758
  const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
1683
1759
  if (lowered.kind === "expression") {
1684
- const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1760
+ const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1685
1761
  if (helper) {
1686
1762
  tally.lowered++;
1687
1763
  candidates.push({
@@ -1792,7 +1868,7 @@ const foldSource = (options) => {
1792
1868
  });
1793
1869
  continue;
1794
1870
  }
1795
- if (!isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
1871
+ if (!(ts_morph.Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
1796
1872
  const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
1797
1873
  if (partial) {
1798
1874
  candidates.push({
@@ -1805,6 +1881,18 @@ const foldSource = (options) => {
1805
1881
  });
1806
1882
  continue;
1807
1883
  }
1884
+ const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
1885
+ if (lowered) {
1886
+ candidates.push({
1887
+ item,
1888
+ call,
1889
+ node: call,
1890
+ start,
1891
+ end,
1892
+ ...lowered
1893
+ });
1894
+ continue;
1895
+ }
1808
1896
  skipped.push({
1809
1897
  name,
1810
1898
  reason: "dynamic",
@@ -1932,6 +2020,36 @@ const foldSource = (options) => {
1932
2020
  });
1933
2021
  collectSourceFiles(item.box, dependencyScan);
1934
2022
  }
2023
+ const recipeSourceFile = [...recipeConfigs?.values() ?? []].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile() ?? candidates[0]?.node.getSourceFile();
2024
+ if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.PropertyAccessExpression)) {
2025
+ if (access.getName() !== "splitVariantProps") continue;
2026
+ const target = access.getExpression();
2027
+ if (!ts_morph.Node.isIdentifier(target)) continue;
2028
+ if (isShadowed(access, target.getText())) continue;
2029
+ const local = recipeConfigs?.get(target.getText());
2030
+ const importedConfig = !local && importsFor(recipeSourceFile).has(target.getText()) ? ctx.recipes.getConfig(target.getText()) : void 0;
2031
+ const entry = local && local !== AMBIGUOUS ? local : importedConfig ? {
2032
+ config: importedConfig,
2033
+ name: "",
2034
+ box: void 0
2035
+ } : void 0;
2036
+ if (!entry) continue;
2037
+ const call = access.getParent();
2038
+ if (!ts_morph.Node.isCallExpression(call) || call.getExpression() !== access) continue;
2039
+ const args = call.getArguments();
2040
+ if (args.length !== 1) continue;
2041
+ const start = call.getStart();
2042
+ const end = call.getEnd();
2043
+ if (code.slice(start, end) !== call.getText()) continue;
2044
+ if (collides([[start, end]])) continue;
2045
+ const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
2046
+ if (!helper) continue;
2047
+ const keys = Object.keys(entry.config.variants ?? {});
2048
+ magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
2049
+ applyInsert(helper.insert);
2050
+ applied.push([start, end]);
2051
+ loweredSplitProps.add(target.getText());
2052
+ }
1935
2053
  for (const [binding, tally] of recipeCalls) {
1936
2054
  if (!tally.seen || tally.lowered !== tally.seen) continue;
1937
2055
  const definition = recipeConfigs?.get(binding)?.box?.getNode?.();
package/dist/index.mjs CHANGED
@@ -768,6 +768,8 @@ const collectRecipeConfigs = (parserResult) => {
768
768
  };
769
769
  /** The generated binding a lowered dynamic axis calls. Lives in `cx`, which pulls no engine. */
770
770
  const RECIPE_PICK_HELPER = "cvaPick";
771
+ /** What `recipe.splitVariantProps` calls, reached directly once the call is lowered. */
772
+ const SPLIT_PROPS_HELPER = "splitProps";
771
773
  const HELPER = RECIPE_PICK_HELPER;
772
774
  /** Marker for a binding the fold must never resolve — declared twice, or unresolvable. */
773
775
  const AMBIGUOUS = Object.freeze({
@@ -775,6 +777,9 @@ const AMBIGUOUS = Object.freeze({
775
777
  name: "",
776
778
  box: void 0
777
779
  });
780
+ /** `.size`, or `["x-large"]` when the variant is not a valid identifier. */
781
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
782
+ const propertyAccess = (key) => IDENTIFIER.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
778
783
  const LITERAL_KINDS = new Set([
779
784
  SyntaxKind.StringLiteral,
780
785
  SyntaxKind.NoSubstitutionTemplateLiteral,
@@ -818,7 +823,7 @@ const propertyKey = (nameNode) => {
818
823
  * there is nothing to match — the host here is any import of the generated css module, which
819
824
  * a file defining a recipe necessarily has, since `cva` came from it.
820
825
  */
821
- const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
826
+ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
822
827
  const sourceFile = call.getSourceFile();
823
828
  let host;
824
829
  for (const declaration of sourceFile.getImportDeclarations()) {
@@ -826,7 +831,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
826
831
  if (declaration.isTypeOnly()) continue;
827
832
  for (const named of declaration.getNamedImports()) {
828
833
  if (named.isTypeOnly()) continue;
829
- if (named.getNameNode().getText() === "cvaPick") {
834
+ if (named.getNameNode().getText() === imported) {
830
835
  if (!isBambooCssModule(mod)) return void 0;
831
836
  const local = (named.getAliasNode() ?? named.getNameNode()).getText();
832
837
  return isShadowed(call, local) ? void 0 : { name: local };
@@ -835,15 +840,15 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
835
840
  if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
836
841
  }
837
842
  if (!host) return void 0;
838
- if (declaredAtModuleScope(sourceFile).has("cvaPick")) return void 0;
839
- if (isShadowed(call, "cvaPick")) return void 0;
843
+ if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
844
+ if (isShadowed(call, imported)) return void 0;
840
845
  const last = host.getNamedImports().at(-1);
841
846
  if (!last) return void 0;
842
847
  return {
843
- name: RECIPE_PICK_HELPER,
848
+ name: imported,
844
849
  insert: {
845
850
  pos: last.getEnd(),
846
- names: [RECIPE_PICK_HELPER]
851
+ names: [imported]
847
852
  }
848
853
  };
849
854
  };
@@ -889,11 +894,28 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
889
894
  const effectful = [];
890
895
  if (args.length === 1) {
891
896
  const arg = args[0];
892
- if (!arg || !Node.isObjectLiteralExpression(arg)) return {
897
+ if (!arg) return {
893
898
  kind: "decline",
894
899
  reason: "dynamic"
895
900
  };
896
- for (const property of arg.getProperties()) {
901
+ /**
902
+ * `input(variantProps)` — a selection the build cannot see inside.
903
+ *
904
+ * The classes are still knowable: a recipe emits one per declared variant, so the call is
905
+ * one term per variant reading that binding. This is the shape a wrapper component takes,
906
+ * where the variants are the component's public API and cannot be literals by definition.
907
+ *
908
+ * An identifier only. Each variant reads the binding again, and re-reading anything else —
909
+ * a call, a property access — would evaluate it once per axis instead of once.
910
+ */
911
+ if (Node.isIdentifier(arg)) {
912
+ const binding = arg.getText();
913
+ for (const key of Object.keys(config.variants ?? {})) dynamicAxes.set(key, `${binding}${propertyAccess(key)}`);
914
+ } else if (!Node.isObjectLiteralExpression(arg)) return {
915
+ kind: "decline",
916
+ reason: "dynamic"
917
+ };
918
+ else for (const property of arg.getProperties()) {
897
919
  if (Node.isSpreadAssignment(property)) return {
898
920
  kind: "decline",
899
921
  reason: "dynamic"
@@ -1388,7 +1410,7 @@ const hasStyles = (data) => data.length > 0 && data.every((entry) => entry != nu
1388
1410
  const argumentsAccountedFor = (call, boxNode) => {
1389
1411
  if (!Node.isCallExpression(call)) return false;
1390
1412
  const args = call.getArguments();
1391
- if (args.length === 0) return false;
1413
+ if (args.length === 0) return true;
1392
1414
  if (box.isArray(boxNode) && boxNode.getNode() === call) {
1393
1415
  if (boxNode.value.length !== args.length) return false;
1394
1416
  return args.every((arg, index) => accountsForSource(arg, boxNode.value[index]));
@@ -1513,6 +1535,8 @@ const foldSource = (options) => {
1513
1535
  * saving real, and it is only correct to claim it once nothing reads the binding.
1514
1536
  */
1515
1537
  const recipeCalls = /* @__PURE__ */ new Map();
1538
+ /** Bindings whose `splitVariantProps` was rewritten, so that access no longer reads them. */
1539
+ const loweredSplitProps = /* @__PURE__ */ new Set();
1516
1540
  /** Ranges already reported as declined, so one call is never counted twice. */
1517
1541
  const reportedRanges = /* @__PURE__ */ new Set();
1518
1542
  const importCache = /* @__PURE__ */ new Map();
@@ -1524,6 +1548,58 @@ const foldSource = (options) => {
1524
1548
  }
1525
1549
  return names;
1526
1550
  };
1551
+ /**
1552
+ * Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
1553
+ *
1554
+ * Read off the declaration rather than the type, so it is the same recipe and the same
1555
+ * destructuring position the source actually wrote.
1556
+ */
1557
+ const isSplitVariantPropsOf = (binding, recipe) => {
1558
+ for (const declaration of binding.getSourceFile().getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
1559
+ const nameNode = declaration.getNameNode();
1560
+ if (!Node.isArrayBindingPattern(nameNode)) continue;
1561
+ const first = nameNode.getElements()[0];
1562
+ if (!first || !Node.isBindingElement(first)) continue;
1563
+ if (first.getNameNode().getText() !== binding.getText()) continue;
1564
+ const initializer = declaration.getInitializer();
1565
+ if (!initializer || !Node.isCallExpression(initializer)) return false;
1566
+ const callee = initializer.getExpression();
1567
+ if (!Node.isPropertyAccessExpression(callee)) return false;
1568
+ return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
1569
+ }
1570
+ return false;
1571
+ };
1572
+ /**
1573
+ * Lower a config recipe call the same way an inline one lowers.
1574
+ *
1575
+ * The config lives in `ctx.recipes` rather than in the module, and the classes are named
1576
+ * from it identically — so this is the same `lowerRecipeCall`, handed the config from a
1577
+ * different place. Slot recipes are excluded by the caller: they resolve to one class per
1578
+ * slot rather than to a string.
1579
+ */
1580
+ const lowerConfigRecipeCall = (call, name) => {
1581
+ const config = ctx.recipes.getConfig(name);
1582
+ if (!config || config.slots !== void 0) return void 0;
1583
+ const className = config.className;
1584
+ if (!className) return void 0;
1585
+ if (!Node.isCallExpression(call)) return void 0;
1586
+ const argument = call.getArguments()[0];
1587
+ if (!argument || !Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
1588
+ const lowered = lowerRecipeCall(call, {
1589
+ config,
1590
+ name: className,
1591
+ box: void 0
1592
+ }, ctx, isInertExpression);
1593
+ if (lowered.kind !== "expression") return void 0;
1594
+ const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1595
+ if (!helper) return void 0;
1596
+ return {
1597
+ replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
1598
+ className: lowered.staticClasses,
1599
+ classNames: lowered.classNames,
1600
+ insert: helper.insert
1601
+ };
1602
+ };
1527
1603
  for (const item of parserResult.toArray()) {
1528
1604
  const type = item.type ?? "";
1529
1605
  const name = item.name ?? type;
@@ -1654,7 +1730,7 @@ const foldSource = (options) => {
1654
1730
  const entry = recipeConfigs.get(name);
1655
1731
  const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
1656
1732
  if (lowered.kind === "expression") {
1657
- const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1733
+ const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1658
1734
  if (helper) {
1659
1735
  tally.lowered++;
1660
1736
  candidates.push({
@@ -1765,7 +1841,7 @@ const foldSource = (options) => {
1765
1841
  });
1766
1842
  continue;
1767
1843
  }
1768
- if (!isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
1844
+ if (!(Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
1769
1845
  const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
1770
1846
  if (partial) {
1771
1847
  candidates.push({
@@ -1778,6 +1854,18 @@ const foldSource = (options) => {
1778
1854
  });
1779
1855
  continue;
1780
1856
  }
1857
+ const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
1858
+ if (lowered) {
1859
+ candidates.push({
1860
+ item,
1861
+ call,
1862
+ node: call,
1863
+ start,
1864
+ end,
1865
+ ...lowered
1866
+ });
1867
+ continue;
1868
+ }
1781
1869
  skipped.push({
1782
1870
  name,
1783
1871
  reason: "dynamic",
@@ -1905,6 +1993,36 @@ const foldSource = (options) => {
1905
1993
  });
1906
1994
  collectSourceFiles(item.box, dependencyScan);
1907
1995
  }
1996
+ const recipeSourceFile = [...recipeConfigs?.values() ?? []].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile() ?? candidates[0]?.node.getSourceFile();
1997
+ if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
1998
+ if (access.getName() !== "splitVariantProps") continue;
1999
+ const target = access.getExpression();
2000
+ if (!Node.isIdentifier(target)) continue;
2001
+ if (isShadowed(access, target.getText())) continue;
2002
+ const local = recipeConfigs?.get(target.getText());
2003
+ const importedConfig = !local && importsFor(recipeSourceFile).has(target.getText()) ? ctx.recipes.getConfig(target.getText()) : void 0;
2004
+ const entry = local && local !== AMBIGUOUS ? local : importedConfig ? {
2005
+ config: importedConfig,
2006
+ name: "",
2007
+ box: void 0
2008
+ } : void 0;
2009
+ if (!entry) continue;
2010
+ const call = access.getParent();
2011
+ if (!Node.isCallExpression(call) || call.getExpression() !== access) continue;
2012
+ const args = call.getArguments();
2013
+ if (args.length !== 1) continue;
2014
+ const start = call.getStart();
2015
+ const end = call.getEnd();
2016
+ if (code.slice(start, end) !== call.getText()) continue;
2017
+ if (collides([[start, end]])) continue;
2018
+ const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
2019
+ if (!helper) continue;
2020
+ const keys = Object.keys(entry.config.variants ?? {});
2021
+ magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
2022
+ applyInsert(helper.insert);
2023
+ applied.push([start, end]);
2024
+ loweredSplitProps.add(target.getText());
2025
+ }
1908
2026
  for (const [binding, tally] of recipeCalls) {
1909
2027
  if (!tally.seen || tally.lowered !== tally.seen) continue;
1910
2028
  const definition = recipeConfigs?.get(binding)?.box?.getNode?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -37,18 +37,18 @@
37
37
  "dependencies": {
38
38
  "magic-string": "0.30.21",
39
39
  "ts-morph": "28.0.0",
40
- "@bamboocss/config": "1.24.0",
41
- "@bamboocss/core": "1.24.0",
42
- "@bamboocss/extractor": "1.24.0",
43
- "@bamboocss/logger": "1.24.0",
44
- "@bamboocss/node": "1.24.0",
45
- "@bamboocss/shared": "1.24.0",
46
- "@bamboocss/types": "1.24.0"
40
+ "@bamboocss/config": "1.26.0",
41
+ "@bamboocss/extractor": "1.26.0",
42
+ "@bamboocss/core": "1.26.0",
43
+ "@bamboocss/node": "1.26.0",
44
+ "@bamboocss/logger": "1.26.0",
45
+ "@bamboocss/shared": "1.26.0",
46
+ "@bamboocss/types": "1.26.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@jridgewell/trace-mapping": "^0.3.31",
50
50
  "vite": "7.2.6",
51
- "@bamboocss/fixture": "1.24.0"
51
+ "@bamboocss/fixture": "1.26.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "vite": ">=5"