@mgcrea/react-native-tailwind 0.13.0 → 0.14.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 (53) hide show
  1. package/README.md +21 -22
  2. package/dist/babel/index.cjs +342 -17
  3. package/dist/babel/plugin/state.d.ts +4 -0
  4. package/dist/babel/plugin/state.ts +8 -0
  5. package/dist/babel/plugin/visitors/className.test.ts +313 -0
  6. package/dist/babel/plugin/visitors/className.ts +36 -8
  7. package/dist/babel/plugin/visitors/imports.ts +16 -1
  8. package/dist/babel/plugin/visitors/program.ts +19 -2
  9. package/dist/babel/plugin/visitors/tw.test.ts +151 -0
  10. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  11. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  12. package/dist/babel/utils/styleInjection.d.ts +16 -0
  13. package/dist/babel/utils/styleInjection.ts +138 -7
  14. package/dist/babel/utils/twProcessing.d.ts +2 -0
  15. package/dist/babel/utils/twProcessing.ts +92 -3
  16. package/dist/parser/borders.js +1 -1
  17. package/dist/parser/borders.test.js +1 -1
  18. package/dist/parser/index.d.ts +2 -2
  19. package/dist/parser/index.js +1 -1
  20. package/dist/parser/layout.js +1 -1
  21. package/dist/parser/layout.test.js +1 -1
  22. package/dist/parser/modifiers.d.ts +32 -2
  23. package/dist/parser/modifiers.js +1 -1
  24. package/dist/parser/modifiers.test.js +1 -1
  25. package/dist/parser/spacing.d.ts +1 -1
  26. package/dist/parser/spacing.js +1 -1
  27. package/dist/parser/spacing.test.js +1 -1
  28. package/dist/parser/typography.test.js +1 -1
  29. package/dist/runtime.cjs +1 -1
  30. package/dist/runtime.cjs.map +3 -3
  31. package/dist/runtime.js +1 -1
  32. package/dist/runtime.js.map +3 -3
  33. package/package.json +6 -6
  34. package/src/babel/plugin/state.ts +8 -0
  35. package/src/babel/plugin/visitors/className.test.ts +313 -0
  36. package/src/babel/plugin/visitors/className.ts +36 -8
  37. package/src/babel/plugin/visitors/imports.ts +16 -1
  38. package/src/babel/plugin/visitors/program.ts +19 -2
  39. package/src/babel/plugin/visitors/tw.test.ts +151 -0
  40. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  41. package/src/babel/utils/styleInjection.ts +138 -7
  42. package/src/babel/utils/twProcessing.ts +92 -3
  43. package/src/parser/borders.test.ts +104 -0
  44. package/src/parser/borders.ts +50 -7
  45. package/src/parser/index.ts +2 -0
  46. package/src/parser/layout.test.ts +74 -0
  47. package/src/parser/layout.ts +94 -0
  48. package/src/parser/modifiers.test.ts +206 -0
  49. package/src/parser/modifiers.ts +62 -3
  50. package/src/parser/spacing.test.ts +66 -0
  51. package/src/parser/spacing.ts +15 -5
  52. package/src/parser/typography.test.ts +8 -0
  53. package/src/parser/typography.ts +4 -0
package/README.md CHANGED
@@ -27,7 +27,7 @@
27
27
  <img src="https://img.shields.io/codecov/c/github/mgcrea/react-native-tailwind?style=for-the-badge" alt="coverage" />
28
28
  </a>
29
29
  <a href="https://depfu.com/github/mgcrea/react-native-tailwind">
30
- <img src="https://img.shields.io/depfu/dependencies/github/mgcrea/react-native-tailwind?style=for-the-badge" alt="dependencies status" />
30
+ <img src="https://img.shields.io/badge/dependencies-none-brightgreen?style=for-the-badge" alt="dependencies status" />
31
31
  </a>
32
32
  </p>
33
33
 
@@ -37,22 +37,21 @@ Compile-time Tailwind CSS for React Native with zero runtime overhead. Transform
37
37
 
38
38
  ## Features
39
39
 
40
- - **Zero runtime overhead** All transformations happen at compile time
41
- - 🔧 **No dependencies** Direct-to-React-Native style generation without tailwindcss package
42
- - 🎯 **Babel-only setup** No Metro configuration required
43
- - 📝 **TypeScript-first** Full type safety and autocomplete support
44
- - 🚀 **Optimized performance** Compiles down to `StyleSheet.create` for optimal performance
45
- - 📦 **Small bundle size** Only includes actual styles used in your app
46
- - 🎨 **Custom colors** Extend the default palette via `tailwind.config.*`
47
- - 📐 **Arbitrary values** Use custom sizes and borders: `w-[123px]`, `rounded-[20px]`
48
- - 🔀 **Dynamic className** Conditional styles with hybrid compile-time optimization
49
- - 🏃 **Runtime option** Optional `tw` template tag for fully dynamic styling (~25KB)
50
- - 🎯 **State modifiers** `active:`, `hover:`, `focus:`, and `disabled:` modifiers for interactive components
51
- - 📱 **Platform modifiers** `ios:`, `android:`, and `web:` modifiers for platform-specific styling
52
- - 🌓 **Color scheme modifiers** `dark:` and `light:` modifiers for automatic theme adaptation
53
- - 🎨 **Scheme modifier** — `scheme:` convenience modifier that expands to both `dark:` and `light:` variants
54
- - 📜 **Special style props** Support for `contentContainerClassName`, `columnWrapperClassName`, and more
55
- - 🎛️ **Custom attributes** — Configure which props to transform with exact matching or glob patterns
40
+ - **⚡ Zero Runtime Overhead** - All transformations happen at compile time
41
+ - **🔧 No Dependencies** - Direct-to-React-Native style generation without tailwindcss package
42
+ - **🎯 Babel-only Setup** - No Metro configuration required
43
+ - **📝 TypeScript-first** - Full type safety and autocomplete support
44
+ - **🚀 Optimized Performance** - Compiles down to StyleSheet.create for optimal performance
45
+ - **🔀 Dynamic className** - Conditional styles support with compile-time optimization
46
+ - **📦 Small Bundle Size** - Only includes actual styles used in your app
47
+ - **🎯 State Modifiers** - `active:`, `hover:`, `focus:`, and `disabled:` modifiers for interactive components
48
+ - **📱 Platform Modifiers** - `ios:`, `android:`, and `web:` modifiers for platform-specific styling
49
+ - **🌓 Color Scheme Modifiers** - `dark:` and `light:` and `scheme:` modifiers for automatic theme adaptation
50
+ - **🎨 Custom Colors** - Extend the default palette via tailwind.config.\*
51
+ - **📐 Arbitrary Values** - Use custom sizes and borders: `w-[123px]`, `rounded-[20px]`
52
+ - **📜 Special Style Props** - Support for `contentContainerClassName`, `columnWrapperClassName`, and more
53
+
54
+ 📊 **[How It Compares](https://mgcrea.github.io/react-native-tailwind/getting-started/how-it-compares/)** - See how this library stacks up against other React Native styling solutions.
56
55
 
57
56
  ## Demo
58
57
 
@@ -200,9 +199,7 @@ import { View, Text } from "react-native";
200
199
  export function PlatformCard() {
201
200
  return (
202
201
  <View className="p-4 ios:p-6 android:p-8 bg-white rounded-lg">
203
- <Text className="text-base ios:text-blue-600 android:text-green-600">
204
- Platform-specific styles
205
- </Text>
202
+ <Text className="text-base ios:text-blue-600 android:text-green-600">Platform-specific styles</Text>
206
203
  </View>
207
204
  );
208
205
  }
@@ -212,12 +209,14 @@ export function PlatformCard() {
212
209
 
213
210
  Contributions are welcome! Please read our [Contributing Guide](https://mgcrea.github.io/react-native-tailwind/advanced/contributing/) for details.
214
211
 
212
+ ## Credits
213
+
214
+ - [Tailwind CSS](https://tailwindcss.com/) - The utility-first CSS framework that revolutionized the way we style applications. If you enjoy this library, consider supporting them by purchasing [Tailwind Plus](https://tailwindcss.com/plus).
215
+
215
216
  ## Authors
216
217
 
217
218
  - [Olivier Louvignes](https://github.com/mgcrea) - [@mgcrea](https://twitter.com/mgcrea)
218
219
 
219
- ## License
220
-
221
220
  ```text
222
221
  MIT License
223
222
 
@@ -212,6 +212,10 @@ function createInitialState(options, filename, colorSchemeImportSource, colorSch
212
212
  needsWindowDimensionsImport: false,
213
213
  windowDimensionsVariableName: "_twDimensions",
214
214
  windowDimensionsLocalIdentifier: void 0,
215
+ hasI18nManagerImport: false,
216
+ needsI18nManagerImport: false,
217
+ i18nManagerVariableName: "_twIsRTL",
218
+ i18nManagerLocalIdentifier: void 0,
215
219
  customTheme,
216
220
  schemeModifierConfig,
217
221
  supportedAttributes: exactMatches,
@@ -715,7 +719,9 @@ var BORDER_WIDTH_PROP_MAP = {
715
719
  t: "borderTopWidth",
716
720
  r: "borderRightWidth",
717
721
  b: "borderBottomWidth",
718
- l: "borderLeftWidth"
722
+ l: "borderLeftWidth",
723
+ s: "borderStartWidth",
724
+ e: "borderEndWidth"
719
725
  };
720
726
  var BORDER_RADIUS_CORNER_MAP = {
721
727
  tl: "borderTopLeftRadius",
@@ -723,11 +729,19 @@ var BORDER_RADIUS_CORNER_MAP = {
723
729
  bl: "borderBottomLeftRadius",
724
730
  br: "borderBottomRightRadius"
725
731
  };
732
+ var BORDER_RADIUS_LOGICAL_CORNER_MAP = {
733
+ ss: "borderTopStartRadius",
734
+ se: "borderTopEndRadius",
735
+ es: "borderBottomStartRadius",
736
+ ee: "borderBottomEndRadius"
737
+ };
726
738
  var BORDER_RADIUS_SIDE_MAP = {
727
739
  t: ["borderTopLeftRadius", "borderTopRightRadius"],
728
740
  r: ["borderTopRightRadius", "borderBottomRightRadius"],
729
741
  b: ["borderBottomLeftRadius", "borderBottomRightRadius"],
730
- l: ["borderTopLeftRadius", "borderBottomLeftRadius"]
742
+ l: ["borderTopLeftRadius", "borderBottomLeftRadius"],
743
+ s: ["borderTopStartRadius", "borderBottomStartRadius"],
744
+ e: ["borderTopEndRadius", "borderBottomEndRadius"]
731
745
  };
732
746
  function parseArbitraryBorderWidth(value) {
733
747
  const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
@@ -775,11 +789,11 @@ function parseBorder(cls, customColors) {
775
789
  return null;
776
790
  }
777
791
  function parseBorderWidth(cls, customColors) {
778
- const dirMatch = cls.match(/^border-([trbl])(?:-(.+))?$/);
792
+ const dirMatch = cls.match(/^border-([trblse])(?:-(.+))?$/);
779
793
  if (dirMatch) {
780
794
  const dir = dirMatch[1];
781
795
  const valueStr = dirMatch[2] || "";
782
- if (valueStr) {
796
+ if (valueStr && dir !== "s" && dir !== "e") {
783
797
  const colorResult = parseColor(cls, customColors);
784
798
  if (colorResult !== null) {
785
799
  return null;
@@ -843,7 +857,24 @@ function parseBorderRadius(cls) {
843
857
  }
844
858
  return null;
845
859
  }
846
- const sideMatch = rest.match(/^([trbl])(?:-(.+))?$/);
860
+ const logicalCornerMatch = rest.match(/^(ss|se|es|ee)(?:-(.+))?$/);
861
+ if (logicalCornerMatch) {
862
+ const corner = logicalCornerMatch[1];
863
+ const valueStr = logicalCornerMatch[2] || "";
864
+ if (valueStr.startsWith("[")) {
865
+ const arbitraryValue = parseArbitraryBorderRadius(valueStr);
866
+ if (arbitraryValue !== null) {
867
+ return { [BORDER_RADIUS_LOGICAL_CORNER_MAP[corner]]: arbitraryValue };
868
+ }
869
+ return null;
870
+ }
871
+ const scaleValue2 = BORDER_RADIUS_SCALE[valueStr];
872
+ if (scaleValue2 !== void 0) {
873
+ return { [BORDER_RADIUS_LOGICAL_CORNER_MAP[corner]]: scaleValue2 };
874
+ }
875
+ return null;
876
+ }
877
+ const sideMatch = rest.match(/^([trblse])(?:-(.+))?$/);
847
878
  if (sideMatch) {
848
879
  const side = sideMatch[1];
849
880
  const valueStr = sideMatch[2] || "";
@@ -1120,6 +1151,52 @@ function parseLayout(cls) {
1120
1151
  return { left: leftValue };
1121
1152
  }
1122
1153
  }
1154
+ const startMatch = cls.match(/^(-?)start-(.+)$/);
1155
+ if (startMatch) {
1156
+ const [, negPrefix, startKey] = startMatch;
1157
+ const isNegative = negPrefix === "-";
1158
+ if (startKey === "auto") {
1159
+ return {};
1160
+ }
1161
+ const arbitraryStart = parseArbitraryInset(startKey);
1162
+ if (arbitraryStart !== null) {
1163
+ if (typeof arbitraryStart === "number") {
1164
+ return { start: isNegative ? -arbitraryStart : arbitraryStart };
1165
+ }
1166
+ if (isNegative && arbitraryStart.endsWith("%")) {
1167
+ const numValue = parseFloat(arbitraryStart);
1168
+ return { start: `${-numValue}%` };
1169
+ }
1170
+ return { start: arbitraryStart };
1171
+ }
1172
+ const startValue = INSET_SCALE[startKey];
1173
+ if (startValue !== void 0) {
1174
+ return { start: isNegative ? -startValue : startValue };
1175
+ }
1176
+ }
1177
+ const endMatch = cls.match(/^(-?)end-(.+)$/);
1178
+ if (endMatch) {
1179
+ const [, negPrefix, endKey] = endMatch;
1180
+ const isNegative = negPrefix === "-";
1181
+ if (endKey === "auto") {
1182
+ return {};
1183
+ }
1184
+ const arbitraryEnd = parseArbitraryInset(endKey);
1185
+ if (arbitraryEnd !== null) {
1186
+ if (typeof arbitraryEnd === "number") {
1187
+ return { end: isNegative ? -arbitraryEnd : arbitraryEnd };
1188
+ }
1189
+ if (isNegative && arbitraryEnd.endsWith("%")) {
1190
+ const numValue = parseFloat(arbitraryEnd);
1191
+ return { end: `${-numValue}%` };
1192
+ }
1193
+ return { end: arbitraryEnd };
1194
+ }
1195
+ const endValue = INSET_SCALE[endKey];
1196
+ if (endValue !== void 0) {
1197
+ return { end: isNegative ? -endValue : endValue };
1198
+ }
1199
+ }
1123
1200
  if (cls.startsWith("inset-x-")) {
1124
1201
  const insetKey = cls.substring(8);
1125
1202
  const arbitraryInset = parseArbitraryInset(insetKey);
@@ -1142,6 +1219,28 @@ function parseLayout(cls) {
1142
1219
  return { top: insetValue, bottom: insetValue };
1143
1220
  }
1144
1221
  }
1222
+ if (cls.startsWith("inset-s-")) {
1223
+ const insetKey = cls.substring(8);
1224
+ const arbitraryInset = parseArbitraryInset(insetKey);
1225
+ if (arbitraryInset !== null) {
1226
+ return { start: arbitraryInset };
1227
+ }
1228
+ const insetValue = INSET_SCALE[insetKey];
1229
+ if (insetValue !== void 0) {
1230
+ return { start: insetValue };
1231
+ }
1232
+ }
1233
+ if (cls.startsWith("inset-e-")) {
1234
+ const insetKey = cls.substring(8);
1235
+ const arbitraryInset = parseArbitraryInset(insetKey);
1236
+ if (arbitraryInset !== null) {
1237
+ return { end: arbitraryInset };
1238
+ }
1239
+ const insetValue = INSET_SCALE[insetKey];
1240
+ if (insetValue !== void 0) {
1241
+ return { end: insetValue };
1242
+ }
1243
+ }
1145
1244
  if (cls.startsWith("inset-")) {
1146
1245
  const insetKey = cls.substring(6);
1147
1246
  const arbitraryInset = parseArbitraryInset(insetKey);
@@ -1467,7 +1566,7 @@ function parseArbitrarySpacing(value) {
1467
1566
  return null;
1468
1567
  }
1469
1568
  function parseSpacing(cls) {
1470
- const marginMatch = cls.match(/^(-?)m([xytrbls]?)-(.+)$/);
1569
+ const marginMatch = cls.match(/^(-?)m([xytrblse]?)-(.+)$/);
1471
1570
  if (marginMatch) {
1472
1571
  const [, negativePrefix, dir, valueStr] = marginMatch;
1473
1572
  const isNegative = negativePrefix === "-";
@@ -1482,7 +1581,7 @@ function parseSpacing(cls) {
1482
1581
  return getMarginStyle(dir, finalValue);
1483
1582
  }
1484
1583
  }
1485
- const paddingMatch = cls.match(/^p([xytrbls]?)-(.+)$/);
1584
+ const paddingMatch = cls.match(/^p([xytrblse]?)-(.+)$/);
1486
1585
  if (paddingMatch) {
1487
1586
  const [, dir, valueStr] = paddingMatch;
1488
1587
  const arbitraryValue = parseArbitrarySpacing(valueStr);
@@ -1524,6 +1623,10 @@ function getMarginStyle(dir, value) {
1524
1623
  return { marginBottom: value };
1525
1624
  case "l":
1526
1625
  return { marginLeft: value };
1626
+ case "s":
1627
+ return { marginStart: value };
1628
+ case "e":
1629
+ return { marginEnd: value };
1527
1630
  default:
1528
1631
  return {};
1529
1632
  }
@@ -1544,6 +1647,10 @@ function getPaddingStyle(dir, value) {
1544
1647
  return { paddingBottom: value };
1545
1648
  case "l":
1546
1649
  return { paddingLeft: value };
1650
+ case "s":
1651
+ return { paddingStart: value };
1652
+ case "e":
1653
+ return { paddingEnd: value };
1547
1654
  default:
1548
1655
  return {};
1549
1656
  }
@@ -2032,11 +2139,13 @@ var STATE_MODIFIERS = [
2032
2139
  var PLATFORM_MODIFIERS = ["ios", "android", "web"];
2033
2140
  var COLOR_SCHEME_MODIFIERS = ["dark", "light"];
2034
2141
  var SCHEME_MODIFIERS = ["scheme"];
2142
+ var DIRECTIONAL_MODIFIERS = ["rtl", "ltr"];
2035
2143
  var SUPPORTED_MODIFIERS = [
2036
2144
  ...STATE_MODIFIERS,
2037
2145
  ...PLATFORM_MODIFIERS,
2038
2146
  ...COLOR_SCHEME_MODIFIERS,
2039
- ...SCHEME_MODIFIERS
2147
+ ...SCHEME_MODIFIERS,
2148
+ ...DIRECTIONAL_MODIFIERS
2040
2149
  ];
2041
2150
  function parseModifier(cls) {
2042
2151
  const colonIndex = cls.indexOf(":");
@@ -2071,6 +2180,9 @@ function isColorSchemeModifier(modifier) {
2071
2180
  function isSchemeModifier(modifier) {
2072
2181
  return SCHEME_MODIFIERS.includes(modifier);
2073
2182
  }
2183
+ function isDirectionalModifier(modifier) {
2184
+ return DIRECTIONAL_MODIFIERS.includes(modifier);
2185
+ }
2074
2186
  function isColorClass(className) {
2075
2187
  return className.startsWith("text-") || className.startsWith("bg-") || className.startsWith("border-");
2076
2188
  }
@@ -2115,11 +2227,24 @@ function expandSchemeModifier(schemeModifier, customColors, darkSuffix = "-dark"
2115
2227
  }
2116
2228
  ];
2117
2229
  }
2230
+ var DIRECTIONAL_TEXT_ALIGN_EXPANSIONS = {
2231
+ "text-start": { ltr: "text-left", rtl: "text-right" },
2232
+ "text-end": { ltr: "text-right", rtl: "text-left" }
2233
+ };
2234
+ function getDirectionalExpansion(cls) {
2235
+ return DIRECTIONAL_TEXT_ALIGN_EXPANSIONS[cls];
2236
+ }
2118
2237
  function splitModifierClasses(className) {
2119
2238
  const classes = className.trim().split(/\s+/).filter(Boolean);
2120
2239
  const baseClasses = [];
2121
2240
  const modifierClasses = [];
2122
2241
  for (const cls of classes) {
2242
+ const directionalExpansion = getDirectionalExpansion(cls);
2243
+ if (directionalExpansion) {
2244
+ modifierClasses.push({ modifier: "ltr", baseClass: directionalExpansion.ltr });
2245
+ modifierClasses.push({ modifier: "rtl", baseClass: directionalExpansion.rtl });
2246
+ continue;
2247
+ }
2123
2248
  const parsed = parseModifier(cls);
2124
2249
  if (parsed) {
2125
2250
  modifierClasses.push(parsed);
@@ -2298,6 +2423,40 @@ function getStatePropertyForModifier(modifier) {
2298
2423
  }
2299
2424
  }
2300
2425
 
2426
+ // src/babel/utils/directionalModifierProcessing.ts
2427
+ function processDirectionalModifiers(directionalModifiers, state, parseClassName2, generateStyleKey2, t) {
2428
+ state.needsI18nManagerImport = true;
2429
+ const modifiersByDirection = /* @__PURE__ */ new Map();
2430
+ for (const mod of directionalModifiers) {
2431
+ const direction = mod.modifier;
2432
+ if (!modifiersByDirection.has(direction)) {
2433
+ modifiersByDirection.set(direction, []);
2434
+ }
2435
+ const directionGroup = modifiersByDirection.get(direction);
2436
+ if (directionGroup) {
2437
+ directionGroup.push(mod);
2438
+ }
2439
+ }
2440
+ const conditionalExpressions = [];
2441
+ for (const [direction, modifiers] of modifiersByDirection) {
2442
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2443
+ const styleObject = parseClassName2(classNames, state.customTheme);
2444
+ if (hasRuntimeDimensions(styleObject)) {
2445
+ throw new Error(
2446
+ `w-screen and h-screen cannot be combined with directional modifiers (rtl:, ltr:). Found in: "${direction}:${classNames}". Use w-screen/h-screen without modifiers instead.`
2447
+ );
2448
+ }
2449
+ const styleKey = generateStyleKey2(`${direction}_${classNames}`);
2450
+ state.styleRegistry.set(styleKey, styleObject);
2451
+ const rtlVariable = t.identifier(state.i18nManagerVariableName);
2452
+ const directionCheck = direction === "rtl" ? rtlVariable : t.unaryExpression("!", rtlVariable);
2453
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2454
+ const conditionalExpression = t.logicalExpression("&&", directionCheck, styleReference);
2455
+ conditionalExpressions.push(conditionalExpression);
2456
+ }
2457
+ return conditionalExpressions;
2458
+ }
2459
+
2301
2460
  // src/babel/utils/dynamicProcessing.ts
2302
2461
  function processDynamicExpression(expression, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2303
2462
  if (t.isTemplateLiteral(expression)) {
@@ -2901,6 +3060,7 @@ function jsxAttributeVisitor(path2, state, t) {
2901
3060
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
2902
3061
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
2903
3062
  const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
3063
+ const directionalModifiers = modifierClasses.filter((m) => isDirectionalModifier(m.modifier));
2904
3064
  const stateModifiers = modifierClasses.filter(
2905
3065
  (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
2906
3066
  );
@@ -2923,6 +3083,7 @@ function jsxAttributeVisitor(path2, state, t) {
2923
3083
  }
2924
3084
  const hasPlatformModifiers = platformModifiers.length > 0;
2925
3085
  const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
3086
+ const hasDirectionalModifiers = directionalModifiers.length > 0;
2926
3087
  const hasStateModifiers = stateModifiers.length > 0;
2927
3088
  const hasBaseClasses = baseClasses.length > 0;
2928
3089
  let componentScope = null;
@@ -2938,7 +3099,7 @@ function jsxAttributeVisitor(path2, state, t) {
2938
3099
  }
2939
3100
  }
2940
3101
  }
2941
- if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
3102
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers || hasDirectionalModifiers)) {
2942
3103
  const jsxOpeningElement = path2.parent;
2943
3104
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2944
3105
  if (componentSupport) {
@@ -2948,7 +3109,7 @@ function jsxAttributeVisitor(path2, state, t) {
2948
3109
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
2949
3110
  if (hasRuntimeDimensions(baseStyleObject)) {
2950
3111
  throw path2.buildCodeFrameError(
2951
- `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with state, platform, or color scheme modifiers. Use w-screen/h-screen without modifiers instead.`
3112
+ `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with state, platform, color scheme, or directional modifiers. Use w-screen/h-screen without modifiers instead.`
2952
3113
  );
2953
3114
  }
2954
3115
  const baseStyleKey = generateStyleKey(baseClassName);
@@ -2977,6 +3138,16 @@ function jsxAttributeVisitor(path2, state, t) {
2977
3138
  );
2978
3139
  styleArrayElements.push(...colorSchemeConditionals);
2979
3140
  }
3141
+ if (hasDirectionalModifiers) {
3142
+ const directionalConditionals = processDirectionalModifiers(
3143
+ directionalModifiers,
3144
+ state,
3145
+ parseClassName,
3146
+ generateStyleKey,
3147
+ t
3148
+ );
3149
+ styleArrayElements.push(...directionalConditionals);
3150
+ }
2980
3151
  const modifiersByType = /* @__PURE__ */ new Map();
2981
3152
  for (const mod of stateModifiers) {
2982
3153
  const modType = mod.modifier;
@@ -3016,14 +3187,14 @@ function jsxAttributeVisitor(path2, state, t) {
3016
3187
  } else {
3017
3188
  }
3018
3189
  }
3019
- if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
3190
+ if ((hasPlatformModifiers || hasColorSchemeModifiers || hasDirectionalModifiers) && !hasStateModifiers) {
3020
3191
  const styleExpressions = [];
3021
3192
  if (hasBaseClasses) {
3022
3193
  const baseClassName = baseClasses.join(" ");
3023
3194
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
3024
3195
  if (hasRuntimeDimensions(baseStyleObject)) {
3025
3196
  throw path2.buildCodeFrameError(
3026
- `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with platform or color scheme modifiers. Use w-screen/h-screen without modifiers instead.`
3197
+ `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with platform, color scheme, or directional modifiers. Use w-screen/h-screen without modifiers instead.`
3027
3198
  );
3028
3199
  }
3029
3200
  const baseStyleKey = generateStyleKey(baseClassName);
@@ -3052,6 +3223,16 @@ function jsxAttributeVisitor(path2, state, t) {
3052
3223
  );
3053
3224
  styleExpressions.push(...colorSchemeConditionals);
3054
3225
  }
3226
+ if (hasDirectionalModifiers) {
3227
+ const directionalConditionals = processDirectionalModifiers(
3228
+ directionalModifiers,
3229
+ state,
3230
+ parseClassName,
3231
+ generateStyleKey,
3232
+ t
3233
+ );
3234
+ styleExpressions.push(...directionalConditionals);
3235
+ }
3055
3236
  const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
3056
3237
  const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3057
3238
  if (styleAttribute2) {
@@ -3277,6 +3458,17 @@ function importDeclarationVisitor(path2, state, t) {
3277
3458
  }
3278
3459
  return false;
3279
3460
  });
3461
+ if (node.importKind !== "type") {
3462
+ for (const spec of specifiers) {
3463
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3464
+ if (spec.imported.name === "I18nManager") {
3465
+ state.hasI18nManagerImport = true;
3466
+ state.i18nManagerLocalIdentifier = spec.local.name;
3467
+ break;
3468
+ }
3469
+ }
3470
+ }
3471
+ }
3280
3472
  if (node.importKind !== "type") {
3281
3473
  for (const spec of specifiers) {
3282
3474
  if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
@@ -3447,6 +3639,71 @@ function injectColorSchemeHook(functionPath, colorSchemeVariableName, hookName,
3447
3639
  body.body.unshift(hookCall);
3448
3640
  return true;
3449
3641
  }
3642
+ function addI18nManagerImport(path2, t) {
3643
+ const body = path2.node.body;
3644
+ let existingValueImport = null;
3645
+ for (const statement of body) {
3646
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
3647
+ if (statement.importKind === "type") {
3648
+ continue;
3649
+ }
3650
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
3651
+ if (hasNamespaceImport) {
3652
+ continue;
3653
+ }
3654
+ existingValueImport = statement;
3655
+ break;
3656
+ }
3657
+ }
3658
+ if (existingValueImport) {
3659
+ const hasI18nManager = existingValueImport.specifiers.some(
3660
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "I18nManager"
3661
+ );
3662
+ if (!hasI18nManager) {
3663
+ existingValueImport.specifiers.push(
3664
+ t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager"))
3665
+ );
3666
+ }
3667
+ } else {
3668
+ const importDeclaration = t.importDeclaration(
3669
+ [t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager"))],
3670
+ t.stringLiteral("react-native")
3671
+ );
3672
+ path2.unshiftContainer("body", importDeclaration);
3673
+ }
3674
+ }
3675
+ function injectI18nManagerVariable(path2, variableName, localIdentifier, t) {
3676
+ const body = path2.node.body;
3677
+ for (const statement of body) {
3678
+ if (t.isVariableDeclaration(statement) && statement.declarations.length > 0 && t.isVariableDeclarator(statement.declarations[0])) {
3679
+ const declarator = statement.declarations[0];
3680
+ if (t.isIdentifier(declarator.id) && declarator.id.name === variableName) {
3681
+ return;
3682
+ }
3683
+ }
3684
+ }
3685
+ const identifierToUse = localIdentifier ?? "I18nManager";
3686
+ const i18nVariable = t.variableDeclaration("const", [
3687
+ t.variableDeclarator(
3688
+ t.identifier(variableName),
3689
+ t.memberExpression(t.identifier(identifierToUse), t.identifier("isRTL"))
3690
+ )
3691
+ ]);
3692
+ let insertIndex = 0;
3693
+ for (let i = 0; i < body.length; i++) {
3694
+ const statement = body[i];
3695
+ if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
3696
+ insertIndex = i + 1;
3697
+ continue;
3698
+ }
3699
+ if (t.isImportDeclaration(statement)) {
3700
+ insertIndex = i + 1;
3701
+ continue;
3702
+ }
3703
+ break;
3704
+ }
3705
+ body.splice(insertIndex, 0, i18nVariable);
3706
+ }
3450
3707
  function addWindowDimensionsImport(path2, t) {
3451
3708
  const body = path2.node.body;
3452
3709
  let existingValueImport = null;
@@ -3534,11 +3791,16 @@ function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
3534
3791
  const body = path2.node.body;
3535
3792
  let insertIndex = 0;
3536
3793
  for (let i = 0; i < body.length; i++) {
3537
- if (t.isImportDeclaration(body[i])) {
3794
+ const statement = body[i];
3795
+ if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
3538
3796
  insertIndex = i + 1;
3539
- } else {
3540
- break;
3797
+ continue;
3798
+ }
3799
+ if (t.isImportDeclaration(statement)) {
3800
+ insertIndex = i + 1;
3801
+ continue;
3541
3802
  }
3803
+ break;
3542
3804
  }
3543
3805
  body.splice(insertIndex, 0, styleSheet);
3544
3806
  }
@@ -3582,8 +3844,9 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
3582
3844
  }
3583
3845
  const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
3584
3846
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
3847
+ const directionalModifiers = modifierClasses.filter((m) => isDirectionalModifier(m.modifier));
3585
3848
  const otherModifiers = modifierClasses.filter(
3586
- (m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier)
3849
+ (m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier) && !isDirectionalModifier(m.modifier)
3587
3850
  );
3588
3851
  const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
3589
3852
  let componentScope = null;
@@ -3717,6 +3980,62 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
3717
3980
  );
3718
3981
  }
3719
3982
  }
3983
+ const hasDirectionalModifiers = directionalModifiers.length > 0;
3984
+ if (hasDirectionalModifiers) {
3985
+ state.needsI18nManagerImport = true;
3986
+ const directionalConditionals = processDirectionalModifiers(
3987
+ directionalModifiers,
3988
+ state,
3989
+ parseClassName2,
3990
+ generateStyleKey2,
3991
+ t
3992
+ );
3993
+ const styleProperty = objectProperties.find(
3994
+ (prop) => t.isIdentifier(prop.key) && prop.key.name === "style"
3995
+ );
3996
+ if (styleProperty && t.isArrayExpression(styleProperty.value)) {
3997
+ styleProperty.value.elements.push(...directionalConditionals);
3998
+ } else {
3999
+ const styleArrayElements = [];
4000
+ if (baseClasses.length > 0) {
4001
+ const baseClassName = baseClasses.join(" ");
4002
+ const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
4003
+ const baseStyleKey = generateStyleKey2(baseClassName);
4004
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
4005
+ styleArrayElements.push(
4006
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
4007
+ );
4008
+ }
4009
+ styleArrayElements.push(...directionalConditionals);
4010
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
4011
+ }
4012
+ const rtlModifiers = directionalModifiers.filter((m) => m.modifier === "rtl");
4013
+ const ltrModifiers = directionalModifiers.filter((m) => m.modifier === "ltr");
4014
+ if (rtlModifiers.length > 0) {
4015
+ const rtlClassNames = rtlModifiers.map((m) => m.baseClass).join(" ");
4016
+ const rtlStyleObject = parseClassName2(rtlClassNames, state.customTheme);
4017
+ const rtlStyleKey = generateStyleKey2(`rtl_${rtlClassNames}`);
4018
+ state.styleRegistry.set(rtlStyleKey, rtlStyleObject);
4019
+ objectProperties.push(
4020
+ t.objectProperty(
4021
+ t.identifier("rtlStyle"),
4022
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(rtlStyleKey))
4023
+ )
4024
+ );
4025
+ }
4026
+ if (ltrModifiers.length > 0) {
4027
+ const ltrClassNames = ltrModifiers.map((m) => m.baseClass).join(" ");
4028
+ const ltrStyleObject = parseClassName2(ltrClassNames, state.customTheme);
4029
+ const ltrStyleKey = generateStyleKey2(`ltr_${ltrClassNames}`);
4030
+ state.styleRegistry.set(ltrStyleKey, ltrStyleObject);
4031
+ objectProperties.push(
4032
+ t.objectProperty(
4033
+ t.identifier("ltrStyle"),
4034
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(ltrStyleKey))
4035
+ )
4036
+ );
4037
+ }
4038
+ }
3720
4039
  const modifiersByType = /* @__PURE__ */ new Map();
3721
4040
  for (const mod of otherModifiers) {
3722
4041
  if (!modifiersByType.has(mod.modifier)) {
@@ -3773,7 +4092,7 @@ function programExit(path2, state, t) {
3773
4092
  if (state.hasTwImport) {
3774
4093
  removeTwImports(path2, t);
3775
4094
  }
3776
- if (!state.hasClassNames && !state.needsWindowDimensionsImport && !state.needsColorSchemeImport) {
4095
+ if (!state.hasClassNames && !state.needsWindowDimensionsImport && !state.needsColorSchemeImport && !state.needsI18nManagerImport) {
3777
4096
  return;
3778
4097
  }
3779
4098
  if (!state.hasStyleSheetImport && state.styleRegistry.size > 0) {
@@ -3782,6 +4101,12 @@ function programExit(path2, state, t) {
3782
4101
  if (state.needsPlatformImport && !state.hasPlatformImport) {
3783
4102
  addPlatformImport(path2, t);
3784
4103
  }
4104
+ if (state.needsI18nManagerImport && !state.hasI18nManagerImport) {
4105
+ addI18nManagerImport(path2, t);
4106
+ }
4107
+ if (state.needsI18nManagerImport) {
4108
+ injectI18nManagerVariable(path2, state.i18nManagerVariableName, state.i18nManagerLocalIdentifier, t);
4109
+ }
3785
4110
  if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
3786
4111
  addColorSchemeImport(path2, state.colorSchemeImportSource, state.colorSchemeHookName, t);
3787
4112
  }
@@ -94,6 +94,10 @@ export type PluginState = PluginPass & {
94
94
  needsWindowDimensionsImport: boolean;
95
95
  windowDimensionsVariableName: string;
96
96
  windowDimensionsLocalIdentifier?: string;
97
+ hasI18nManagerImport: boolean;
98
+ needsI18nManagerImport: boolean;
99
+ i18nManagerVariableName: string;
100
+ i18nManagerLocalIdentifier?: string;
97
101
  customTheme: CustomTheme;
98
102
  schemeModifierConfig: SchemeModifierConfig;
99
103
  supportedAttributes: Set<string>;