@mgcrea/react-native-tailwind 0.8.1 → 0.9.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 (71) hide show
  1. package/README.md +152 -0
  2. package/dist/babel/config-loader.ts +2 -0
  3. package/dist/babel/index.cjs +205 -17
  4. package/dist/babel/plugin.d.ts +4 -1
  5. package/dist/babel/plugin.test.ts +327 -0
  6. package/dist/babel/plugin.ts +194 -14
  7. package/dist/babel/utils/platformModifierProcessing.d.ts +30 -0
  8. package/dist/babel/utils/platformModifierProcessing.ts +80 -0
  9. package/dist/babel/utils/styleInjection.d.ts +5 -1
  10. package/dist/babel/utils/styleInjection.ts +52 -7
  11. package/dist/babel/utils/styleTransforms.ts +1 -0
  12. package/dist/parser/aspectRatio.js +1 -1
  13. package/dist/parser/aspectRatio.test.js +1 -1
  14. package/dist/parser/index.d.ts +2 -2
  15. package/dist/parser/index.js +1 -1
  16. package/dist/parser/modifiers.d.ts +20 -2
  17. package/dist/parser/modifiers.js +1 -1
  18. package/dist/parser/spacing.d.ts +1 -1
  19. package/dist/parser/spacing.js +1 -1
  20. package/dist/parser/spacing.test.js +1 -1
  21. package/dist/runtime.cjs +1 -1
  22. package/dist/runtime.cjs.map +4 -4
  23. package/dist/runtime.js +1 -1
  24. package/dist/runtime.js.map +4 -4
  25. package/dist/runtime.test.js +1 -1
  26. package/dist/stubs/tw.test.js +1 -0
  27. package/package.json +7 -7
  28. package/src/babel/config-loader.ts +2 -0
  29. package/src/babel/plugin.test.ts +327 -0
  30. package/src/babel/plugin.ts +194 -14
  31. package/src/babel/utils/platformModifierProcessing.ts +80 -0
  32. package/src/babel/utils/styleInjection.ts +52 -7
  33. package/src/babel/utils/styleTransforms.ts +1 -0
  34. package/src/parser/aspectRatio.test.ts +25 -2
  35. package/src/parser/aspectRatio.ts +4 -3
  36. package/src/parser/borders.ts +2 -0
  37. package/src/parser/colors.ts +2 -0
  38. package/src/parser/index.ts +9 -2
  39. package/src/parser/layout.ts +2 -0
  40. package/src/parser/modifiers.ts +38 -4
  41. package/src/parser/placeholder.ts +1 -0
  42. package/src/parser/sizing.ts +1 -0
  43. package/src/parser/spacing.test.ts +63 -0
  44. package/src/parser/spacing.ts +11 -6
  45. package/src/parser/transforms.ts +5 -0
  46. package/src/parser/typography.ts +2 -0
  47. package/src/runtime.test.ts +27 -0
  48. package/src/runtime.ts +2 -1
  49. package/src/stubs/tw.test.ts +27 -0
  50. package/dist/babel/index.test.ts +0 -481
  51. package/dist/config/palettes.d.ts +0 -302
  52. package/dist/config/palettes.js +0 -1
  53. package/dist/parser/__snapshots__/aspectRatio.test.js.snap +0 -9
  54. package/dist/parser/__snapshots__/borders.test.js.snap +0 -23
  55. package/dist/parser/__snapshots__/colors.test.js.snap +0 -251
  56. package/dist/parser/__snapshots__/shadows.test.js.snap +0 -76
  57. package/dist/parser/__snapshots__/sizing.test.js.snap +0 -61
  58. package/dist/parser/__snapshots__/spacing.test.js.snap +0 -40
  59. package/dist/parser/__snapshots__/transforms.test.js.snap +0 -58
  60. package/dist/parser/__snapshots__/typography.test.js.snap +0 -30
  61. package/dist/parser/aspectRatio.test.d.ts +0 -1
  62. package/dist/parser/borders.test.d.ts +0 -1
  63. package/dist/parser/colors.test.d.ts +0 -1
  64. package/dist/parser/layout.test.d.ts +0 -1
  65. package/dist/parser/modifiers.test.d.ts +0 -1
  66. package/dist/parser/shadows.test.d.ts +0 -1
  67. package/dist/parser/sizing.test.d.ts +0 -1
  68. package/dist/parser/spacing.test.d.ts +0 -1
  69. package/dist/parser/typography.test.d.ts +0 -1
  70. package/dist/types.d.ts +0 -42
  71. package/dist/types.js +0 -1
package/README.md CHANGED
@@ -45,6 +45,7 @@ Compile-time Tailwind CSS for React Native with zero runtime overhead. Transform
45
45
  - 🔀 **Dynamic className** — Conditional styles with hybrid compile-time optimization
46
46
  - 🏃 **Runtime option** — Optional `tw` template tag for fully dynamic styling (~25KB)
47
47
  - 🎯 **State modifiers** — `active:`, `hover:`, `focus:`, and `disabled:` modifiers for interactive components
48
+ - 📱 **Platform modifiers** — `ios:`, `android:`, and `web:` modifiers for platform-specific styling
48
49
  - 📜 **Special style props** — Support for `contentContainerClassName`, `columnWrapperClassName`, and more
49
50
  - 🎛️ **Custom attributes** — Configure which props to transform with exact matching or glob patterns
50
51
 
@@ -709,6 +710,157 @@ The enhanced `TextInput` also provides a convenient `disabled` prop that overrid
709
710
  - ✅ **Type-safe** — Full TypeScript autocomplete for all modifiers
710
711
  - ✅ **Works with custom colors** — `focus:border-primary`, `active:bg-secondary`, `disabled:bg-gray-200`, etc.
711
712
 
713
+ ### Platform Modifiers
714
+
715
+ Apply platform-specific styles using `ios:`, `android:`, and `web:` modifiers. These work on **all components** (not just enhanced ones) and compile to `Platform.select()` calls with zero runtime parsing overhead.
716
+
717
+ **Basic Example:**
718
+
719
+ ```tsx
720
+ import { View, Text } from "react-native";
721
+
722
+ export function PlatformCard() {
723
+ return (
724
+ <View className="p-4 ios:p-6 android:p-8 bg-white rounded-lg">
725
+ <Text className="text-base ios:text-blue-600 android:text-green-600">
726
+ Platform-specific styles
727
+ </Text>
728
+ </View>
729
+ );
730
+ }
731
+ ```
732
+
733
+ **Transforms to:**
734
+
735
+ ```tsx
736
+ import { Platform, StyleSheet } from "react-native";
737
+
738
+ <View
739
+ style={[
740
+ _twStyles._bg_white_p_4_rounded_lg,
741
+ Platform.select({
742
+ ios: _twStyles._ios_p_6,
743
+ android: _twStyles._android_p_8,
744
+ }),
745
+ ]}
746
+ >
747
+ <Text
748
+ style={[
749
+ _twStyles._text_base,
750
+ Platform.select({
751
+ ios: _twStyles._ios_text_blue_600,
752
+ android: _twStyles._android_text_green_600,
753
+ }),
754
+ ]}
755
+ >
756
+ Platform-specific styles
757
+ </Text>
758
+ </View>;
759
+
760
+ // Generated styles:
761
+ const _twStyles = StyleSheet.create({
762
+ _bg_white_p_4_rounded_lg: {
763
+ backgroundColor: "#FFFFFF",
764
+ padding: 16,
765
+ borderRadius: 8,
766
+ },
767
+ _ios_p_6: { padding: 24 },
768
+ _android_p_8: { padding: 32 },
769
+ _text_base: { fontSize: 16 },
770
+ _ios_text_blue_600: { color: "#2563EB" },
771
+ _android_text_green_600: { color: "#059669" },
772
+ });
773
+ ```
774
+
775
+ **Common Use Cases:**
776
+
777
+ **Platform-specific colors:**
778
+
779
+ ```tsx
780
+ // Different colors per platform for brand consistency
781
+ <View className="bg-blue-500 ios:bg-blue-600 android:bg-green-600">
782
+ <Text className="text-white">Platform-specific background</Text>
783
+ </View>
784
+ ```
785
+
786
+ **Platform-specific spacing:**
787
+
788
+ ```tsx
789
+ // More padding on Android due to larger default touch targets
790
+ <View className="p-4 ios:p-6 android:p-8">
791
+ <Text>Platform-specific padding</Text>
792
+ </View>
793
+ ```
794
+
795
+ **Combined with base styles:**
796
+
797
+ ```tsx
798
+ // Base styles + platform-specific overrides
799
+ <View className="border-2 border-gray-300 ios:border-blue-500 android:border-green-500 rounded-lg p-4">
800
+ <Text className="text-gray-800 ios:text-blue-800 android:text-green-800">
801
+ Base styles with platform overrides
802
+ </Text>
803
+ </View>
804
+ ```
805
+
806
+ **Multiple platform modifiers:**
807
+
808
+ ```tsx
809
+ // Combine multiple platform-specific styles
810
+ <View className="bg-gray-100 ios:bg-blue-50 android:bg-green-50 p-4 ios:p-6 android:p-8 rounded-lg">
811
+ <Text>Multiple platform styles</Text>
812
+ </View>
813
+ ```
814
+
815
+ **Web platform support:**
816
+
817
+ ```tsx
818
+ // Different styles for React Native Web
819
+ <View className="p-4 ios:p-6 android:p-8 web:p-2">
820
+ <Text className="text-base web:text-lg">Cross-platform styling</Text>
821
+ </View>
822
+ ```
823
+
824
+ **Mixing with state modifiers:**
825
+
826
+ ```tsx
827
+ import { Pressable } from "@mgcrea/react-native-tailwind";
828
+
829
+ // Platform modifiers work alongside state modifiers
830
+ <Pressable className="bg-blue-500 active:bg-blue-700 ios:border-2 android:border-0 p-4 rounded-lg">
831
+ <Text className="text-white">Button with platform + state modifiers</Text>
832
+ </Pressable>;
833
+ ```
834
+
835
+ **Key Features:**
836
+
837
+ - ✅ **Works on all components** — No need for enhanced components (unlike state modifiers)
838
+ - ✅ **Zero runtime overhead** — All parsing happens at compile-time
839
+ - ✅ **Native Platform API** — Uses React Native's `Platform.select()` under the hood
840
+ - ✅ **Type-safe** — Full TypeScript autocomplete for platform modifiers
841
+ - ✅ **Optimized** — Styles deduplicated via `StyleSheet.create`
842
+ - ✅ **Works with custom colors** — `ios:bg-primary`, `android:bg-secondary`, etc.
843
+ - ✅ **Minimal runtime cost** — Only one `Platform.select()` call per element with platform modifiers
844
+
845
+ **Supported Platforms:**
846
+
847
+ | Modifier | Platform | Description |
848
+ | -------- | -------------- | ----------------------------- |
849
+ | `ios:` | iOS | Styles specific to iOS |
850
+ | `android:` | Android | Styles specific to Android |
851
+ | `web:` | React Native Web | Styles for web platform |
852
+
853
+ **How it works:**
854
+
855
+ The Babel plugin:
856
+ 1. Detects platform modifiers during compilation
857
+ 2. Parses all platform-specific classes at compile-time
858
+ 3. Generates `Platform.select()` expressions with references to pre-compiled styles
859
+ 4. Auto-imports `Platform` from `react-native` when needed
860
+ 5. Merges platform styles with base classes and other modifiers in style arrays
861
+
862
+ This approach provides the best of both worlds: compile-time optimization for all styles, with minimal runtime platform detection only for the conditional selection logic.
863
+
712
864
  ### ScrollView Content Container
713
865
 
714
866
  Use `contentContainerClassName` to style the ScrollView's content container:
@@ -72,6 +72,7 @@ export function loadTailwindConfig(configPath: string): TailwindConfig | null {
72
72
  configCache.set(configPath, resolved);
73
73
  return resolved;
74
74
  } catch (error) {
75
+ /* v8 ignore next 3 */
75
76
  if (process.env.NODE_ENV !== "production") {
76
77
  console.warn(`[react-native-tailwind] Failed to load config from ${configPath}:`, error);
77
78
  }
@@ -98,6 +99,7 @@ export function extractCustomColors(filename: string): Record<string, string> {
98
99
  }
99
100
 
100
101
  // Warn if using theme.colors instead of theme.extend.colors
102
+ /* v8 ignore next 5 */
101
103
  if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
102
104
  console.warn(
103
105
  "[react-native-tailwind] Using theme.colors will override all default colors. " +
@@ -65,7 +65,7 @@ function parseAspectRatio(cls) {
65
65
  if (cls in ASPECT_RATIO_PRESETS) {
66
66
  const aspectRatio2 = ASPECT_RATIO_PRESETS[cls];
67
67
  if (aspectRatio2 === void 0) {
68
- return {};
68
+ return { aspectRatio: void 0 };
69
69
  }
70
70
  return { aspectRatio: aspectRatio2 };
71
71
  }
@@ -1209,16 +1209,19 @@ function parseArbitrarySpacing(value) {
1209
1209
  return null;
1210
1210
  }
1211
1211
  function parseSpacing(cls) {
1212
- const marginMatch = cls.match(/^m([xytrbls]?)-(.+)$/);
1212
+ const marginMatch = cls.match(/^(-?)m([xytrbls]?)-(.+)$/);
1213
1213
  if (marginMatch) {
1214
- const [, dir, valueStr] = marginMatch;
1214
+ const [, negativePrefix, dir, valueStr] = marginMatch;
1215
+ const isNegative = negativePrefix === "-";
1215
1216
  const arbitraryValue = parseArbitrarySpacing(valueStr);
1216
1217
  if (arbitraryValue !== null) {
1217
- return getMarginStyle(dir, arbitraryValue);
1218
+ const finalValue = isNegative ? -arbitraryValue : arbitraryValue;
1219
+ return getMarginStyle(dir, finalValue);
1218
1220
  }
1219
1221
  const scaleValue = SPACING_SCALE[valueStr];
1220
1222
  if (scaleValue !== void 0) {
1221
- return getMarginStyle(dir, scaleValue);
1223
+ const finalValue = isNegative ? -scaleValue : scaleValue;
1224
+ return getMarginStyle(dir, finalValue);
1222
1225
  }
1223
1226
  }
1224
1227
  const paddingMatch = cls.match(/^p([xytrbls]?)-(.+)$/);
@@ -1730,13 +1733,15 @@ function parsePlaceholderClasses(classes, customColors) {
1730
1733
  }
1731
1734
 
1732
1735
  // src/parser/modifiers.ts
1733
- var SUPPORTED_MODIFIERS = [
1736
+ var STATE_MODIFIERS = [
1734
1737
  "active",
1735
1738
  "hover",
1736
1739
  "focus",
1737
1740
  "disabled",
1738
1741
  "placeholder"
1739
1742
  ];
1743
+ var PLATFORM_MODIFIERS = ["ios", "android", "web"];
1744
+ var SUPPORTED_MODIFIERS = [...STATE_MODIFIERS, ...PLATFORM_MODIFIERS];
1740
1745
  function parseModifier(cls) {
1741
1746
  const colonIndex = cls.indexOf(":");
1742
1747
  if (colonIndex === -1) {
@@ -1758,6 +1763,12 @@ function parseModifier(cls) {
1758
1763
  baseClass
1759
1764
  };
1760
1765
  }
1766
+ function isStateModifier(modifier) {
1767
+ return STATE_MODIFIERS.includes(modifier);
1768
+ }
1769
+ function isPlatformModifier(modifier) {
1770
+ return PLATFORM_MODIFIERS.includes(modifier);
1771
+ }
1761
1772
  function splitModifierClasses(className) {
1762
1773
  const classes = className.trim().split(/\s+/).filter(Boolean);
1763
1774
  const baseClasses = [];
@@ -2118,13 +2129,74 @@ function createStyleFunction(styleExpression, modifierTypes, t) {
2118
2129
  return t.arrowFunctionExpression([param], styleExpression);
2119
2130
  }
2120
2131
 
2132
+ // src/babel/utils/platformModifierProcessing.ts
2133
+ function processPlatformModifiers(platformModifiers, state, parseClassName2, generateStyleKey2, t) {
2134
+ state.needsPlatformImport = true;
2135
+ const modifiersByPlatform = /* @__PURE__ */ new Map();
2136
+ for (const mod of platformModifiers) {
2137
+ const platform = mod.modifier;
2138
+ if (!modifiersByPlatform.has(platform)) {
2139
+ modifiersByPlatform.set(platform, []);
2140
+ }
2141
+ const platformGroup = modifiersByPlatform.get(platform);
2142
+ if (platformGroup) {
2143
+ platformGroup.push(mod);
2144
+ }
2145
+ }
2146
+ const selectProperties = [];
2147
+ for (const [platform, modifiers] of modifiersByPlatform) {
2148
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2149
+ const styleObject = parseClassName2(classNames, state.customColors);
2150
+ const styleKey = generateStyleKey2(`${platform}_${classNames}`);
2151
+ state.styleRegistry.set(styleKey, styleObject);
2152
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2153
+ selectProperties.push(t.objectProperty(t.identifier(platform), styleReference));
2154
+ }
2155
+ return t.callExpression(t.memberExpression(t.identifier("Platform"), t.identifier("select")), [
2156
+ t.objectExpression(selectProperties)
2157
+ ]);
2158
+ }
2159
+
2121
2160
  // src/babel/utils/styleInjection.ts
2122
2161
  function addStyleSheetImport(path2, t) {
2123
- const importDeclaration = t.importDeclaration(
2124
- [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
2125
- t.stringLiteral("react-native")
2126
- );
2127
- path2.unshiftContainer("body", importDeclaration);
2162
+ const body = path2.node.body;
2163
+ let reactNativeImport = null;
2164
+ for (const statement of body) {
2165
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2166
+ reactNativeImport = statement;
2167
+ break;
2168
+ }
2169
+ }
2170
+ if (reactNativeImport) {
2171
+ reactNativeImport.specifiers.push(
2172
+ t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))
2173
+ );
2174
+ } else {
2175
+ const importDeclaration = t.importDeclaration(
2176
+ [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
2177
+ t.stringLiteral("react-native")
2178
+ );
2179
+ path2.unshiftContainer("body", importDeclaration);
2180
+ }
2181
+ }
2182
+ function addPlatformImport(path2, t) {
2183
+ const body = path2.node.body;
2184
+ let reactNativeImport = null;
2185
+ for (const statement of body) {
2186
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2187
+ reactNativeImport = statement;
2188
+ break;
2189
+ }
2190
+ }
2191
+ if (reactNativeImport) {
2192
+ reactNativeImport.specifiers.push(t.importSpecifier(t.identifier("Platform"), t.identifier("Platform")));
2193
+ } else {
2194
+ const importDeclaration = t.importDeclaration(
2195
+ [t.importSpecifier(t.identifier("Platform"), t.identifier("Platform"))],
2196
+ t.stringLiteral("react-native")
2197
+ );
2198
+ path2.unshiftContainer("body", importDeclaration);
2199
+ }
2128
2200
  }
2129
2201
  function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
2130
2202
  const styleProperties = [];
@@ -2355,6 +2427,8 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2355
2427
  state.styleRegistry = /* @__PURE__ */ new Map();
2356
2428
  state.hasClassNames = false;
2357
2429
  state.hasStyleSheetImport = false;
2430
+ state.hasPlatformImport = false;
2431
+ state.needsPlatformImport = false;
2358
2432
  state.supportedAttributes = exactMatches;
2359
2433
  state.attributePatterns = patterns;
2360
2434
  state.stylesIdentifier = stylesIdentifier;
@@ -2372,10 +2446,13 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2372
2446
  if (!state.hasStyleSheetImport) {
2373
2447
  addStyleSheetImport(path2, t);
2374
2448
  }
2449
+ if (state.needsPlatformImport && !state.hasPlatformImport) {
2450
+ addPlatformImport(path2, t);
2451
+ }
2375
2452
  injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
2376
2453
  }
2377
2454
  },
2378
- // Check if StyleSheet is already imported and track tw/twStyle imports
2455
+ // Check if StyleSheet/Platform are already imported and track tw/twStyle imports
2379
2456
  ImportDeclaration(path2, state) {
2380
2457
  const node = path2.node;
2381
2458
  if (node.source.value === "react-native") {
@@ -2386,12 +2463,19 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2386
2463
  }
2387
2464
  return false;
2388
2465
  });
2466
+ const hasPlatform = specifiers.some((spec) => {
2467
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
2468
+ return spec.imported.name === "Platform";
2469
+ }
2470
+ return false;
2471
+ });
2389
2472
  if (hasStyleSheet) {
2390
2473
  state.hasStyleSheetImport = true;
2391
- } else {
2392
- node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
2393
- state.hasStyleSheetImport = true;
2394
2474
  }
2475
+ if (hasPlatform) {
2476
+ state.hasPlatformImport = true;
2477
+ }
2478
+ state.reactNativeImportPath = path2;
2395
2479
  }
2396
2480
  if (node.source.value === "@mgcrea/react-native-tailwind") {
2397
2481
  const specifiers = node.specifiers;
@@ -2494,7 +2578,10 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2494
2578
  state.hasClassNames = true;
2495
2579
  const { baseClasses, modifierClasses } = splitModifierClasses(className);
2496
2580
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
2497
- const stateModifiers = modifierClasses.filter((m) => m.modifier !== "placeholder");
2581
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
2582
+ const stateModifiers = modifierClasses.filter(
2583
+ (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
2584
+ );
2498
2585
  if (placeholderModifiers.length > 0) {
2499
2586
  const jsxOpeningElement = path2.parent;
2500
2587
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
@@ -2512,7 +2599,108 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2512
2599
  }
2513
2600
  }
2514
2601
  }
2515
- if (stateModifiers.length > 0) {
2602
+ const hasPlatformModifiers = platformModifiers.length > 0;
2603
+ const hasStateModifiers = stateModifiers.length > 0;
2604
+ const hasBaseClasses = baseClasses.length > 0;
2605
+ if (hasStateModifiers && hasPlatformModifiers) {
2606
+ const jsxOpeningElement = path2.parent;
2607
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2608
+ if (componentSupport) {
2609
+ const styleArrayElements = [];
2610
+ if (hasBaseClasses) {
2611
+ const baseClassName = baseClasses.join(" ");
2612
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
2613
+ const baseStyleKey = generateStyleKey(baseClassName);
2614
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2615
+ styleArrayElements.push(
2616
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2617
+ );
2618
+ }
2619
+ const platformSelectExpression = processPlatformModifiers(
2620
+ platformModifiers,
2621
+ state,
2622
+ parseClassName,
2623
+ generateStyleKey,
2624
+ t
2625
+ );
2626
+ styleArrayElements.push(platformSelectExpression);
2627
+ const modifiersByType = /* @__PURE__ */ new Map();
2628
+ for (const mod of stateModifiers) {
2629
+ const modType = mod.modifier;
2630
+ if (!modifiersByType.has(modType)) {
2631
+ modifiersByType.set(modType, []);
2632
+ }
2633
+ modifiersByType.get(modType)?.push(mod);
2634
+ }
2635
+ for (const [modifierType, modifiers] of modifiersByType) {
2636
+ if (!componentSupport.supportedModifiers.includes(modifierType)) {
2637
+ continue;
2638
+ }
2639
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
2640
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customColors);
2641
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
2642
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
2643
+ const stateProperty = getStatePropertyForModifier(modifierType);
2644
+ const conditionalExpression = t.logicalExpression(
2645
+ "&&",
2646
+ t.identifier(stateProperty),
2647
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey))
2648
+ );
2649
+ styleArrayElements.push(conditionalExpression);
2650
+ }
2651
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter(
2652
+ (mod) => componentSupport.supportedModifiers.includes(mod)
2653
+ );
2654
+ const styleArrayExpression = t.arrayExpression(styleArrayElements);
2655
+ const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
2656
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
2657
+ if (styleAttribute2) {
2658
+ mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
2659
+ } else {
2660
+ replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
2661
+ }
2662
+ return;
2663
+ } else {
2664
+ }
2665
+ }
2666
+ if (hasPlatformModifiers && !hasStateModifiers) {
2667
+ const styleExpressions = [];
2668
+ if (hasBaseClasses) {
2669
+ const baseClassName = baseClasses.join(" ");
2670
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
2671
+ const baseStyleKey = generateStyleKey(baseClassName);
2672
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2673
+ styleExpressions.push(
2674
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2675
+ );
2676
+ }
2677
+ const platformSelectExpression = processPlatformModifiers(
2678
+ platformModifiers,
2679
+ state,
2680
+ parseClassName,
2681
+ generateStyleKey,
2682
+ t
2683
+ );
2684
+ styleExpressions.push(platformSelectExpression);
2685
+ const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
2686
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
2687
+ if (styleAttribute2) {
2688
+ const existingStyle = styleAttribute2.value;
2689
+ if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
2690
+ const existing = existingStyle.expression;
2691
+ const mergedArray = t.isArrayExpression(existing) ? t.arrayExpression([styleExpression, ...existing.elements]) : t.arrayExpression([styleExpression, existing]);
2692
+ styleAttribute2.value = t.jsxExpressionContainer(mergedArray);
2693
+ } else {
2694
+ styleAttribute2.value = t.jsxExpressionContainer(styleExpression);
2695
+ }
2696
+ path2.remove();
2697
+ } else {
2698
+ path2.node.name = t.jsxIdentifier(targetStyleProp);
2699
+ path2.node.value = t.jsxExpressionContainer(styleExpression);
2700
+ }
2701
+ return;
2702
+ }
2703
+ if (hasStateModifiers) {
2516
2704
  const jsxOpeningElement = path2.parent;
2517
2705
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2518
2706
  if (componentSupport) {
@@ -2,7 +2,7 @@
2
2
  * Babel plugin for react-native-tailwind
3
3
  * Transforms className props to style props at compile time
4
4
  */
5
- import type { PluginObj, PluginPass } from "@babel/core";
5
+ import type { NodePath, PluginObj, PluginPass } from "@babel/core";
6
6
  import * as BabelTypes from "@babel/types";
7
7
  import type { StyleObject } from "../types/core.js";
8
8
  /**
@@ -29,12 +29,15 @@ type PluginState = PluginPass & {
29
29
  styleRegistry: Map<string, StyleObject>;
30
30
  hasClassNames: boolean;
31
31
  hasStyleSheetImport: boolean;
32
+ hasPlatformImport: boolean;
33
+ needsPlatformImport: boolean;
32
34
  customColors: Record<string, string>;
33
35
  supportedAttributes: Set<string>;
34
36
  attributePatterns: RegExp[];
35
37
  stylesIdentifier: string;
36
38
  twImportNames: Set<string>;
37
39
  hasTwImport: boolean;
40
+ reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
38
41
  };
39
42
  export default function reactNativeTailwindBabelPlugin({ types: t }: {
40
43
  types: typeof BabelTypes;