@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
@@ -0,0 +1,406 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { transform } from "../../../../test/helpers/babelTransform.js";
4
+
5
+ describe("className visitor - window dimensions (w-screen/h-screen)", () => {
6
+ it("should inject useWindowDimensions hook for w-screen in function component", () => {
7
+ const input = `
8
+ import React from 'react';
9
+ import { View } from 'react-native';
10
+
11
+ export function MyComponent() {
12
+ return <View className="w-screen bg-white" />;
13
+ }
14
+ `;
15
+
16
+ const output = transform(input, undefined, true);
17
+
18
+ // Should import useWindowDimensions
19
+ expect(output).toContain("useWindowDimensions");
20
+ expect(output).toMatch(/import.*useWindowDimensions.*from ['"]react-native['"]/);
21
+
22
+ // Should inject hook call
23
+ expect(output).toContain("_twDimensions");
24
+ expect(output).toContain("useWindowDimensions()");
25
+
26
+ // Should generate inline style with width
27
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
28
+
29
+ // Should have StyleSheet for static styles
30
+ expect(output).toContain("_twStyles");
31
+ expect(output).toContain("backgroundColor:");
32
+ });
33
+
34
+ it("should inject useWindowDimensions hook for h-screen in function component", () => {
35
+ const input = `
36
+ import React from 'react';
37
+ import { View } from 'react-native';
38
+
39
+ export function MyComponent() {
40
+ return <View className="h-screen bg-blue-500" />;
41
+ }
42
+ `;
43
+
44
+ const output = transform(input, undefined, true);
45
+
46
+ // Should import useWindowDimensions
47
+ expect(output).toContain("useWindowDimensions");
48
+
49
+ // Should inject hook call
50
+ expect(output).toContain("_twDimensions");
51
+ expect(output).toContain("useWindowDimensions()");
52
+
53
+ // Should generate inline style with height
54
+ expect(output).toMatch(/height:\s*_twDimensions\.height/);
55
+ });
56
+
57
+ it("should handle both w-screen and h-screen together", () => {
58
+ const input = `
59
+ import React from 'react';
60
+ import { View } from 'react-native';
61
+
62
+ function FullScreenView() {
63
+ return <View className="w-screen h-screen bg-gray-100" />;
64
+ }
65
+ `;
66
+
67
+ const output = transform(input, undefined, true);
68
+
69
+ // Should inject hook
70
+ expect(output).toContain("useWindowDimensions()");
71
+
72
+ // Should generate inline style with both dimensions
73
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
74
+ expect(output).toMatch(/height:\s*_twDimensions\.height/);
75
+
76
+ // Should have StyleSheet for static styles
77
+ expect(output).toContain("backgroundColor:");
78
+ });
79
+
80
+ it("should handle w-screen/h-screen with arrow function component", () => {
81
+ const input = `
82
+ import React from 'react';
83
+ import { View } from 'react-native';
84
+
85
+ const MyComponent = () => {
86
+ return <View className="w-screen" />;
87
+ };
88
+ `;
89
+
90
+ const output = transform(input, undefined, true);
91
+
92
+ // Should inject hook
93
+ expect(output).toContain("useWindowDimensions()");
94
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
95
+ });
96
+
97
+ it("should handle concise arrow function and inject hook", () => {
98
+ const input = `
99
+ import React from 'react';
100
+ import { View } from 'react-native';
101
+
102
+ const MyComponent = () => <View className="w-screen" />;
103
+ `;
104
+
105
+ const output = transform(input, undefined, true);
106
+
107
+ // Should convert concise arrow to block statement and inject hook
108
+ expect(output).toContain("useWindowDimensions()");
109
+ expect(output).toContain("return");
110
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
111
+ });
112
+
113
+ it("should merge useWindowDimensions with existing react-native import", () => {
114
+ const input = `
115
+ import React from 'react';
116
+ import { View, Text } from 'react-native';
117
+
118
+ function MyComponent() {
119
+ return <View className="w-screen" />;
120
+ }
121
+ `;
122
+
123
+ const output = transform(input, undefined, true);
124
+
125
+ // Should merge useWindowDimensions into existing import (not create separate import)
126
+ // The key is that useWindowDimensions should be imported
127
+ expect(output).toContain("useWindowDimensions");
128
+ });
129
+
130
+ it("should warn when w-screen/h-screen used outside function component", () => {
131
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
132
+
133
+ const input = `
134
+ import React from 'react';
135
+ import { View } from 'react-native';
136
+
137
+ class MyComponent extends React.Component {
138
+ render() {
139
+ return <View className="w-screen" />;
140
+ }
141
+ }
142
+ `;
143
+
144
+ transform(input, undefined, true);
145
+
146
+ // Should warn about usage in class component
147
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
148
+ expect.stringContaining("w-screen/h-screen classes require a function component scope"),
149
+ );
150
+
151
+ consoleWarnSpy.mockRestore();
152
+ });
153
+
154
+ it("should combine w-screen with other static classes", () => {
155
+ const input = `
156
+ import React from 'react';
157
+ import { View } from 'react-native';
158
+
159
+ function MyComponent() {
160
+ return <View className="w-screen p-4 bg-white rounded-lg" />;
161
+ }
162
+ `;
163
+
164
+ const output = transform(input, undefined, true);
165
+
166
+ // Should have static styles in StyleSheet
167
+ expect(output).toContain("padding:");
168
+ expect(output).toContain("backgroundColor:");
169
+ expect(output).toContain("borderRadius:");
170
+
171
+ // Should have runtime dimension
172
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
173
+
174
+ // Should combine as array
175
+ expect(output).toContain("_twStyles");
176
+ expect(output).toContain("_twDimensions");
177
+ });
178
+
179
+ it("should inject hook only once even with multiple w-screen/h-screen uses", () => {
180
+ const input = `
181
+ import React from 'react';
182
+ import { View } from 'react-native';
183
+
184
+ function MyComponent() {
185
+ return (
186
+ <>
187
+ <View className="w-screen" />
188
+ <View className="h-screen" />
189
+ <View className="w-screen h-screen" />
190
+ </>
191
+ );
192
+ }
193
+ `;
194
+
195
+ const output = transform(input, undefined, true);
196
+
197
+ // Count occurrences of hook injection (should be exactly 1)
198
+ const hookMatches = output.match(/_twDimensions\s*=\s*useWindowDimensions\(\)/g) ?? [];
199
+ expect(hookMatches.length).toBe(1);
200
+ });
201
+
202
+ it("should handle aliased useWindowDimensions import", () => {
203
+ const input = `
204
+ import React from 'react';
205
+ import { View, useWindowDimensions as useDims } from 'react-native';
206
+
207
+ function MyComponent() {
208
+ const dims = useDims();
209
+ return <View className="w-screen" />;
210
+ }
211
+ `;
212
+
213
+ const output = transform(input, undefined, true);
214
+
215
+ // Should use the aliased name
216
+ expect(output).toContain("useDims()");
217
+ // Should still generate _twDimensions variable for our use
218
+ expect(output).toContain("_twDimensions");
219
+ });
220
+
221
+ it("should error when w-screen is combined with dark: modifier", () => {
222
+ const input = `
223
+ import { View } from 'react-native';
224
+ export function MyComponent() {
225
+ return <View className="dark:w-screen bg-white" />;
226
+ }
227
+ `;
228
+
229
+ expect(() => transform(input, undefined, true)).toThrow(
230
+ /w-screen and h-screen cannot be combined with color scheme modifiers/,
231
+ );
232
+ });
233
+
234
+ it("should error when h-screen is combined with light: modifier", () => {
235
+ const input = `
236
+ import { View } from 'react-native';
237
+ export function MyComponent() {
238
+ return <View className="light:h-screen p-4" />;
239
+ }
240
+ `;
241
+
242
+ expect(() => transform(input, undefined, true)).toThrow(
243
+ /w-screen and h-screen cannot be combined with color scheme modifiers/,
244
+ );
245
+ });
246
+
247
+ it("should error when w-screen is combined with active: modifier", () => {
248
+ const input = `
249
+ import { Pressable } from 'react-native';
250
+ export function MyComponent() {
251
+ return <Pressable className="active:w-screen bg-blue-500" />;
252
+ }
253
+ `;
254
+
255
+ expect(() => transform(input, undefined, true)).toThrow(
256
+ /w-screen and h-screen cannot be combined with state modifiers/,
257
+ );
258
+ });
259
+
260
+ it("should error when h-screen is combined with hover: modifier", () => {
261
+ const input = `
262
+ import { Pressable } from 'react-native';
263
+ export function MyComponent() {
264
+ return <Pressable className="hover:h-screen p-4" />;
265
+ }
266
+ `;
267
+
268
+ expect(() => transform(input, undefined, true)).toThrow(
269
+ /w-screen and h-screen cannot be combined with state modifiers/,
270
+ );
271
+ });
272
+
273
+ it("should error when w-screen is combined with ios: modifier", () => {
274
+ const input = `
275
+ import { View } from 'react-native';
276
+ export function MyComponent() {
277
+ return <View className="ios:w-screen bg-white" />;
278
+ }
279
+ `;
280
+
281
+ expect(() => transform(input, undefined, true)).toThrow(
282
+ /w-screen and h-screen cannot be combined with.*platform modifiers/,
283
+ );
284
+ });
285
+
286
+ it("should error when h-screen is combined with android: modifier", () => {
287
+ const input = `
288
+ import { View } from 'react-native';
289
+ export function MyComponent() {
290
+ return <View className="android:h-screen p-4" />;
291
+ }
292
+ `;
293
+
294
+ expect(() => transform(input, undefined, true)).toThrow(
295
+ /w-screen and h-screen cannot be combined with.*platform modifiers/,
296
+ );
297
+ });
298
+
299
+ it("should error when w-screen is used in tw`` call", () => {
300
+ const input = `
301
+ import { tw } from '@mgcrea/react-native-tailwind';
302
+ export function MyComponent() {
303
+ const styles = tw\`w-screen bg-white\`;
304
+ return <View style={styles.style} />;
305
+ }
306
+ `;
307
+
308
+ expect(() => transform(input, undefined, true)).toThrow(
309
+ /w-screen and h-screen are not supported in tw.*or twStyle/,
310
+ );
311
+ });
312
+
313
+ it("should error when h-screen is used in twStyle() call", () => {
314
+ const input = `
315
+ import { twStyle } from '@mgcrea/react-native-tailwind';
316
+ export function MyComponent() {
317
+ const styles = twStyle('h-screen p-4');
318
+ return <View style={styles.style} />;
319
+ }
320
+ `;
321
+
322
+ expect(() => transform(input, undefined, true)).toThrow(
323
+ /w-screen and h-screen are not supported in tw.*or twStyle/,
324
+ );
325
+ });
326
+
327
+ it("should properly merge w-screen with Pressable style function", () => {
328
+ const input = `
329
+ import { Pressable } from 'react-native';
330
+ export function MyComponent() {
331
+ return (
332
+ <Pressable
333
+ className="w-screen bg-white"
334
+ style={({ pressed }) => [pressed && { opacity: 0.5 }]}
335
+ />
336
+ );
337
+ }
338
+ `;
339
+
340
+ const output = transform(input, undefined, true);
341
+
342
+ // Should inject useWindowDimensions hook
343
+ expect(output).toContain("useWindowDimensions");
344
+ expect(output).toContain("_twDimensions");
345
+
346
+ // Should wrap the style function properly
347
+ expect(output).toContain("_state");
348
+ // Should contain the runtime dimension access
349
+ expect(output).toContain("_twDimensions.width");
350
+ // Should call the existing function
351
+ expect(output).toContain("pressed");
352
+ });
353
+
354
+ it("should properly merge h-screen with arrow function style", () => {
355
+ const input = `
356
+ import { Pressable } from 'react-native';
357
+ export function MyComponent() {
358
+ return (
359
+ <Pressable
360
+ className="h-screen p-4"
361
+ style={(state) => state.pressed ? { opacity: 0.8 } : null}
362
+ />
363
+ );
364
+ }
365
+ `;
366
+
367
+ const output = transform(input, undefined, true);
368
+
369
+ // Should inject useWindowDimensions hook
370
+ expect(output).toContain("useWindowDimensions");
371
+ expect(output).toContain("_twDimensions");
372
+
373
+ // Should wrap the style function
374
+ expect(output).toContain("_state");
375
+ // Should contain the runtime dimension access
376
+ expect(output).toContain("_twDimensions.height");
377
+ // Should call the original function
378
+ expect(output).toContain("state.pressed");
379
+ });
380
+
381
+ it("should error when w-screen is in base classes with dark: modifier", () => {
382
+ const input = `
383
+ import { View } from 'react-native';
384
+ export function MyComponent() {
385
+ return <View className="w-screen dark:bg-black" />;
386
+ }
387
+ `;
388
+
389
+ expect(() => transform(input, undefined, true)).toThrow(
390
+ /w-screen and h-screen cannot be combined with modifiers/,
391
+ );
392
+ });
393
+
394
+ it("should error when w-screen is in base classes with ios: modifier", () => {
395
+ const input = `
396
+ import { View } from 'react-native';
397
+ export function MyComponent() {
398
+ return <View className="w-screen ios:p-4" />;
399
+ }
400
+ `;
401
+
402
+ expect(() => transform(input, undefined, true)).toThrow(
403
+ /w-screen and h-screen cannot be combined with modifiers/,
404
+ );
405
+ });
406
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ImportDeclaration visitor - tracks existing imports
3
+ */
4
+ import type { NodePath } from "@babel/core";
5
+ import type * as BabelTypes from "@babel/types";
6
+ import type { PluginState } from "../state.js";
7
+ /**
8
+ * ImportDeclaration visitor
9
+ * Tracks existing imports from react-native and the main package
10
+ */
11
+ export declare function importDeclarationVisitor(path: NodePath<BabelTypes.ImportDeclaration>, state: PluginState, t: typeof BabelTypes): void;
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { transform } from "../../../../test/helpers/babelTransform.js";
3
+
4
+ describe("imports visitor - import tracking and injection", () => {
5
+ it("should not add StyleSheet import to files without className usage", () => {
6
+ const input = `
7
+ import { View, Text } from 'react-native';
8
+
9
+ function MyComponent() {
10
+ return <View><Text>Hello</Text></View>;
11
+ }
12
+ `;
13
+
14
+ const output = transform(input, undefined, true);
15
+
16
+ // Should not mutate the import by adding StyleSheet
17
+ // Count occurrences of "StyleSheet" in output
18
+ const styleSheetCount = (output.match(/StyleSheet/g) ?? []).length;
19
+ expect(styleSheetCount).toBe(0);
20
+
21
+ // Should not have _twStyles definition
22
+ expect(output).not.toContain("_twStyles");
23
+ expect(output).not.toContain("StyleSheet.create");
24
+
25
+ // Original imports should remain unchanged
26
+ expect(output).toContain("View");
27
+ expect(output).toContain("Text");
28
+ });
29
+
30
+ it("should add StyleSheet import only when className is used", () => {
31
+ const input = `
32
+ import { View } from 'react-native';
33
+
34
+ function MyComponent() {
35
+ return <View className="m-4 p-2" />;
36
+ }
37
+ `;
38
+
39
+ const output = transform(input, undefined, true);
40
+
41
+ // Should have StyleSheet import (both single and double quotes)
42
+ expect(output).toMatch(/import.*StyleSheet.*from ['"]react-native['"]|require\(['"]react-native['"]\)/);
43
+
44
+ // Should have _twStyles definition
45
+ expect(output).toContain("_twStyles");
46
+ expect(output).toContain("StyleSheet.create");
47
+ });
48
+
49
+ it("should add Platform import only when platform modifiers are used", () => {
50
+ const input = `
51
+ import { View } from 'react-native';
52
+
53
+ function MyComponent() {
54
+ return <View className="ios:m-4 android:m-2" />;
55
+ }
56
+ `;
57
+
58
+ const output = transform(input, undefined, true);
59
+
60
+ // Should have Platform import
61
+ expect(output).toContain("Platform");
62
+
63
+ // Should have StyleSheet import too
64
+ expect(output).toContain("StyleSheet");
65
+
66
+ // Should use Platform.select
67
+ expect(output).toContain("Platform.select");
68
+ });
69
+
70
+ it("should not add Platform import without platform modifiers", () => {
71
+ const input = `
72
+ import { View } from 'react-native';
73
+
74
+ function MyComponent() {
75
+ return <View className="m-4 p-2" />;
76
+ }
77
+ `;
78
+
79
+ const output = transform(input, undefined, true);
80
+
81
+ // Should not have Platform import
82
+ const platformCount = (output.match(/Platform/g) ?? []).length;
83
+ expect(platformCount).toBe(0);
84
+
85
+ // Should still have StyleSheet
86
+ expect(output).toContain("StyleSheet");
87
+ });
88
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * ImportDeclaration visitor - tracks existing imports
3
+ */
4
+
5
+ import type { NodePath } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+ import type { PluginState } from "../state.js";
8
+
9
+ /**
10
+ * ImportDeclaration visitor
11
+ * Tracks existing imports from react-native and the main package
12
+ */
13
+ export function importDeclarationVisitor(
14
+ path: NodePath<BabelTypes.ImportDeclaration>,
15
+ state: PluginState,
16
+ t: typeof BabelTypes,
17
+ ): void {
18
+ const node = path.node;
19
+
20
+ // Track react-native StyleSheet, Platform, and I18nManager imports
21
+ if (node.source.value === "react-native") {
22
+ const specifiers = node.specifiers;
23
+
24
+ const hasStyleSheet = specifiers.some((spec) => {
25
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
26
+ return spec.imported.name === "StyleSheet";
27
+ }
28
+ return false;
29
+ });
30
+
31
+ const hasPlatform = specifiers.some((spec) => {
32
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
33
+ return spec.imported.name === "Platform";
34
+ }
35
+ return false;
36
+ });
37
+
38
+ // Check for I18nManager import (only value imports, not type-only)
39
+ if (node.importKind !== "type") {
40
+ for (const spec of specifiers) {
41
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
42
+ if (spec.imported.name === "I18nManager") {
43
+ state.hasI18nManagerImport = true;
44
+ // Track the local identifier (handles aliased imports)
45
+ // e.g., import { I18nManager as RTL } → local name is 'RTL'
46
+ state.i18nManagerLocalIdentifier = spec.local.name;
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ // Check for useWindowDimensions import (only value imports, not type-only)
54
+ if (node.importKind !== "type") {
55
+ for (const spec of specifiers) {
56
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
57
+ if (spec.imported.name === "useWindowDimensions") {
58
+ state.hasWindowDimensionsImport = true;
59
+ // Track the local identifier (handles aliased imports)
60
+ // e.g., import { useWindowDimensions as useDims } → local name is 'useDims'
61
+ state.windowDimensionsLocalIdentifier = spec.local.name;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ // Only track if imports exist - don't mutate yet
69
+ // Actual import injection happens in Program.exit only if needed
70
+ if (hasStyleSheet) {
71
+ state.hasStyleSheetImport = true;
72
+ }
73
+
74
+ if (hasPlatform) {
75
+ state.hasPlatformImport = true;
76
+ }
77
+
78
+ // Store reference to the react-native import for later modification if needed
79
+ state.reactNativeImportPath = path;
80
+ }
81
+
82
+ // Track color scheme hook import from the configured source
83
+ // (default: react-native, but can be custom like @/hooks/useColorScheme)
84
+ // Only track value imports (not type-only imports which get erased)
85
+ if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
86
+ const specifiers = node.specifiers;
87
+
88
+ for (const spec of specifiers) {
89
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
90
+ if (spec.imported.name === state.colorSchemeHookName) {
91
+ state.hasColorSchemeImport = true;
92
+ // Track the local identifier (handles aliased imports)
93
+ // e.g., import { useTheme as navTheme } → local name is 'navTheme'
94
+ state.colorSchemeLocalIdentifier = spec.local.name;
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ // Track tw/twStyle imports from main package (for compile-time transformation)
102
+ if (node.source.value === "@mgcrea/react-native-tailwind") {
103
+ const specifiers = node.specifiers;
104
+ specifiers.forEach((spec) => {
105
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
106
+ const importedName = spec.imported.name;
107
+ if (importedName === "tw" || importedName === "twStyle") {
108
+ // Track the local name (could be renamed: import { tw as customTw })
109
+ const localName = spec.local.name;
110
+ state.twImportNames.add(localName);
111
+ // Don't set hasTwImport yet - only set it when we successfully transform a call
112
+ }
113
+ }
114
+ });
115
+ }
116
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Program visitor - entry and exit points for file processing
3
+ */
4
+ import type { NodePath } from "@babel/core";
5
+ import type * as BabelTypes from "@babel/types";
6
+ import type { PluginState } from "../state.js";
7
+ /**
8
+ * Program enter visitor - initialize state for each file
9
+ */
10
+ export declare function programEnter(_path: NodePath<BabelTypes.Program>, _state: PluginState): void;
11
+ /**
12
+ * Program exit visitor - finalize transformations
13
+ * Injects imports, hooks, and StyleSheet.create
14
+ */
15
+ export declare function programExit(path: NodePath<BabelTypes.Program>, state: PluginState, t: typeof BabelTypes): void;