@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,652 @@
1
+ /**
2
+ * JSXAttribute visitor - handles className attribute transformations
3
+ */
4
+
5
+ import type { NodePath } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+ import type { ParsedModifier, StateModifierType } from "../../../parser/index.js";
8
+ import {
9
+ expandSchemeModifier,
10
+ isColorSchemeModifier,
11
+ isDirectionalModifier,
12
+ isPlatformModifier,
13
+ isSchemeModifier,
14
+ isStateModifier,
15
+ parseClassName,
16
+ parsePlaceholderClasses,
17
+ splitModifierClasses,
18
+ } from "../../../parser/index.js";
19
+ import { generateStyleKey } from "../../../utils/styleKey.js";
20
+ import { getTargetStyleProp, isAttributeSupported } from "../../utils/attributeMatchers.js";
21
+ import { processColorSchemeModifiers } from "../../utils/colorSchemeModifierProcessing.js";
22
+ import { getComponentModifierSupport, getStatePropertyForModifier } from "../../utils/componentSupport.js";
23
+ import { processDirectionalModifiers } from "../../utils/directionalModifierProcessing.js";
24
+ import { processDynamicExpression } from "../../utils/dynamicProcessing.js";
25
+ import { createStyleFunction, processStaticClassNameWithModifiers } from "../../utils/modifierProcessing.js";
26
+ import { processPlatformModifiers } from "../../utils/platformModifierProcessing.js";
27
+ import {
28
+ addOrMergePlaceholderTextColorProp,
29
+ findStyleAttribute,
30
+ mergeDynamicStyleAttribute,
31
+ mergeStyleAttribute,
32
+ mergeStyleFunctionAttribute,
33
+ replaceDynamicWithStyleAttribute,
34
+ replaceWithStyleAttribute,
35
+ replaceWithStyleFunctionAttribute,
36
+ } from "../../utils/styleTransforms.js";
37
+ import {
38
+ createRuntimeDimensionObject,
39
+ hasRuntimeDimensions,
40
+ splitStaticAndRuntimeStyles,
41
+ } from "../../utils/windowDimensionsProcessing.js";
42
+ import { findComponentScope } from "../componentScope.js";
43
+ import type { PluginState } from "../state.js";
44
+
45
+ /**
46
+ * JSXAttribute visitor
47
+ * Handles all className attribute transformations (static, dynamic, modifiers)
48
+ */
49
+ export function jsxAttributeVisitor(
50
+ path: NodePath<BabelTypes.JSXAttribute>,
51
+ state: PluginState,
52
+ t: typeof BabelTypes,
53
+ ): void {
54
+ const node = path.node;
55
+
56
+ // Ensure we have a JSXIdentifier name (not JSXNamespacedName)
57
+ if (!t.isJSXIdentifier(node.name)) {
58
+ return;
59
+ }
60
+
61
+ const attributeName = node.name.name;
62
+
63
+ // Only process configured className-like attributes
64
+ if (!isAttributeSupported(attributeName, state.supportedAttributes, state.attributePatterns)) {
65
+ return;
66
+ }
67
+
68
+ const value = node.value;
69
+
70
+ // Determine target style prop based on attribute name
71
+ const targetStyleProp = getTargetStyleProp(attributeName);
72
+
73
+ /**
74
+ * Process static className string (handles both direct StringLiteral and StringLiteral in JSXExpressionContainer)
75
+ */
76
+ const processStaticClassName = (className: string): boolean => {
77
+ const trimmedClassName = className.trim();
78
+
79
+ // Skip empty classNames
80
+ if (!trimmedClassName) {
81
+ path.remove();
82
+ return true;
83
+ }
84
+
85
+ state.hasClassNames = true;
86
+
87
+ // Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:, dark:, light:, scheme:)
88
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
89
+
90
+ // Expand scheme: modifiers into dark: and light: modifiers
91
+ const modifierClasses: ParsedModifier[] = [];
92
+ for (const modifier of rawModifierClasses) {
93
+ if (isSchemeModifier(modifier.modifier)) {
94
+ // Expand scheme: into dark: and light:
95
+ const expanded = expandSchemeModifier(
96
+ modifier,
97
+ state.customTheme.colors ?? {},
98
+ state.schemeModifierConfig.darkSuffix,
99
+ state.schemeModifierConfig.lightSuffix,
100
+ );
101
+ modifierClasses.push(...expanded);
102
+ } else {
103
+ // Keep other modifiers as-is
104
+ modifierClasses.push(modifier);
105
+ }
106
+ }
107
+
108
+ // Separate modifiers by type
109
+ const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
110
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
111
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
112
+ const directionalModifiers = modifierClasses.filter((m) => isDirectionalModifier(m.modifier));
113
+ const stateModifiers = modifierClasses.filter(
114
+ (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
115
+ );
116
+
117
+ // Handle placeholder modifiers first (they generate placeholderTextColor prop, not style)
118
+ if (placeholderModifiers.length > 0) {
119
+ // Check if this is a TextInput component (placeholder only works on TextInput)
120
+ const jsxOpeningElement = path.parent as BabelTypes.JSXOpeningElement;
121
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
122
+
123
+ if (componentSupport?.supportedModifiers.includes("placeholder")) {
124
+ const placeholderClasses = placeholderModifiers.map((m) => m.baseClass).join(" ");
125
+ const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.customTheme.colors);
126
+
127
+ if (placeholderColor) {
128
+ // Add or merge placeholderTextColor prop
129
+ addOrMergePlaceholderTextColorProp(jsxOpeningElement, placeholderColor, t);
130
+ }
131
+ } else {
132
+ // Warn if placeholder modifier used on non-TextInput element
133
+ if (process.env.NODE_ENV !== "production") {
134
+ console.warn(
135
+ `[react-native-tailwind] placeholder: modifier can only be used on TextInput component at ${state.file.opts.filename ?? "unknown"}`,
136
+ );
137
+ }
138
+ }
139
+ }
140
+
141
+ // Handle combination of modifiers
142
+ const hasPlatformModifiers = platformModifiers.length > 0;
143
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
144
+ const hasDirectionalModifiers = directionalModifiers.length > 0;
145
+ const hasStateModifiers = stateModifiers.length > 0;
146
+ const hasBaseClasses = baseClasses.length > 0;
147
+
148
+ // If we have color scheme modifiers, we need to track the parent function component
149
+ let componentScope: NodePath<BabelTypes.Function> | null = null;
150
+ if (hasColorSchemeModifiers) {
151
+ componentScope = findComponentScope(path, t);
152
+ if (componentScope) {
153
+ state.functionComponentsNeedingColorScheme.add(componentScope);
154
+ } else {
155
+ // Warn if color scheme modifiers used in invalid context (class component, nested function)
156
+ if (process.env.NODE_ENV !== "production") {
157
+ console.warn(
158
+ `[react-native-tailwind] dark:/light: modifiers require a function component scope. ` +
159
+ `Found in non-component context at ${state.file.opts.filename ?? "unknown"}. ` +
160
+ `These modifiers are not supported in class components or nested callbacks.`,
161
+ );
162
+ }
163
+ }
164
+ }
165
+
166
+ // If we have multiple modifier types, combine them in an array expression
167
+ // For state modifiers, wrap in arrow function; for color scheme and directional, they're just conditionals
168
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers || hasDirectionalModifiers)) {
169
+ // Get the JSX opening element for component support checking
170
+ const jsxOpeningElement = path.parent;
171
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
172
+
173
+ if (componentSupport) {
174
+ // Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, directionalConditionals, stateConditionals]
175
+ const styleArrayElements: BabelTypes.Expression[] = [];
176
+
177
+ // Add base classes
178
+ if (hasBaseClasses) {
179
+ const baseClassName = baseClasses.join(" ");
180
+ const baseStyleObject = parseClassName(baseClassName, state.customTheme);
181
+
182
+ // Check for runtime dimensions (w-screen, h-screen) in base classes
183
+ if (hasRuntimeDimensions(baseStyleObject)) {
184
+ throw path.buildCodeFrameError(
185
+ `w-screen and h-screen cannot be combined with modifiers. ` +
186
+ `Found: "${baseClassName}" with state, platform, color scheme, or directional modifiers. ` +
187
+ `Use w-screen/h-screen without modifiers instead.`,
188
+ );
189
+ }
190
+
191
+ const baseStyleKey = generateStyleKey(baseClassName);
192
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
193
+ styleArrayElements.push(
194
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
195
+ );
196
+ }
197
+
198
+ // Add platform modifiers as Platform.select()
199
+ if (hasPlatformModifiers) {
200
+ const platformSelectExpression = processPlatformModifiers(
201
+ platformModifiers,
202
+ state,
203
+ parseClassName,
204
+ generateStyleKey,
205
+ t,
206
+ );
207
+ styleArrayElements.push(platformSelectExpression);
208
+ }
209
+
210
+ // Add color scheme modifiers as conditionals (only if component scope exists)
211
+ if (hasColorSchemeModifiers && componentScope) {
212
+ const colorSchemeConditionals = processColorSchemeModifiers(
213
+ colorSchemeModifiers,
214
+ state,
215
+ parseClassName,
216
+ generateStyleKey,
217
+ t,
218
+ );
219
+ styleArrayElements.push(...colorSchemeConditionals);
220
+ }
221
+
222
+ // Add directional modifiers as conditionals
223
+ if (hasDirectionalModifiers) {
224
+ const directionalConditionals = processDirectionalModifiers(
225
+ directionalModifiers,
226
+ state,
227
+ parseClassName,
228
+ generateStyleKey,
229
+ t,
230
+ );
231
+ styleArrayElements.push(...directionalConditionals);
232
+ }
233
+
234
+ // Add state modifiers as conditionals
235
+ // Group by modifier type
236
+ const modifiersByType = new Map<StateModifierType, ParsedModifier[]>();
237
+ for (const mod of stateModifiers) {
238
+ const modType = mod.modifier as StateModifierType;
239
+ if (!modifiersByType.has(modType)) {
240
+ modifiersByType.set(modType, []);
241
+ }
242
+ modifiersByType.get(modType)?.push(mod);
243
+ }
244
+
245
+ // Build conditionals for each state modifier type
246
+ for (const [modifierType, modifiers] of modifiersByType) {
247
+ if (!componentSupport.supportedModifiers.includes(modifierType)) {
248
+ continue; // Skip unsupported modifiers
249
+ }
250
+
251
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
252
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
253
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
254
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
255
+
256
+ const stateProperty = getStatePropertyForModifier(modifierType);
257
+ const conditionalExpression = t.logicalExpression(
258
+ "&&",
259
+ t.identifier(stateProperty),
260
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey)),
261
+ );
262
+
263
+ styleArrayElements.push(conditionalExpression);
264
+ }
265
+
266
+ // Wrap in arrow function for state support
267
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter((mod) =>
268
+ componentSupport.supportedModifiers.includes(mod),
269
+ );
270
+ const styleArrayExpression = t.arrayExpression(styleArrayElements);
271
+ const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
272
+
273
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
274
+ if (styleAttribute) {
275
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
276
+ } else {
277
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
278
+ }
279
+ return true;
280
+ } else {
281
+ // Component doesn't support state modifiers, but we can still use platform modifiers
282
+ // Fall through to platform-only handling
283
+ }
284
+ }
285
+
286
+ // Handle platform, color scheme, and/or directional modifiers (no state modifiers)
287
+ if ((hasPlatformModifiers || hasColorSchemeModifiers || hasDirectionalModifiers) && !hasStateModifiers) {
288
+ // Build style array/expression: [baseStyle, Platform.select(...), colorSchemeConditionals, directionalConditionals]
289
+ const styleExpressions: BabelTypes.Expression[] = [];
290
+
291
+ // Add base classes
292
+ if (hasBaseClasses) {
293
+ const baseClassName = baseClasses.join(" ");
294
+ const baseStyleObject = parseClassName(baseClassName, state.customTheme);
295
+
296
+ // Check for runtime dimensions (w-screen, h-screen) in base classes
297
+ if (hasRuntimeDimensions(baseStyleObject)) {
298
+ throw path.buildCodeFrameError(
299
+ `w-screen and h-screen cannot be combined with modifiers. ` +
300
+ `Found: "${baseClassName}" with platform, color scheme, or directional modifiers. ` +
301
+ `Use w-screen/h-screen without modifiers instead.`,
302
+ );
303
+ }
304
+
305
+ const baseStyleKey = generateStyleKey(baseClassName);
306
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
307
+ styleExpressions.push(
308
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
309
+ );
310
+ }
311
+
312
+ // Add platform modifiers as Platform.select()
313
+ if (hasPlatformModifiers) {
314
+ const platformSelectExpression = processPlatformModifiers(
315
+ platformModifiers,
316
+ state,
317
+ parseClassName,
318
+ generateStyleKey,
319
+ t,
320
+ );
321
+ styleExpressions.push(platformSelectExpression);
322
+ }
323
+
324
+ // Add color scheme modifiers as conditionals (only if we have a valid component scope)
325
+ if (hasColorSchemeModifiers && componentScope) {
326
+ const colorSchemeConditionals = processColorSchemeModifiers(
327
+ colorSchemeModifiers,
328
+ state,
329
+ parseClassName,
330
+ generateStyleKey,
331
+ t,
332
+ );
333
+ styleExpressions.push(...colorSchemeConditionals);
334
+ }
335
+
336
+ // Add directional modifiers as conditionals
337
+ if (hasDirectionalModifiers) {
338
+ const directionalConditionals = processDirectionalModifiers(
339
+ directionalModifiers,
340
+ state,
341
+ parseClassName,
342
+ generateStyleKey,
343
+ t,
344
+ );
345
+ styleExpressions.push(...directionalConditionals);
346
+ }
347
+
348
+ // Generate style attribute
349
+ const styleExpression =
350
+ styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
351
+
352
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
353
+ if (styleAttribute) {
354
+ // Merge with existing style attribute
355
+ const existingStyle = styleAttribute.value;
356
+ if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
357
+ const existing = existingStyle.expression;
358
+ // Merge as array: [ourStyles, existingStyles]
359
+ const mergedArray = t.isArrayExpression(existing)
360
+ ? t.arrayExpression([styleExpression, ...existing.elements])
361
+ : t.arrayExpression([styleExpression, existing]);
362
+ styleAttribute.value = t.jsxExpressionContainer(mergedArray);
363
+ } else {
364
+ styleAttribute.value = t.jsxExpressionContainer(styleExpression);
365
+ }
366
+ path.remove();
367
+ } else {
368
+ // Replace className with style prop containing our expression
369
+ path.node.name = t.jsxIdentifier(targetStyleProp);
370
+ path.node.value = t.jsxExpressionContainer(styleExpression);
371
+ }
372
+ return true;
373
+ }
374
+
375
+ // If there are state modifiers (and no platform modifiers), check if this component supports them
376
+ if (hasStateModifiers) {
377
+ // Get the JSX opening element (the direct parent of the attribute)
378
+ const jsxOpeningElement = path.parent;
379
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
380
+
381
+ if (componentSupport) {
382
+ // Get modifier types used in className
383
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
384
+
385
+ // Check if all modifiers are supported by this component
386
+ const unsupportedModifiers = usedModifiers.filter(
387
+ (mod) => !componentSupport.supportedModifiers.includes(mod),
388
+ );
389
+
390
+ if (unsupportedModifiers.length > 0) {
391
+ // Warn about unsupported modifiers
392
+ if (process.env.NODE_ENV !== "production") {
393
+ console.warn(
394
+ `[react-native-tailwind] Modifiers (${unsupportedModifiers.map((m) => `${m}:`).join(", ")}) are not supported on ${componentSupport.component} component at ${state.file.opts.filename ?? "unknown"}. ` +
395
+ `Supported modifiers: ${componentSupport.supportedModifiers.join(", ")}`,
396
+ );
397
+ }
398
+ // Filter out unsupported modifiers
399
+ const supportedModifierClasses = stateModifiers.filter((m) =>
400
+ componentSupport.supportedModifiers.includes(m.modifier),
401
+ );
402
+
403
+ // If no supported modifiers remain, fall through to normal processing
404
+ if (supportedModifierClasses.length === 0) {
405
+ // Continue to normal processing
406
+ } else {
407
+ // Process only supported modifiers
408
+ const filteredClassName =
409
+ baseClasses.join(" ") +
410
+ " " +
411
+ supportedModifierClasses.map((m) => `${m.modifier}:${m.baseClass}`).join(" ");
412
+ const styleExpression = processStaticClassNameWithModifiers(
413
+ filteredClassName.trim(),
414
+ state,
415
+ parseClassName,
416
+ generateStyleKey,
417
+ splitModifierClasses,
418
+ t,
419
+ );
420
+ const modifierTypes = Array.from(new Set(supportedModifierClasses.map((m) => m.modifier)));
421
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
422
+
423
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
424
+
425
+ if (styleAttribute) {
426
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
427
+ } else {
428
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
429
+ }
430
+ return true;
431
+ }
432
+ } else {
433
+ // All modifiers are supported - process normally
434
+ const styleExpression = processStaticClassNameWithModifiers(
435
+ trimmedClassName,
436
+ state,
437
+ parseClassName,
438
+ generateStyleKey,
439
+ splitModifierClasses,
440
+ t,
441
+ );
442
+ const modifierTypes = usedModifiers;
443
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
444
+
445
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
446
+
447
+ if (styleAttribute) {
448
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
449
+ } else {
450
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
451
+ }
452
+ return true;
453
+ }
454
+ } else {
455
+ // Component doesn't support any modifiers
456
+ if (process.env.NODE_ENV !== "production") {
457
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
458
+ console.warn(
459
+ `[react-native-tailwind] Modifiers (${usedModifiers.map((m) => `${m}:`).join(", ")}) can only be used on compatible components (Pressable, TextInput). Found on unsupported element at ${state.file.opts.filename ?? "unknown"}`,
460
+ );
461
+ }
462
+ // Fall through to normal processing (ignore modifiers)
463
+ }
464
+ }
465
+
466
+ // Normal processing without modifiers
467
+ // Use baseClasses only (placeholder modifiers already handled separately)
468
+ const classNameForStyle = baseClasses.join(" ");
469
+ if (!classNameForStyle) {
470
+ // No base classes, only had placeholder modifiers - just remove className
471
+ path.remove();
472
+ return true;
473
+ }
474
+
475
+ const styleObject = parseClassName(classNameForStyle, state.customTheme);
476
+
477
+ // Check if the style object contains runtime dimension markers (w-screen, h-screen)
478
+ if (hasRuntimeDimensions(styleObject)) {
479
+ // Split into static and runtime parts
480
+ const { static: staticStyles, runtime: runtimeStyles } = splitStaticAndRuntimeStyles(styleObject);
481
+
482
+ // Track component scope for hook injection
483
+ const componentScope = findComponentScope(path, t);
484
+ if (componentScope) {
485
+ state.hasClassNames = true; // Mark that we have classNames to process
486
+ state.functionComponentsNeedingWindowDimensions.add(componentScope);
487
+ state.needsWindowDimensionsImport = true;
488
+
489
+ // Build style array: [staticStyles, { width: _twDimensions.width }]
490
+ const styleExpressions: BabelTypes.Expression[] = [];
491
+
492
+ // Add static styles if any
493
+ if (Object.keys(staticStyles).length > 0) {
494
+ const styleKey = generateStyleKey(classNameForStyle);
495
+ state.styleRegistry.set(styleKey, staticStyles);
496
+ styleExpressions.push(
497
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)),
498
+ );
499
+ }
500
+
501
+ // Add runtime dimension object
502
+ const runtimeDimensionObject = createRuntimeDimensionObject(runtimeStyles, state, t);
503
+ styleExpressions.push(runtimeDimensionObject);
504
+
505
+ // Create style array or single expression
506
+ const styleExpression =
507
+ styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
508
+
509
+ // Check if there's already a style prop on this element
510
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
511
+
512
+ if (styleAttribute) {
513
+ // Merge with existing style attribute
514
+ const existingStyle = styleAttribute.value;
515
+ if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
516
+ const existing = existingStyle.expression;
517
+
518
+ // Check if existing style is a function (e.g., Pressable's style prop)
519
+ if (t.isArrowFunctionExpression(existing) || t.isFunctionExpression(existing)) {
520
+ // Existing style is a function - create wrapper that calls it and merges results
521
+ // (_state) => [styleExpression, existingStyleFn(_state)]
522
+ const paramIdentifier = t.identifier("_state");
523
+ const functionCall = t.callExpression(existing, [paramIdentifier]);
524
+
525
+ const mergedArray = t.arrayExpression([styleExpression, functionCall]);
526
+
527
+ const wrappedFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
528
+ styleAttribute.value = t.jsxExpressionContainer(wrappedFunction);
529
+ } else {
530
+ // Merge as array: [ourStyles, existingStyles]
531
+ const mergedArray = t.isArrayExpression(existing)
532
+ ? t.arrayExpression([styleExpression, ...existing.elements])
533
+ : t.arrayExpression([styleExpression, existing]);
534
+ styleAttribute.value = t.jsxExpressionContainer(mergedArray);
535
+ }
536
+ } else {
537
+ styleAttribute.value = t.jsxExpressionContainer(styleExpression);
538
+ }
539
+ path.remove();
540
+ } else {
541
+ // Replace className with style prop containing runtime expression
542
+ path.node.name = t.jsxIdentifier(targetStyleProp);
543
+ path.node.value = t.jsxExpressionContainer(styleExpression);
544
+ }
545
+ return true;
546
+ } else {
547
+ // Warn if w-screen/h-screen used in invalid context
548
+ if (process.env.NODE_ENV !== "production") {
549
+ console.warn(
550
+ `[react-native-tailwind] w-screen/h-screen classes require a function component scope. ` +
551
+ `Found in non-component context at ${state.file.opts.filename ?? "unknown"}. ` +
552
+ `These classes are not supported in class components or nested callbacks.`,
553
+ );
554
+ }
555
+ // Fall through to normal processing (will generate static styles, which won't work correctly)
556
+ }
557
+ }
558
+
559
+ const styleKey = generateStyleKey(classNameForStyle);
560
+ state.styleRegistry.set(styleKey, styleObject);
561
+
562
+ // Check if there's already a style prop on this element
563
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
564
+
565
+ if (styleAttribute) {
566
+ // Merge with existing style prop
567
+ mergeStyleAttribute(path, styleAttribute, styleKey, state.stylesIdentifier, t);
568
+ } else {
569
+ // Replace className with style prop
570
+ replaceWithStyleAttribute(path, styleKey, targetStyleProp, state.stylesIdentifier, t);
571
+ }
572
+ return true;
573
+ };
574
+
575
+ // Handle static string literals
576
+ if (t.isStringLiteral(value)) {
577
+ if (processStaticClassName(value.value)) {
578
+ return;
579
+ }
580
+ }
581
+
582
+ // Handle dynamic expressions (JSXExpressionContainer)
583
+ if (t.isJSXExpressionContainer(value)) {
584
+ const expression = value.expression;
585
+
586
+ // Skip JSXEmptyExpression
587
+ if (t.isJSXEmptyExpression(expression)) {
588
+ return;
589
+ }
590
+
591
+ // Fast path: Support string literals wrapped in JSXExpressionContainer: className={"flex-row"}
592
+ if (t.isStringLiteral(expression)) {
593
+ if (processStaticClassName(expression.value)) {
594
+ return;
595
+ }
596
+ }
597
+
598
+ try {
599
+ // Find component scope for color scheme modifiers
600
+ const componentScope = findComponentScope(path, t);
601
+
602
+ // Process dynamic expression with modifier support
603
+ const result = processDynamicExpression(
604
+ expression,
605
+ state,
606
+ parseClassName,
607
+ generateStyleKey,
608
+ splitModifierClasses,
609
+ processPlatformModifiers,
610
+ processColorSchemeModifiers,
611
+ componentScope,
612
+ isPlatformModifier as (modifier: unknown) => boolean,
613
+ isColorSchemeModifier as (modifier: unknown) => boolean,
614
+ isSchemeModifier as (modifier: unknown) => boolean,
615
+ expandSchemeModifier,
616
+ t,
617
+ );
618
+
619
+ if (result) {
620
+ state.hasClassNames = true;
621
+
622
+ // Check if there's already a style prop on this element
623
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
624
+
625
+ if (styleAttribute) {
626
+ // Merge with existing style prop
627
+ mergeDynamicStyleAttribute(path, styleAttribute, result, t);
628
+ } else {
629
+ // Replace className with style prop
630
+ replaceDynamicWithStyleAttribute(path, result, targetStyleProp, t);
631
+ }
632
+ return;
633
+ }
634
+ } catch (error) {
635
+ // Fall through to warning
636
+ if (process.env.NODE_ENV !== "production") {
637
+ console.warn(
638
+ `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`,
639
+ );
640
+ }
641
+ }
642
+ }
643
+
644
+ // Unsupported dynamic className - warn in development
645
+ if (process.env.NODE_ENV !== "production") {
646
+ const filename = state.file.opts.filename ?? "unknown";
647
+ console.warn(
648
+ `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. ` +
649
+ `Use the ${targetStyleProp} prop for dynamic values.`,
650
+ );
651
+ }
652
+ }