@mgcrea/react-native-tailwind 0.12.1 → 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 (104) hide show
  1. package/README.md +45 -2031
  2. package/dist/babel/index.cjs +1726 -1094
  3. package/dist/babel/plugin/componentScope.d.ts +26 -0
  4. package/dist/babel/plugin/componentScope.ts +87 -0
  5. package/dist/babel/plugin/state.d.ts +123 -0
  6. package/dist/babel/plugin/state.ts +185 -0
  7. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  8. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
  9. package/dist/babel/plugin/visitors/className.ts +652 -0
  10. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  11. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  12. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  13. package/dist/babel/plugin/visitors/imports.ts +116 -0
  14. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  15. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  16. package/dist/babel/plugin/visitors/program.ts +116 -0
  17. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  18. package/dist/babel/plugin/visitors/tw.test.ts +771 -0
  19. package/dist/babel/plugin/visitors/tw.ts +148 -0
  20. package/dist/babel/plugin.d.ts +3 -96
  21. package/dist/babel/plugin.test.ts +470 -0
  22. package/dist/babel/plugin.ts +28 -963
  23. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  24. package/dist/babel/utils/componentSupport.test.ts +20 -7
  25. package/dist/babel/utils/componentSupport.ts +2 -0
  26. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  27. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  28. package/dist/babel/utils/modifierProcessing.ts +21 -0
  29. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  30. package/dist/babel/utils/styleInjection.d.ts +31 -0
  31. package/dist/babel/utils/styleInjection.ts +253 -7
  32. package/dist/babel/utils/twProcessing.d.ts +2 -0
  33. package/dist/babel/utils/twProcessing.ts +103 -3
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +2 -2
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/modifiers.d.ts +32 -2
  54. package/dist/parser/modifiers.js +1 -1
  55. package/dist/parser/modifiers.test.js +1 -1
  56. package/dist/parser/sizing.js +1 -1
  57. package/dist/parser/spacing.d.ts +1 -1
  58. package/dist/parser/spacing.js +1 -1
  59. package/dist/parser/spacing.test.js +1 -1
  60. package/dist/parser/typography.test.js +1 -1
  61. package/dist/runtime.cjs +1 -1
  62. package/dist/runtime.cjs.map +4 -4
  63. package/dist/runtime.js +1 -1
  64. package/dist/runtime.js.map +4 -4
  65. package/package.json +6 -6
  66. package/src/babel/plugin/componentScope.ts +87 -0
  67. package/src/babel/plugin/state.ts +185 -0
  68. package/src/babel/plugin/visitors/className.test.ts +1625 -0
  69. package/src/babel/plugin/visitors/className.ts +652 -0
  70. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  71. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  72. package/src/babel/plugin/visitors/imports.ts +116 -0
  73. package/src/babel/plugin/visitors/program.test.ts +325 -0
  74. package/src/babel/plugin/visitors/program.ts +116 -0
  75. package/src/babel/plugin/visitors/tw.test.ts +771 -0
  76. package/src/babel/plugin/visitors/tw.ts +148 -0
  77. package/src/babel/plugin.ts +28 -963
  78. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  79. package/src/babel/utils/componentSupport.test.ts +20 -7
  80. package/src/babel/utils/componentSupport.ts +2 -0
  81. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  82. package/src/babel/utils/modifierProcessing.ts +21 -0
  83. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  84. package/src/babel/utils/styleInjection.ts +253 -7
  85. package/src/babel/utils/twProcessing.ts +103 -3
  86. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  87. package/src/components/TouchableOpacity.tsx +71 -0
  88. package/src/components/index.ts +3 -0
  89. package/src/config/markers.ts +5 -0
  90. package/src/index.ts +4 -5
  91. package/src/parser/borders.test.ts +162 -0
  92. package/src/parser/borders.ts +67 -9
  93. package/src/parser/colors.test.ts +249 -0
  94. package/src/parser/colors.ts +38 -0
  95. package/src/parser/index.ts +4 -2
  96. package/src/parser/layout.test.ts +74 -0
  97. package/src/parser/layout.ts +94 -0
  98. package/src/parser/modifiers.test.ts +206 -0
  99. package/src/parser/modifiers.ts +62 -3
  100. package/src/parser/sizing.ts +11 -0
  101. package/src/parser/spacing.test.ts +66 -0
  102. package/src/parser/spacing.ts +15 -5
  103. package/src/parser/typography.test.ts +8 -0
  104. package/src/parser/typography.ts +4 -0
@@ -5,6 +5,7 @@
5
5
  import type * as BabelTypes from "@babel/types";
6
6
  import type { ColorSchemeModifierType, CustomTheme, ParsedModifier } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
8
9
 
9
10
  /**
10
11
  * Plugin state interface (subset needed for color scheme modifier processing)
@@ -66,6 +67,16 @@ export function processColorSchemeModifiers(
66
67
  // Parse all classes for this color scheme together
67
68
  const classNames = modifiers.map((m) => m.baseClass).join(" ");
68
69
  const styleObject = parseClassName(classNames, state.customTheme);
70
+
71
+ // Check for runtime dimensions (w-screen, h-screen)
72
+ if (hasRuntimeDimensions(styleObject)) {
73
+ throw new Error(
74
+ `w-screen and h-screen cannot be combined with color scheme modifiers (dark:, light:, scheme:). ` +
75
+ `Found in: "${scheme}:${classNames}". ` +
76
+ `Use w-screen/h-screen without modifiers instead.`,
77
+ );
78
+ }
79
+
69
80
  const styleKey = generateStyleKey(`${scheme}_${classNames}`);
70
81
 
71
82
  // Register style in the registry
@@ -86,6 +86,26 @@ describe("getComponentModifierSupport", () => {
86
86
  supportedModifiers: ["focus", "disabled", "placeholder"],
87
87
  });
88
88
  });
89
+
90
+ it("should recognize TouchableOpacity component", () => {
91
+ const element = createJSXElement("<TouchableOpacity />");
92
+ const result = getComponentModifierSupport(element, t);
93
+
94
+ expect(result).toEqual({
95
+ component: "TouchableOpacity",
96
+ supportedModifiers: ["active", "disabled"],
97
+ });
98
+ });
99
+
100
+ it("should recognize TouchableOpacity with attributes", () => {
101
+ const element = createJSXElement('<TouchableOpacity className="m-4" onPress={handlePress} />');
102
+ const result = getComponentModifierSupport(element, t);
103
+
104
+ expect(result).toEqual({
105
+ component: "TouchableOpacity",
106
+ supportedModifiers: ["active", "disabled"],
107
+ });
108
+ });
89
109
  });
90
110
 
91
111
  describe("Member expressions", () => {
@@ -139,13 +159,6 @@ describe("getComponentModifierSupport", () => {
139
159
  expect(result).toBeNull();
140
160
  });
141
161
 
142
- it("should return null for TouchableOpacity", () => {
143
- const element = createJSXElement("<TouchableOpacity />");
144
- const result = getComponentModifierSupport(element, t);
145
-
146
- expect(result).toBeNull();
147
- });
148
-
149
162
  it("should return null for custom components", () => {
150
163
  const element = createJSXElement("<CustomButton />");
151
164
  const result = getComponentModifierSupport(element, t);
@@ -41,6 +41,8 @@ export function getComponentModifierSupport(
41
41
  switch (componentName) {
42
42
  case "Pressable":
43
43
  return { component: "Pressable", supportedModifiers: ["active", "hover", "focus", "disabled"] };
44
+ case "TouchableOpacity":
45
+ return { component: "TouchableOpacity", supportedModifiers: ["active", "disabled"] };
44
46
  case "TextInput":
45
47
  return { component: "TextInput", supportedModifiers: ["focus", "disabled", "placeholder"] };
46
48
  default:
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Utility functions for processing directional modifiers (rtl:, ltr:)
3
+ */
4
+
5
+ import type * as BabelTypes from "@babel/types";
6
+ import type { CustomTheme, DirectionalModifierType, ParsedModifier } from "../../parser/index.js";
7
+ import type { StyleObject } from "../../types/core.js";
8
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
9
+
10
+ /**
11
+ * Plugin state interface (subset needed for directional modifier processing)
12
+ */
13
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
14
+ export interface DirectionalModifierProcessingState {
15
+ styleRegistry: Map<string, StyleObject>;
16
+ customTheme: CustomTheme;
17
+ stylesIdentifier: string;
18
+ needsI18nManagerImport: boolean;
19
+ i18nManagerVariableName: string;
20
+ }
21
+
22
+ /**
23
+ * Process directional modifiers and generate conditional style expressions
24
+ *
25
+ * @param directionalModifiers - Array of parsed directional modifiers
26
+ * @param state - Plugin state
27
+ * @param parseClassName - Function to parse class names into style objects
28
+ * @param generateStyleKey - Function to generate unique style keys
29
+ * @param t - Babel types
30
+ * @returns Array of AST nodes for conditional expressions
31
+ *
32
+ * @example
33
+ * Input: [{ modifier: "rtl", baseClass: "mr-4" }, { modifier: "ltr", baseClass: "ml-4" }]
34
+ * Output: [
35
+ * _twIsRTL && styles._rtl_mr_4,
36
+ * !_twIsRTL && styles._ltr_ml_4
37
+ * ]
38
+ */
39
+ export function processDirectionalModifiers(
40
+ directionalModifiers: ParsedModifier[],
41
+ state: DirectionalModifierProcessingState,
42
+ parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
43
+ generateStyleKey: (className: string) => string,
44
+ t: typeof BabelTypes,
45
+ ): BabelTypes.Expression[] {
46
+ // Mark that we need I18nManager import
47
+ state.needsI18nManagerImport = true;
48
+
49
+ // Group modifiers by direction (rtl, ltr)
50
+ const modifiersByDirection = new Map<DirectionalModifierType, ParsedModifier[]>();
51
+
52
+ for (const mod of directionalModifiers) {
53
+ const direction = mod.modifier as DirectionalModifierType;
54
+ if (!modifiersByDirection.has(direction)) {
55
+ modifiersByDirection.set(direction, []);
56
+ }
57
+ const directionGroup = modifiersByDirection.get(direction);
58
+ if (directionGroup) {
59
+ directionGroup.push(mod);
60
+ }
61
+ }
62
+
63
+ // Build conditional expressions for each direction
64
+ const conditionalExpressions: BabelTypes.Expression[] = [];
65
+
66
+ for (const [direction, modifiers] of modifiersByDirection) {
67
+ // Parse all classes for this direction together
68
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
69
+ const styleObject = parseClassName(classNames, state.customTheme);
70
+
71
+ // Check for runtime dimensions (w-screen, h-screen)
72
+ if (hasRuntimeDimensions(styleObject)) {
73
+ throw new Error(
74
+ `w-screen and h-screen cannot be combined with directional modifiers (rtl:, ltr:). ` +
75
+ `Found in: "${direction}:${classNames}". ` +
76
+ `Use w-screen/h-screen without modifiers instead.`,
77
+ );
78
+ }
79
+
80
+ const styleKey = generateStyleKey(`${direction}_${classNames}`);
81
+
82
+ // Register style in the registry
83
+ state.styleRegistry.set(styleKey, styleObject);
84
+
85
+ // Create conditional:
86
+ // - For rtl: _twIsRTL && styles._rtl_...
87
+ // - For ltr: !_twIsRTL && styles._ltr_...
88
+ const rtlVariable = t.identifier(state.i18nManagerVariableName);
89
+ const directionCheck = direction === "rtl" ? rtlVariable : t.unaryExpression("!", rtlVariable);
90
+
91
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
92
+
93
+ const conditionalExpression = t.logicalExpression("&&", directionCheck, styleReference);
94
+
95
+ conditionalExpressions.push(conditionalExpression);
96
+ }
97
+
98
+ return conditionalExpressions;
99
+ }
@@ -6,6 +6,7 @@ import type * as BabelTypes from "@babel/types";
6
6
  import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
8
  import { getStatePropertyForModifier } from "./componentSupport.js";
9
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
9
10
 
10
11
  /**
11
12
  * Plugin state interface (subset needed for modifier processing)
@@ -36,6 +37,16 @@ export function processStaticClassNameWithModifiers(
36
37
  if (baseClasses.length > 0) {
37
38
  const baseClassName = baseClasses.join(" ");
38
39
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
40
+
41
+ // Check for runtime dimensions (w-screen, h-screen) in base classes
42
+ if (hasRuntimeDimensions(baseStyleObject)) {
43
+ throw new Error(
44
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). ` +
45
+ `Found in: "${baseClassName}". ` +
46
+ `Use w-screen/h-screen without modifiers instead.`,
47
+ );
48
+ }
49
+
39
50
  const baseStyleKey = generateStyleKey(baseClassName);
40
51
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
41
52
  baseStyleExpression = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey));
@@ -67,6 +78,16 @@ export function processStaticClassNameWithModifiers(
67
78
  // Parse all modifier classes together
68
79
  const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
69
80
  const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
81
+
82
+ // Check for runtime dimensions (w-screen, h-screen) in modifier classes
83
+ if (hasRuntimeDimensions(modifierStyleObject)) {
84
+ throw new Error(
85
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). ` +
86
+ `Found in: "${modifierType}:${modifierClassNames}". ` +
87
+ `Use w-screen/h-screen without modifiers instead.`,
88
+ );
89
+ }
90
+
70
91
  const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
71
92
  state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
72
93
 
@@ -5,6 +5,7 @@
5
5
  import type * as BabelTypes from "@babel/types";
6
6
  import type { CustomTheme, ParsedModifier, PlatformModifierType } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
8
9
 
9
10
  /**
10
11
  * Plugin state interface (subset needed for platform modifier processing)
@@ -62,6 +63,16 @@ export function processPlatformModifiers(
62
63
  // Parse all classes for this platform together
63
64
  const classNames = modifiers.map((m) => m.baseClass).join(" ");
64
65
  const styleObject = parseClassName(classNames, state.customTheme);
66
+
67
+ // Check for runtime dimensions (w-screen, h-screen)
68
+ if (hasRuntimeDimensions(styleObject)) {
69
+ throw new Error(
70
+ `w-screen and h-screen cannot be combined with platform modifiers (ios:, android:, web:). ` +
71
+ `Found in: "${platform}:${classNames}". ` +
72
+ `Use w-screen/h-screen without modifiers instead.`,
73
+ );
74
+ }
75
+
65
76
  const styleKey = generateStyleKey(`${platform}_${classNames}`);
66
77
 
67
78
  // Register style in the registry
@@ -220,6 +220,243 @@ export function injectColorSchemeHook(
220
220
  return true;
221
221
  }
222
222
 
223
+ /**
224
+ * Add I18nManager import to the file or merge with existing react-native import
225
+ */
226
+ export function addI18nManagerImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
227
+ // Check if there's already a value import from react-native
228
+ const body = path.node.body;
229
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
230
+
231
+ for (const statement of body) {
232
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
233
+ // Skip type-only imports (they get erased at runtime)
234
+ if (statement.importKind === "type") {
235
+ continue;
236
+ }
237
+ // Skip namespace imports (import * as RN) - can't add named specifiers to them
238
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
239
+ if (hasNamespaceImport) {
240
+ continue;
241
+ }
242
+ existingValueImport = statement;
243
+ break; // Found a value import, we can stop
244
+ }
245
+ }
246
+
247
+ if (existingValueImport) {
248
+ // Check if I18nManager is already imported
249
+ const hasI18nManager = existingValueImport.specifiers.some(
250
+ (spec) =>
251
+ t.isImportSpecifier(spec) &&
252
+ spec.imported.type === "Identifier" &&
253
+ spec.imported.name === "I18nManager",
254
+ );
255
+
256
+ if (!hasI18nManager) {
257
+ // Add I18nManager to existing value import
258
+ existingValueImport.specifiers.push(
259
+ t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager")),
260
+ );
261
+ }
262
+ } else {
263
+ // No value import exists - create a new one
264
+ // (Don't merge with type-only or namespace imports)
265
+ const importDeclaration = t.importDeclaration(
266
+ [t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager"))],
267
+ t.stringLiteral("react-native"),
268
+ );
269
+ path.unshiftContainer("body", importDeclaration);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Inject I18nManager.isRTL variable at the top of the file (after imports and directives)
275
+ *
276
+ * Unlike hooks (useColorScheme, useWindowDimensions), I18nManager.isRTL is not a hook
277
+ * and can be accessed at module level. This is injected once per file.
278
+ *
279
+ * @param path - Program path
280
+ * @param variableName - Name for the RTL variable (e.g., '_twIsRTL')
281
+ * @param localIdentifier - Local identifier if I18nManager is already imported with an alias
282
+ * @param t - Babel types
283
+ */
284
+ export function injectI18nManagerVariable(
285
+ path: NodePath<BabelTypes.Program>,
286
+ variableName: string,
287
+ localIdentifier: string | undefined,
288
+ t: typeof BabelTypes,
289
+ ): void {
290
+ const body = path.node.body;
291
+
292
+ // Check if variable is already declared
293
+ for (const statement of body) {
294
+ if (
295
+ t.isVariableDeclaration(statement) &&
296
+ statement.declarations.length > 0 &&
297
+ t.isVariableDeclarator(statement.declarations[0])
298
+ ) {
299
+ const declarator = statement.declarations[0];
300
+ if (t.isIdentifier(declarator.id) && declarator.id.name === variableName) {
301
+ return; // Already injected
302
+ }
303
+ }
304
+ }
305
+
306
+ // Use the local identifier if I18nManager was already imported with an alias,
307
+ // otherwise use 'I18nManager'
308
+ // e.g., import { I18nManager as RTL } → use RTL.isRTL
309
+ const identifierToUse = localIdentifier ?? "I18nManager";
310
+
311
+ // Create: const _twIsRTL = I18nManager.isRTL; (or aliased name if already imported)
312
+ const i18nVariable = t.variableDeclaration("const", [
313
+ t.variableDeclarator(
314
+ t.identifier(variableName),
315
+ t.memberExpression(t.identifier(identifierToUse), t.identifier("isRTL")),
316
+ ),
317
+ ]);
318
+
319
+ // Find the index to insert after all imports and directives ('use client', 'use strict', etc.)
320
+ let insertIndex = 0;
321
+
322
+ for (let i = 0; i < body.length; i++) {
323
+ const statement = body[i];
324
+
325
+ // Skip directives ('use client', 'use strict', etc.)
326
+ if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
327
+ insertIndex = i + 1;
328
+ continue;
329
+ }
330
+
331
+ // Skip imports
332
+ if (t.isImportDeclaration(statement)) {
333
+ insertIndex = i + 1;
334
+ continue;
335
+ }
336
+
337
+ // Stop at the first non-directive, non-import statement
338
+ break;
339
+ }
340
+
341
+ // Insert after imports and directives
342
+ body.splice(insertIndex, 0, i18nVariable);
343
+ }
344
+
345
+ /**
346
+ * Add useWindowDimensions import to the file or merge with existing react-native import
347
+ */
348
+ export function addWindowDimensionsImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
349
+ // Check if there's already an import from react-native
350
+ const body = path.node.body;
351
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
352
+
353
+ for (const statement of body) {
354
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
355
+ // Only consider value imports (not type-only imports which get erased)
356
+ if (statement.importKind !== "type") {
357
+ existingValueImport = statement;
358
+ break; // Found a value import, we can stop
359
+ }
360
+ }
361
+ }
362
+
363
+ // If we found a value import (not type-only), merge with it
364
+ if (existingValueImport) {
365
+ // Check if the hook is already imported
366
+ const hasHook = existingValueImport.specifiers.some(
367
+ (spec) =>
368
+ t.isImportSpecifier(spec) &&
369
+ spec.imported.type === "Identifier" &&
370
+ spec.imported.name === "useWindowDimensions",
371
+ );
372
+
373
+ if (!hasHook) {
374
+ // Add hook to existing value import
375
+ existingValueImport.specifiers.push(
376
+ t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions")),
377
+ );
378
+ }
379
+ } else {
380
+ // No value import exists - create a new one
381
+ // (Don't merge with type-only imports as they get erased by Babel/TypeScript)
382
+ const importDeclaration = t.importDeclaration(
383
+ [t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions"))],
384
+ t.stringLiteral("react-native"),
385
+ );
386
+ path.unshiftContainer("body", importDeclaration);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Inject useWindowDimensions hook call at the top of a function component
392
+ *
393
+ * @param functionPath - Path to the function component
394
+ * @param dimensionsVariableName - Name for the dimensions variable
395
+ * @param hookName - Name of the hook to call (e.g., 'useWindowDimensions')
396
+ * @param localIdentifier - Local identifier if hook is already imported with an alias
397
+ * @param t - Babel types
398
+ * @returns true if hook was injected, false if already exists
399
+ */
400
+ export function injectWindowDimensionsHook(
401
+ functionPath: NodePath<BabelTypes.Function>,
402
+ dimensionsVariableName: string,
403
+ hookName: string,
404
+ localIdentifier: string | undefined,
405
+ t: typeof BabelTypes,
406
+ ): boolean {
407
+ let body = functionPath.node.body;
408
+
409
+ // Handle concise arrow functions: () => <JSX />
410
+ // Convert to block statement: () => { const _twDimensions = useWindowDimensions(); return <JSX />; }
411
+ if (!t.isBlockStatement(body)) {
412
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
413
+ // Convert concise body to block statement with return
414
+ const returnStatement = t.returnStatement(body);
415
+ const blockStatement = t.blockStatement([returnStatement]);
416
+ functionPath.node.body = blockStatement;
417
+ body = blockStatement;
418
+ } else {
419
+ // Other non-block functions (shouldn't happen for components, but be safe)
420
+ return false;
421
+ }
422
+ }
423
+
424
+ // Check if hook is already injected
425
+ const hasHook = body.body.some((statement) => {
426
+ if (
427
+ t.isVariableDeclaration(statement) &&
428
+ statement.declarations.length > 0 &&
429
+ t.isVariableDeclarator(statement.declarations[0])
430
+ ) {
431
+ const declarator = statement.declarations[0];
432
+ return t.isIdentifier(declarator.id) && declarator.id.name === dimensionsVariableName;
433
+ }
434
+ return false;
435
+ });
436
+
437
+ if (hasHook) {
438
+ return false; // Already injected
439
+ }
440
+
441
+ // Use the local identifier if hook was already imported with an alias,
442
+ // otherwise use the configured hook name
443
+ // e.g., import { useWindowDimensions as useDims } → call useDims()
444
+ const identifierToCall = localIdentifier ?? hookName;
445
+
446
+ // Create: const _twDimensions = useWindowDimensions(); (or aliased name if already imported)
447
+ const hookCall = t.variableDeclaration("const", [
448
+ t.variableDeclarator(
449
+ t.identifier(dimensionsVariableName),
450
+ t.callExpression(t.identifier(identifierToCall), []),
451
+ ),
452
+ ]);
453
+
454
+ // Insert at the beginning of function body
455
+ body.body.unshift(hookCall);
456
+
457
+ return true;
458
+ }
459
+
223
460
  /**
224
461
  * Inject StyleSheet.create with all collected styles at the top of the file
225
462
  * This ensures the styles object is defined before any code that references it
@@ -262,20 +499,29 @@ export function injectStylesAtTop(
262
499
  ),
263
500
  ]);
264
501
 
265
- // Find the index to insert after all imports
502
+ // Find the index to insert after all imports and directives ('use client', 'use strict', etc.)
266
503
  const body = path.node.body;
267
504
  let insertIndex = 0;
268
505
 
269
- // Find the last import statement
270
506
  for (let i = 0; i < body.length; i++) {
271
- if (t.isImportDeclaration(body[i])) {
507
+ const statement = body[i];
508
+
509
+ // Skip directives ('use client', 'use strict', etc.)
510
+ if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
272
511
  insertIndex = i + 1;
273
- } else {
274
- // Stop at the first non-import statement
275
- break;
512
+ continue;
276
513
  }
514
+
515
+ // Skip imports
516
+ if (t.isImportDeclaration(statement)) {
517
+ insertIndex = i + 1;
518
+ continue;
519
+ }
520
+
521
+ // Stop at the first non-directive, non-import statement
522
+ break;
277
523
  }
278
524
 
279
- // Insert StyleSheet.create after imports
525
+ // Insert StyleSheet.create after imports and directives
280
526
  body.splice(insertIndex, 0, styleSheet);
281
527
  }
@@ -8,13 +8,16 @@ import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/ind
8
8
  import {
9
9
  expandSchemeModifier,
10
10
  isColorSchemeModifier,
11
+ isDirectionalModifier,
11
12
  isPlatformModifier,
12
13
  isSchemeModifier,
13
14
  } from "../../parser/index.js";
14
15
  import type { SchemeModifierConfig } from "../../types/config.js";
15
16
  import type { StyleObject } from "../../types/core.js";
16
17
  import { processColorSchemeModifiers } from "./colorSchemeModifierProcessing.js";
18
+ import { processDirectionalModifiers } from "./directionalModifierProcessing.js";
17
19
  import { processPlatformModifiers } from "./platformModifierProcessing.js";
20
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
18
21
 
19
22
  /**
20
23
  * Plugin state interface (subset needed for tw processing)
@@ -32,6 +35,9 @@ export interface TwProcessingState {
32
35
  colorSchemeLocalIdentifier?: string;
33
36
  // Platform support (for ios:/android:/web: modifiers)
34
37
  needsPlatformImport: boolean;
38
+ // Directional support (for rtl:/ltr: modifiers)
39
+ needsI18nManagerImport: boolean;
40
+ i18nManagerVariableName: string;
35
41
  }
36
42
 
37
43
  /**
@@ -77,6 +83,16 @@ export function processTwCall(
77
83
  if (baseClasses.length > 0) {
78
84
  const baseClassName = baseClasses.join(" ");
79
85
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
86
+
87
+ // Check for runtime dimensions (w-screen, h-screen)
88
+ if (hasRuntimeDimensions(baseStyleObject)) {
89
+ throw path.buildCodeFrameError(
90
+ `w-screen and h-screen are not supported in tw\`\` or twStyle() calls. ` +
91
+ `Found: "${baseClassName}". ` +
92
+ `Use them in className attributes instead.`,
93
+ );
94
+ }
95
+
80
96
  const baseStyleKey = generateStyleKey(baseClassName);
81
97
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
82
98
 
@@ -91,11 +107,15 @@ export function processTwCall(
91
107
  objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
92
108
  }
93
109
 
94
- // Separate color-scheme and platform modifiers from other modifiers
110
+ // Separate color-scheme, platform, and directional modifiers from other modifiers
95
111
  const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
96
112
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
113
+ const directionalModifiers = modifierClasses.filter((m) => isDirectionalModifier(m.modifier));
97
114
  const otherModifiers = modifierClasses.filter(
98
- (m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier),
115
+ (m) =>
116
+ !isColorSchemeModifier(m.modifier) &&
117
+ !isPlatformModifier(m.modifier) &&
118
+ !isDirectionalModifier(m.modifier),
99
119
  );
100
120
 
101
121
  // Check if we need color scheme support
@@ -282,7 +302,87 @@ export function processTwCall(
282
302
  }
283
303
  }
284
304
 
285
- // Group other modifiers by type (non-color-scheme and non-platform modifiers)
305
+ // Process directional modifiers if present
306
+ const hasDirectionalModifiers = directionalModifiers.length > 0;
307
+
308
+ if (hasDirectionalModifiers) {
309
+ // Mark that we need I18nManager import
310
+ state.needsI18nManagerImport = true;
311
+
312
+ // Generate directional conditional expressions
313
+ const directionalConditionals = processDirectionalModifiers(
314
+ directionalModifiers,
315
+ state,
316
+ parseClassName,
317
+ generateStyleKey,
318
+ t,
319
+ );
320
+
321
+ // If we already have a style array (from color scheme or platform modifiers), add to it
322
+ // Otherwise, convert style property to an array
323
+ const styleProperty = objectProperties.find(
324
+ (prop) => t.isIdentifier(prop.key) && prop.key.name === "style",
325
+ );
326
+
327
+ if (styleProperty && t.isArrayExpression(styleProperty.value)) {
328
+ // Already have style array, add directional conditionals to it
329
+ styleProperty.value.elements.push(...directionalConditionals);
330
+ } else {
331
+ // No existing array, create style array with base + directional conditionals
332
+ const styleArrayElements: BabelTypes.Expression[] = [];
333
+
334
+ // Add base style if present
335
+ if (baseClasses.length > 0) {
336
+ const baseClassName = baseClasses.join(" ");
337
+ const baseStyleObject = parseClassName(baseClassName, state.customTheme);
338
+ const baseStyleKey = generateStyleKey(baseClassName);
339
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
340
+ styleArrayElements.push(
341
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
342
+ );
343
+ }
344
+
345
+ // Add directional conditionals
346
+ styleArrayElements.push(...directionalConditionals);
347
+
348
+ // Replace style property with array
349
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
350
+ }
351
+
352
+ // Also add rtlStyle/ltrStyle properties for manual processing
353
+ const rtlModifiers = directionalModifiers.filter((m) => m.modifier === "rtl");
354
+ const ltrModifiers = directionalModifiers.filter((m) => m.modifier === "ltr");
355
+
356
+ if (rtlModifiers.length > 0) {
357
+ const rtlClassNames = rtlModifiers.map((m) => m.baseClass).join(" ");
358
+ const rtlStyleObject = parseClassName(rtlClassNames, state.customTheme);
359
+ const rtlStyleKey = generateStyleKey(`rtl_${rtlClassNames}`);
360
+ state.styleRegistry.set(rtlStyleKey, rtlStyleObject);
361
+
362
+ objectProperties.push(
363
+ t.objectProperty(
364
+ t.identifier("rtlStyle"),
365
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(rtlStyleKey)),
366
+ ),
367
+ );
368
+ }
369
+
370
+ if (ltrModifiers.length > 0) {
371
+ const ltrClassNames = ltrModifiers.map((m) => m.baseClass).join(" ");
372
+ const ltrStyleObject = parseClassName(ltrClassNames, state.customTheme);
373
+ const ltrStyleKey = generateStyleKey(`ltr_${ltrClassNames}`);
374
+ state.styleRegistry.set(ltrStyleKey, ltrStyleObject);
375
+
376
+ objectProperties.push(
377
+ t.objectProperty(
378
+ t.identifier("ltrStyle"),
379
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(ltrStyleKey)),
380
+ ),
381
+ );
382
+ }
383
+ }
384
+
385
+ // Group other modifiers by type (non-color-scheme, non-platform, and non-directional modifiers)
286
386
  const modifiersByType = new Map<ModifierType, ParsedModifier[]>();
287
387
  for (const mod of otherModifiers) {
288
388
  if (!modifiersByType.has(mod.modifier)) {