@mgcrea/react-native-tailwind 0.11.1 → 0.12.1

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 (41) hide show
  1. package/dist/babel/config-loader.d.ts +3 -0
  2. package/dist/babel/config-loader.test.ts +2 -2
  3. package/dist/babel/config-loader.ts +37 -2
  4. package/dist/babel/index.cjs +325 -42
  5. package/dist/babel/plugin.test.ts +498 -0
  6. package/dist/babel/plugin.ts +66 -17
  7. package/dist/babel/utils/styleInjection.ts +57 -17
  8. package/dist/babel/utils/twProcessing.d.ts +8 -1
  9. package/dist/babel/utils/twProcessing.ts +212 -4
  10. package/dist/parser/index.d.ts +1 -0
  11. package/dist/parser/index.js +1 -1
  12. package/dist/parser/layout.js +1 -1
  13. package/dist/parser/layout.test.js +1 -1
  14. package/dist/parser/spacing.d.ts +1 -1
  15. package/dist/parser/spacing.js +1 -1
  16. package/dist/parser/spacing.test.js +1 -1
  17. package/dist/parser/typography.d.ts +2 -1
  18. package/dist/parser/typography.js +1 -1
  19. package/dist/parser/typography.test.js +1 -1
  20. package/dist/runtime.cjs +1 -1
  21. package/dist/runtime.cjs.map +3 -3
  22. package/dist/runtime.js +1 -1
  23. package/dist/runtime.js.map +3 -3
  24. package/dist/runtime.test.js +1 -1
  25. package/dist/types/runtime.d.ts +8 -1
  26. package/package.json +1 -1
  27. package/src/babel/config-loader.test.ts +2 -2
  28. package/src/babel/config-loader.ts +37 -2
  29. package/src/babel/plugin.test.ts +498 -0
  30. package/src/babel/plugin.ts +66 -17
  31. package/src/babel/utils/styleInjection.ts +57 -17
  32. package/src/babel/utils/twProcessing.ts +212 -4
  33. package/src/parser/index.ts +2 -1
  34. package/src/parser/layout.test.ts +61 -0
  35. package/src/parser/layout.ts +55 -1
  36. package/src/parser/spacing.test.ts +62 -0
  37. package/src/parser/spacing.ts +7 -7
  38. package/src/parser/typography.test.ts +102 -0
  39. package/src/parser/typography.ts +61 -15
  40. package/src/runtime.test.ts +4 -1
  41. package/src/types/runtime.ts +8 -1
@@ -701,6 +701,21 @@ function parseArbitraryZIndex(value) {
701
701
  }
702
702
  return null;
703
703
  }
704
+ function parseArbitraryGrowShrink(value) {
705
+ const match = value.match(/^\[(\d+(?:\.\d+)?|\.\d+)\]$/);
706
+ if (match) {
707
+ return parseFloat(match[1]);
708
+ }
709
+ if (value.startsWith("[") && value.endsWith("]")) {
710
+ if (process.env.NODE_ENV !== "production") {
711
+ console.warn(
712
+ `[react-native-tailwind] Invalid arbitrary grow/shrink value: ${value}. Only non-negative numbers are supported (e.g., [1.5], [2], [0.5], [.5]).`
713
+ );
714
+ }
715
+ return null;
716
+ }
717
+ return null;
718
+ }
704
719
  var DISPLAY_MAP = {
705
720
  flex: { display: "flex" },
706
721
  hidden: { display: "none" }
@@ -725,7 +740,12 @@ var GROW_SHRINK_MAP = {
725
740
  grow: { flexGrow: 1 },
726
741
  "grow-0": { flexGrow: 0 },
727
742
  shrink: { flexShrink: 1 },
728
- "shrink-0": { flexShrink: 0 }
743
+ "shrink-0": { flexShrink: 0 },
744
+ // CSS-style aliases
745
+ "flex-grow": { flexGrow: 1 },
746
+ "flex-grow-0": { flexGrow: 0 },
747
+ "flex-shrink": { flexShrink: 1 },
748
+ "flex-shrink-0": { flexShrink: 0 }
729
749
  };
730
750
  var JUSTIFY_CONTENT_MAP = {
731
751
  "justify-start": { justifyContent: "flex-start" },
@@ -920,6 +940,22 @@ function parseLayout(cls) {
920
940
  return { top: insetValue, right: insetValue, bottom: insetValue, left: insetValue };
921
941
  }
922
942
  }
943
+ if (cls.startsWith("grow-") || cls.startsWith("flex-grow-")) {
944
+ const prefix = cls.startsWith("flex-grow-") ? "flex-grow-" : "grow-";
945
+ const growKey = cls.substring(prefix.length);
946
+ const arbitraryGrow = parseArbitraryGrowShrink(growKey);
947
+ if (arbitraryGrow !== null) {
948
+ return { flexGrow: arbitraryGrow };
949
+ }
950
+ }
951
+ if (cls.startsWith("shrink-") || cls.startsWith("flex-shrink-")) {
952
+ const prefix = cls.startsWith("flex-shrink-") ? "flex-shrink-" : "shrink-";
953
+ const shrinkKey = cls.substring(prefix.length);
954
+ const arbitraryShrink = parseArbitraryGrowShrink(shrinkKey);
955
+ if (arbitraryShrink !== null) {
956
+ return { flexShrink: arbitraryShrink };
957
+ }
958
+ }
923
959
  return DISPLAY_MAP[cls] ?? FLEX_DIRECTION_MAP[cls] ?? FLEX_WRAP_MAP[cls] ?? FLEX_MAP[cls] ?? GROW_SHRINK_MAP[cls] ?? JUSTIFY_CONTENT_MAP[cls] ?? ALIGN_ITEMS_MAP[cls] ?? ALIGN_SELF_MAP[cls] ?? ALIGN_CONTENT_MAP[cls] ?? POSITION_MAP[cls] ?? OVERFLOW_MAP[cls] ?? OPACITY_MAP[cls] ?? null;
924
960
  }
925
961
 
@@ -1194,14 +1230,14 @@ var SPACING_SCALE = {
1194
1230
  96: 384
1195
1231
  };
1196
1232
  function parseArbitrarySpacing(value) {
1197
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
1233
+ const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?)(?:px)?\]$/);
1198
1234
  if (pxMatch) {
1199
- return parseInt(pxMatch[1], 10);
1235
+ return parseFloat(pxMatch[1]);
1200
1236
  }
1201
1237
  if (value.startsWith("[") && value.endsWith("]")) {
1202
1238
  if (process.env.NODE_ENV !== "production") {
1203
1239
  console.warn(
1204
- `[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px] or [16]).`
1240
+ `[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px], [16], [4.5px], [4.5]).`
1205
1241
  );
1206
1242
  }
1207
1243
  return null;
@@ -1649,14 +1685,14 @@ var TRACKING_MAP = {
1649
1685
  "tracking-widest": { letterSpacing: 1.6 }
1650
1686
  };
1651
1687
  function parseArbitraryFontSize(value) {
1652
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
1688
+ const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
1653
1689
  if (pxMatch) {
1654
- return parseInt(pxMatch[1], 10);
1690
+ return parseFloat(pxMatch[1]);
1655
1691
  }
1656
1692
  if (value.startsWith("[") && value.endsWith("]")) {
1657
1693
  if (process.env.NODE_ENV !== "production") {
1658
1694
  console.warn(
1659
- `[react-native-tailwind] Unsupported arbitrary font size value: ${value}. Only px values are supported (e.g., [18px] or [18]).`
1695
+ `[react-native-tailwind] Unsupported arbitrary font size value: ${value}. Only px values are supported (e.g., [18px], [13.5px], [.5]).`
1660
1696
  );
1661
1697
  }
1662
1698
  return null;
@@ -1664,21 +1700,36 @@ function parseArbitraryFontSize(value) {
1664
1700
  return null;
1665
1701
  }
1666
1702
  function parseArbitraryLineHeight(value) {
1667
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
1703
+ const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
1668
1704
  if (pxMatch) {
1669
- return parseInt(pxMatch[1], 10);
1705
+ return parseFloat(pxMatch[1]);
1670
1706
  }
1671
1707
  if (value.startsWith("[") && value.endsWith("]")) {
1672
1708
  if (process.env.NODE_ENV !== "production") {
1673
1709
  console.warn(
1674
- `[react-native-tailwind] Unsupported arbitrary line height value: ${value}. Only px values are supported (e.g., [24px] or [24]).`
1710
+ `[react-native-tailwind] Unsupported arbitrary line height value: ${value}. Only px values are supported (e.g., [24px], [21.5px], [.5]).`
1675
1711
  );
1676
1712
  }
1677
1713
  return null;
1678
1714
  }
1679
1715
  return null;
1680
1716
  }
1681
- function parseTypography(cls, customFontFamily) {
1717
+ function parseArbitraryLetterSpacing(value) {
1718
+ const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?|-?\.\d+)(?:px)?\]$/);
1719
+ if (pxMatch) {
1720
+ return parseFloat(pxMatch[1]);
1721
+ }
1722
+ if (value.startsWith("[") && value.endsWith("]")) {
1723
+ if (process.env.NODE_ENV !== "production") {
1724
+ console.warn(
1725
+ `[react-native-tailwind] Unsupported arbitrary letter spacing value: ${value}. Only px values are supported (e.g., [0.5px], [0.3], [.5], [-0.4]).`
1726
+ );
1727
+ }
1728
+ return null;
1729
+ }
1730
+ return null;
1731
+ }
1732
+ function parseTypography(cls, customFontFamily, customFontSize) {
1682
1733
  const fontFamilyMap = customFontFamily ? {
1683
1734
  ...FONT_FAMILY_MAP,
1684
1735
  ...Object.fromEntries(
@@ -1691,6 +1742,9 @@ function parseTypography(cls, customFontFamily) {
1691
1742
  if (arbitraryValue !== null) {
1692
1743
  return { fontSize: arbitraryValue };
1693
1744
  }
1745
+ if (customFontSize?.[sizeKey] !== void 0) {
1746
+ return { fontSize: customFontSize[sizeKey] };
1747
+ }
1694
1748
  const fontSize = FONT_SIZES[sizeKey];
1695
1749
  if (fontSize !== void 0) {
1696
1750
  return { fontSize };
@@ -1707,6 +1761,13 @@ function parseTypography(cls, customFontFamily) {
1707
1761
  return { lineHeight };
1708
1762
  }
1709
1763
  }
1764
+ if (cls.startsWith("tracking-")) {
1765
+ const trackingKey = cls.substring(9);
1766
+ const arbitraryValue = parseArbitraryLetterSpacing(trackingKey);
1767
+ if (arbitraryValue !== null) {
1768
+ return { letterSpacing: arbitraryValue };
1769
+ }
1770
+ }
1710
1771
  return fontFamilyMap[cls] ?? FONT_WEIGHT_MAP[cls] ?? FONT_STYLE_MAP[cls] ?? TEXT_ALIGN_MAP[cls] ?? TEXT_DECORATION_MAP[cls] ?? TEXT_TRANSFORM_MAP[cls] ?? LINE_HEIGHT_MAP[cls] ?? TRACKING_MAP[cls] ?? null;
1711
1772
  }
1712
1773
 
@@ -1863,7 +1924,7 @@ function parseClass(cls, customTheme) {
1863
1924
  parseBorder,
1864
1925
  (cls2) => parseColor(cls2, customTheme?.colors),
1865
1926
  parseLayout,
1866
- (cls2) => parseTypography(cls2, customTheme?.fontFamily),
1927
+ (cls2) => parseTypography(cls2, customTheme?.fontFamily, customTheme?.fontSize),
1867
1928
  parseSizing,
1868
1929
  parseShadow,
1869
1930
  parseAspectRatio,
@@ -1935,11 +1996,11 @@ function extractCustomTheme(filename) {
1935
1996
  const projectDir = path.dirname(filename);
1936
1997
  const configPath = findTailwindConfig(projectDir);
1937
1998
  if (!configPath) {
1938
- return { colors: {}, fontFamily: {} };
1999
+ return { colors: {}, fontFamily: {}, fontSize: {} };
1939
2000
  }
1940
2001
  const config = loadTailwindConfig(configPath);
1941
2002
  if (!config?.theme) {
1942
- return { colors: {}, fontFamily: {} };
2003
+ return { colors: {}, fontFamily: {}, fontSize: {} };
1943
2004
  }
1944
2005
  if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
1945
2006
  console.warn(
@@ -1961,9 +2022,33 @@ function extractCustomTheme(filename) {
1961
2022
  fontFamilyResult[key] = value;
1962
2023
  }
1963
2024
  }
2025
+ if (config.theme.fontSize && !config.theme.extend?.fontSize && process.env.NODE_ENV !== "production") {
2026
+ console.warn(
2027
+ "[react-native-tailwind] Using theme.fontSize will override all default font sizes. Use theme.extend.fontSize to add custom font sizes while keeping defaults."
2028
+ );
2029
+ }
2030
+ const fontSize = config.theme.extend?.fontSize ?? config.theme.fontSize ?? {};
2031
+ const fontSizeResult = {};
2032
+ for (const [key, value] of Object.entries(fontSize)) {
2033
+ if (typeof value === "number") {
2034
+ fontSizeResult[key] = value;
2035
+ } else if (typeof value === "string") {
2036
+ const parsed = parseFloat(value.replace(/px$/, ""));
2037
+ if (!isNaN(parsed)) {
2038
+ fontSizeResult[key] = parsed;
2039
+ } else {
2040
+ if (process.env.NODE_ENV !== "production") {
2041
+ console.warn(
2042
+ `[react-native-tailwind] Invalid fontSize value for "${key}": ${value}. Expected number or string like "18px".`
2043
+ );
2044
+ }
2045
+ }
2046
+ }
2047
+ }
1964
2048
  return {
1965
2049
  colors: flattenColors(colors),
1966
- fontFamily: fontFamilyResult
2050
+ fontFamily: fontFamilyResult,
2051
+ fontSize: fontSizeResult
1967
2052
  };
1968
2053
  }
1969
2054
 
@@ -2480,17 +2565,29 @@ function processPlatformModifiers(platformModifiers, state, parseClassName2, gen
2480
2565
  // src/babel/utils/styleInjection.ts
2481
2566
  function addStyleSheetImport(path2, t) {
2482
2567
  const body = path2.node.body;
2483
- let reactNativeImport = null;
2568
+ let existingValueImport = null;
2484
2569
  for (const statement of body) {
2485
2570
  if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2486
- reactNativeImport = statement;
2571
+ if (statement.importKind === "type") {
2572
+ continue;
2573
+ }
2574
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
2575
+ if (hasNamespaceImport) {
2576
+ continue;
2577
+ }
2578
+ existingValueImport = statement;
2487
2579
  break;
2488
2580
  }
2489
2581
  }
2490
- if (reactNativeImport) {
2491
- reactNativeImport.specifiers.push(
2492
- t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))
2582
+ if (existingValueImport) {
2583
+ const hasStyleSheet = existingValueImport.specifiers.some(
2584
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "StyleSheet"
2493
2585
  );
2586
+ if (!hasStyleSheet) {
2587
+ existingValueImport.specifiers.push(
2588
+ t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))
2589
+ );
2590
+ }
2494
2591
  } else {
2495
2592
  const importDeclaration = t.importDeclaration(
2496
2593
  [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
@@ -2501,15 +2598,29 @@ function addStyleSheetImport(path2, t) {
2501
2598
  }
2502
2599
  function addPlatformImport(path2, t) {
2503
2600
  const body = path2.node.body;
2504
- let reactNativeImport = null;
2601
+ let existingValueImport = null;
2505
2602
  for (const statement of body) {
2506
2603
  if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2507
- reactNativeImport = statement;
2604
+ if (statement.importKind === "type") {
2605
+ continue;
2606
+ }
2607
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
2608
+ if (hasNamespaceImport) {
2609
+ continue;
2610
+ }
2611
+ existingValueImport = statement;
2508
2612
  break;
2509
2613
  }
2510
2614
  }
2511
- if (reactNativeImport) {
2512
- reactNativeImport.specifiers.push(t.importSpecifier(t.identifier("Platform"), t.identifier("Platform")));
2615
+ if (existingValueImport) {
2616
+ const hasPlatform = existingValueImport.specifiers.some(
2617
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "Platform"
2618
+ );
2619
+ if (!hasPlatform) {
2620
+ existingValueImport.specifiers.push(
2621
+ t.importSpecifier(t.identifier("Platform"), t.identifier("Platform"))
2622
+ );
2623
+ }
2513
2624
  } else {
2514
2625
  const importDeclaration = t.importDeclaration(
2515
2626
  [t.importSpecifier(t.identifier("Platform"), t.identifier("Platform"))],
@@ -2725,7 +2836,7 @@ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2725
2836
  }
2726
2837
 
2727
2838
  // src/babel/utils/twProcessing.ts
2728
- function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, t) {
2839
+ function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, findComponentScope2, t) {
2729
2840
  const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2730
2841
  const modifierClasses = [];
2731
2842
  for (const modifier of rawModifierClasses) {
@@ -2756,8 +2867,145 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
2756
2867
  } else {
2757
2868
  objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
2758
2869
  }
2870
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
2871
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
2872
+ const otherModifiers = modifierClasses.filter(
2873
+ (m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier)
2874
+ );
2875
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
2876
+ let componentScope = null;
2877
+ if (hasColorSchemeModifiers) {
2878
+ componentScope = findComponentScope2(path2, t);
2879
+ if (!componentScope) {
2880
+ if (process.env.NODE_ENV !== "production") {
2881
+ console.warn(
2882
+ `[react-native-tailwind] Color scheme modifiers (dark:, light:) in tw/twStyle calls must be used inside a React component. Modifiers will be ignored.`
2883
+ );
2884
+ }
2885
+ } else {
2886
+ state.functionComponentsNeedingColorScheme.add(componentScope);
2887
+ }
2888
+ }
2889
+ if (hasColorSchemeModifiers && componentScope) {
2890
+ const colorSchemeConditionals = processColorSchemeModifiers(
2891
+ colorSchemeModifiers,
2892
+ state,
2893
+ parseClassName2,
2894
+ generateStyleKey2,
2895
+ t
2896
+ );
2897
+ const styleArrayElements = [];
2898
+ if (baseClasses.length > 0) {
2899
+ const baseClassName = baseClasses.join(" ");
2900
+ const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
2901
+ const baseStyleKey = generateStyleKey2(baseClassName);
2902
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2903
+ styleArrayElements.push(
2904
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2905
+ );
2906
+ }
2907
+ styleArrayElements.push(...colorSchemeConditionals);
2908
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
2909
+ const darkModifiers = colorSchemeModifiers.filter((m) => m.modifier === "dark");
2910
+ const lightModifiers = colorSchemeModifiers.filter((m) => m.modifier === "light");
2911
+ if (darkModifiers.length > 0) {
2912
+ const darkClassNames = darkModifiers.map((m) => m.baseClass).join(" ");
2913
+ const darkStyleObject = parseClassName2(darkClassNames, state.customTheme);
2914
+ const darkStyleKey = generateStyleKey2(`dark_${darkClassNames}`);
2915
+ state.styleRegistry.set(darkStyleKey, darkStyleObject);
2916
+ objectProperties.push(
2917
+ t.objectProperty(
2918
+ t.identifier("darkStyle"),
2919
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(darkStyleKey))
2920
+ )
2921
+ );
2922
+ }
2923
+ if (lightModifiers.length > 0) {
2924
+ const lightClassNames = lightModifiers.map((m) => m.baseClass).join(" ");
2925
+ const lightStyleObject = parseClassName2(lightClassNames, state.customTheme);
2926
+ const lightStyleKey = generateStyleKey2(`light_${lightClassNames}`);
2927
+ state.styleRegistry.set(lightStyleKey, lightStyleObject);
2928
+ objectProperties.push(
2929
+ t.objectProperty(
2930
+ t.identifier("lightStyle"),
2931
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(lightStyleKey))
2932
+ )
2933
+ );
2934
+ }
2935
+ }
2936
+ const hasPlatformModifiers = platformModifiers.length > 0;
2937
+ if (hasPlatformModifiers) {
2938
+ state.needsPlatformImport = true;
2939
+ const platformSelectExpression = processPlatformModifiers(
2940
+ platformModifiers,
2941
+ state,
2942
+ parseClassName2,
2943
+ generateStyleKey2,
2944
+ t
2945
+ );
2946
+ if (hasColorSchemeModifiers && componentScope) {
2947
+ const styleProperty = objectProperties.find(
2948
+ (prop) => t.isIdentifier(prop.key) && prop.key.name === "style"
2949
+ );
2950
+ if (styleProperty && t.isArrayExpression(styleProperty.value)) {
2951
+ styleProperty.value.elements.push(platformSelectExpression);
2952
+ }
2953
+ } else {
2954
+ const styleArrayElements = [];
2955
+ if (baseClasses.length > 0) {
2956
+ const baseClassName = baseClasses.join(" ");
2957
+ const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
2958
+ const baseStyleKey = generateStyleKey2(baseClassName);
2959
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2960
+ styleArrayElements.push(
2961
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2962
+ );
2963
+ }
2964
+ styleArrayElements.push(platformSelectExpression);
2965
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
2966
+ }
2967
+ const iosModifiers = platformModifiers.filter((m) => m.modifier === "ios");
2968
+ const androidModifiers = platformModifiers.filter((m) => m.modifier === "android");
2969
+ const webModifiers = platformModifiers.filter((m) => m.modifier === "web");
2970
+ if (iosModifiers.length > 0) {
2971
+ const iosClassNames = iosModifiers.map((m) => m.baseClass).join(" ");
2972
+ const iosStyleObject = parseClassName2(iosClassNames, state.customTheme);
2973
+ const iosStyleKey = generateStyleKey2(`ios_${iosClassNames}`);
2974
+ state.styleRegistry.set(iosStyleKey, iosStyleObject);
2975
+ objectProperties.push(
2976
+ t.objectProperty(
2977
+ t.identifier("iosStyle"),
2978
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(iosStyleKey))
2979
+ )
2980
+ );
2981
+ }
2982
+ if (androidModifiers.length > 0) {
2983
+ const androidClassNames = androidModifiers.map((m) => m.baseClass).join(" ");
2984
+ const androidStyleObject = parseClassName2(androidClassNames, state.customTheme);
2985
+ const androidStyleKey = generateStyleKey2(`android_${androidClassNames}`);
2986
+ state.styleRegistry.set(androidStyleKey, androidStyleObject);
2987
+ objectProperties.push(
2988
+ t.objectProperty(
2989
+ t.identifier("androidStyle"),
2990
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(androidStyleKey))
2991
+ )
2992
+ );
2993
+ }
2994
+ if (webModifiers.length > 0) {
2995
+ const webClassNames = webModifiers.map((m) => m.baseClass).join(" ");
2996
+ const webStyleObject = parseClassName2(webClassNames, state.customTheme);
2997
+ const webStyleKey = generateStyleKey2(`web_${webClassNames}`);
2998
+ state.styleRegistry.set(webStyleKey, webStyleObject);
2999
+ objectProperties.push(
3000
+ t.objectProperty(
3001
+ t.identifier("webStyle"),
3002
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(webStyleKey))
3003
+ )
3004
+ );
3005
+ }
3006
+ }
2759
3007
  const modifiersByType = /* @__PURE__ */ new Map();
2760
- for (const mod of modifierClasses) {
3008
+ for (const mod of otherModifiers) {
2761
3009
  if (!modifiersByType.has(mod.modifier)) {
2762
3010
  modifiersByType.set(mod.modifier, []);
2763
3011
  }
@@ -2876,6 +3124,10 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2876
3124
  state.twImportNames = /* @__PURE__ */ new Set();
2877
3125
  state.hasTwImport = false;
2878
3126
  state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
3127
+ state.hasColorSchemeImport = false;
3128
+ state.colorSchemeLocalIdentifier = void 0;
3129
+ state.needsPlatformImport = false;
3130
+ state.hasPlatformImport = false;
2879
3131
  state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
2880
3132
  state.schemeModifierConfig = schemeModifierConfig;
2881
3133
  },
@@ -2934,7 +3186,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2934
3186
  }
2935
3187
  state.reactNativeImportPath = path2;
2936
3188
  }
2937
- if (node.source.value === state.colorSchemeImportSource) {
3189
+ if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
2938
3190
  const specifiers = node.specifiers;
2939
3191
  for (const spec of specifiers) {
2940
3192
  if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
@@ -2954,7 +3206,6 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2954
3206
  if (importedName === "tw" || importedName === "twStyle") {
2955
3207
  const localName = spec.local.name;
2956
3208
  state.twImportNames.add(localName);
2957
- state.hasTwImport = true;
2958
3209
  }
2959
3210
  }
2960
3211
  });
@@ -2987,10 +3238,21 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2987
3238
  path2.replaceWith(
2988
3239
  t.objectExpression([t.objectProperty(t.identifier("style"), t.objectExpression([]))])
2989
3240
  );
3241
+ state.hasTwImport = true;
2990
3242
  return;
2991
3243
  }
2992
3244
  state.hasClassNames = true;
2993
- processTwCall(className, path2, state, parseClassName, generateStyleKey, splitModifierClasses, t);
3245
+ processTwCall(
3246
+ className,
3247
+ path2,
3248
+ state,
3249
+ parseClassName,
3250
+ generateStyleKey,
3251
+ splitModifierClasses,
3252
+ findComponentScope,
3253
+ t
3254
+ );
3255
+ state.hasTwImport = true;
2994
3256
  },
2995
3257
  // Handle twStyle('...') call expressions
2996
3258
  CallExpression(path2, state) {
@@ -3022,10 +3284,21 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3022
3284
  const className = arg.value.trim();
3023
3285
  if (!className) {
3024
3286
  path2.replaceWith(t.identifier("undefined"));
3287
+ state.hasTwImport = true;
3025
3288
  return;
3026
3289
  }
3027
3290
  state.hasClassNames = true;
3028
- processTwCall(className, path2, state, parseClassName, generateStyleKey, splitModifierClasses, t);
3291
+ processTwCall(
3292
+ className,
3293
+ path2,
3294
+ state,
3295
+ parseClassName,
3296
+ generateStyleKey,
3297
+ splitModifierClasses,
3298
+ findComponentScope,
3299
+ t
3300
+ );
3301
+ state.hasTwImport = true;
3029
3302
  },
3030
3303
  JSXAttribute(path2, state) {
3031
3304
  const node = path2.node;
@@ -3038,14 +3311,14 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3038
3311
  }
3039
3312
  const value = node.value;
3040
3313
  const targetStyleProp = getTargetStyleProp(attributeName);
3041
- if (t.isStringLiteral(value)) {
3042
- const className = value.value.trim();
3043
- if (!className) {
3314
+ const processStaticClassName = (className) => {
3315
+ const trimmedClassName = className.trim();
3316
+ if (!trimmedClassName) {
3044
3317
  path2.remove();
3045
- return;
3318
+ return true;
3046
3319
  }
3047
3320
  state.hasClassNames = true;
3048
- const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
3321
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
3049
3322
  const modifierClasses = [];
3050
3323
  for (const modifier of rawModifierClasses) {
3051
3324
  if (isSchemeModifier(modifier.modifier)) {
@@ -3169,7 +3442,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3169
3442
  } else {
3170
3443
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3171
3444
  }
3172
- return;
3445
+ return true;
3173
3446
  } else {
3174
3447
  }
3175
3448
  }
@@ -3220,7 +3493,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3220
3493
  path2.node.name = t.jsxIdentifier(targetStyleProp);
3221
3494
  path2.node.value = t.jsxExpressionContainer(styleExpression);
3222
3495
  }
3223
- return;
3496
+ return true;
3224
3497
  }
3225
3498
  if (hasStateModifiers) {
3226
3499
  const jsxOpeningElement = path2.parent;
@@ -3258,11 +3531,11 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3258
3531
  } else {
3259
3532
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3260
3533
  }
3261
- return;
3534
+ return true;
3262
3535
  }
3263
3536
  } else {
3264
3537
  const styleExpression = processStaticClassNameWithModifiers(
3265
- className,
3538
+ trimmedClassName,
3266
3539
  state,
3267
3540
  parseClassName,
3268
3541
  generateStyleKey,
@@ -3277,7 +3550,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3277
3550
  } else {
3278
3551
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3279
3552
  }
3280
- return;
3553
+ return true;
3281
3554
  }
3282
3555
  } else {
3283
3556
  if (process.env.NODE_ENV !== "production") {
@@ -3291,7 +3564,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3291
3564
  const classNameForStyle = baseClasses.join(" ");
3292
3565
  if (!classNameForStyle) {
3293
3566
  path2.remove();
3294
- return;
3567
+ return true;
3295
3568
  }
3296
3569
  const styleObject = parseClassName(classNameForStyle, state.customTheme);
3297
3570
  const styleKey = generateStyleKey(classNameForStyle);
@@ -3302,13 +3575,23 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3302
3575
  } else {
3303
3576
  replaceWithStyleAttribute(path2, styleKey, targetStyleProp, state.stylesIdentifier, t);
3304
3577
  }
3305
- return;
3578
+ return true;
3579
+ };
3580
+ if (t.isStringLiteral(value)) {
3581
+ if (processStaticClassName(value.value)) {
3582
+ return;
3583
+ }
3306
3584
  }
3307
3585
  if (t.isJSXExpressionContainer(value)) {
3308
3586
  const expression = value.expression;
3309
3587
  if (t.isJSXEmptyExpression(expression)) {
3310
3588
  return;
3311
3589
  }
3590
+ if (t.isStringLiteral(expression)) {
3591
+ if (processStaticClassName(expression.value)) {
3592
+ return;
3593
+ }
3594
+ }
3312
3595
  try {
3313
3596
  const componentScope = findComponentScope(path2, t);
3314
3597
  const result = processDynamicExpression(