@mgcrea/react-native-tailwind 0.12.1 → 0.13.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 (82) hide show
  1. package/README.md +29 -2014
  2. package/dist/babel/index.cjs +1462 -1155
  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 +119 -0
  6. package/dist/babel/plugin/state.ts +177 -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} +74 -674
  9. package/dist/babel/plugin/visitors/className.ts +624 -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 +101 -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 +99 -0
  17. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  18. package/dist/babel/plugin/visitors/tw.test.ts +620 -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/modifierProcessing.ts +21 -0
  27. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  28. package/dist/babel/utils/styleInjection.d.ts +15 -0
  29. package/dist/babel/utils/styleInjection.ts +115 -0
  30. package/dist/babel/utils/twProcessing.ts +11 -0
  31. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  32. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  33. package/dist/components/TouchableOpacity.d.ts +35 -0
  34. package/dist/components/TouchableOpacity.js +1 -0
  35. package/dist/components/index.d.ts +3 -0
  36. package/dist/components/index.js +1 -0
  37. package/dist/config/markers.d.ts +5 -0
  38. package/dist/config/markers.js +1 -0
  39. package/dist/index.d.ts +2 -5
  40. package/dist/index.js +1 -1
  41. package/dist/parser/borders.d.ts +3 -1
  42. package/dist/parser/borders.js +1 -1
  43. package/dist/parser/borders.test.js +1 -1
  44. package/dist/parser/colors.js +1 -1
  45. package/dist/parser/colors.test.js +1 -1
  46. package/dist/parser/index.js +1 -1
  47. package/dist/parser/sizing.js +1 -1
  48. package/dist/runtime.cjs +1 -1
  49. package/dist/runtime.cjs.map +4 -4
  50. package/dist/runtime.js +1 -1
  51. package/dist/runtime.js.map +4 -4
  52. package/package.json +1 -1
  53. package/src/babel/plugin/componentScope.ts +87 -0
  54. package/src/babel/plugin/state.ts +177 -0
  55. package/src/babel/plugin/visitors/className.test.ts +1312 -0
  56. package/src/babel/plugin/visitors/className.ts +624 -0
  57. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  58. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  59. package/src/babel/plugin/visitors/imports.ts +101 -0
  60. package/src/babel/plugin/visitors/program.test.ts +325 -0
  61. package/src/babel/plugin/visitors/program.ts +99 -0
  62. package/src/babel/plugin/visitors/tw.test.ts +620 -0
  63. package/src/babel/plugin/visitors/tw.ts +148 -0
  64. package/src/babel/plugin.ts +28 -963
  65. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  66. package/src/babel/utils/componentSupport.test.ts +20 -7
  67. package/src/babel/utils/componentSupport.ts +2 -0
  68. package/src/babel/utils/modifierProcessing.ts +21 -0
  69. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  70. package/src/babel/utils/styleInjection.ts +115 -0
  71. package/src/babel/utils/twProcessing.ts +11 -0
  72. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  73. package/src/components/TouchableOpacity.tsx +71 -0
  74. package/src/components/index.ts +3 -0
  75. package/src/config/markers.ts +5 -0
  76. package/src/index.ts +4 -5
  77. package/src/parser/borders.test.ts +58 -0
  78. package/src/parser/borders.ts +18 -3
  79. package/src/parser/colors.test.ts +249 -0
  80. package/src/parser/colors.ts +38 -0
  81. package/src/parser/index.ts +2 -2
  82. package/src/parser/sizing.ts +11 -0
@@ -34,6 +34,197 @@ __export(index_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
+ // src/babel/config-loader.ts
38
+ var fs = __toESM(require("fs"), 1);
39
+ var path = __toESM(require("path"), 1);
40
+
41
+ // src/utils/flattenColors.ts
42
+ function flattenColors(colors, prefix = "") {
43
+ const result = {};
44
+ for (const [key, value] of Object.entries(colors)) {
45
+ const newKey = key === "DEFAULT" && prefix ? prefix : prefix ? `${prefix}-${key}` : key;
46
+ if (typeof value === "string") {
47
+ result[newKey] = value;
48
+ } else if (typeof value === "object" && value !== null) {
49
+ Object.assign(result, flattenColors(value, newKey));
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+
55
+ // src/babel/config-loader.ts
56
+ var configCache = /* @__PURE__ */ new Map();
57
+ function findTailwindConfig(startDir) {
58
+ let currentDir = startDir;
59
+ const root = path.parse(currentDir).root;
60
+ const configNames = [
61
+ "tailwind.config.mjs",
62
+ "tailwind.config.js",
63
+ "tailwind.config.cjs",
64
+ "tailwind.config.ts"
65
+ ];
66
+ while (currentDir !== root) {
67
+ for (const configName of configNames) {
68
+ const configPath = path.join(currentDir, configName);
69
+ if (fs.existsSync(configPath)) {
70
+ return configPath;
71
+ }
72
+ }
73
+ currentDir = path.dirname(currentDir);
74
+ }
75
+ return null;
76
+ }
77
+ function loadTailwindConfig(configPath) {
78
+ if (configCache.has(configPath)) {
79
+ return configCache.get(configPath);
80
+ }
81
+ try {
82
+ const resolvedPath = require.resolve(configPath);
83
+ delete require.cache[resolvedPath];
84
+ const config = require(configPath);
85
+ const resolved = "default" in config ? config.default : config;
86
+ configCache.set(configPath, resolved);
87
+ return resolved;
88
+ } catch (error) {
89
+ if (process.env.NODE_ENV !== "production") {
90
+ console.warn(`[react-native-tailwind] Failed to load config from ${configPath}:`, error);
91
+ }
92
+ configCache.set(configPath, null);
93
+ return null;
94
+ }
95
+ }
96
+ function extractCustomTheme(filename) {
97
+ const projectDir = path.dirname(filename);
98
+ const configPath = findTailwindConfig(projectDir);
99
+ if (!configPath) {
100
+ return { colors: {}, fontFamily: {}, fontSize: {} };
101
+ }
102
+ const config = loadTailwindConfig(configPath);
103
+ if (!config?.theme) {
104
+ return { colors: {}, fontFamily: {}, fontSize: {} };
105
+ }
106
+ if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
107
+ console.warn(
108
+ "[react-native-tailwind] Using theme.colors will override all default colors. Use theme.extend.colors to add custom colors while keeping defaults."
109
+ );
110
+ }
111
+ const colors = config.theme.extend?.colors ?? config.theme.colors ?? {};
112
+ if (config.theme.fontFamily && !config.theme.extend?.fontFamily && process.env.NODE_ENV !== "production") {
113
+ console.warn(
114
+ "[react-native-tailwind] Using theme.fontFamily will override all default font families. Use theme.extend.fontFamily to add custom fonts while keeping defaults."
115
+ );
116
+ }
117
+ const fontFamily = config.theme.extend?.fontFamily ?? config.theme.fontFamily ?? {};
118
+ const fontFamilyResult = {};
119
+ for (const [key, value] of Object.entries(fontFamily)) {
120
+ if (Array.isArray(value)) {
121
+ fontFamilyResult[key] = value[0];
122
+ } else {
123
+ fontFamilyResult[key] = value;
124
+ }
125
+ }
126
+ if (config.theme.fontSize && !config.theme.extend?.fontSize && process.env.NODE_ENV !== "production") {
127
+ console.warn(
128
+ "[react-native-tailwind] Using theme.fontSize will override all default font sizes. Use theme.extend.fontSize to add custom font sizes while keeping defaults."
129
+ );
130
+ }
131
+ const fontSize = config.theme.extend?.fontSize ?? config.theme.fontSize ?? {};
132
+ const fontSizeResult = {};
133
+ for (const [key, value] of Object.entries(fontSize)) {
134
+ if (typeof value === "number") {
135
+ fontSizeResult[key] = value;
136
+ } else if (typeof value === "string") {
137
+ const parsed = parseFloat(value.replace(/px$/, ""));
138
+ if (!isNaN(parsed)) {
139
+ fontSizeResult[key] = parsed;
140
+ } else {
141
+ if (process.env.NODE_ENV !== "production") {
142
+ console.warn(
143
+ `[react-native-tailwind] Invalid fontSize value for "${key}": ${value}. Expected number or string like "18px".`
144
+ );
145
+ }
146
+ }
147
+ }
148
+ }
149
+ return {
150
+ colors: flattenColors(colors),
151
+ fontFamily: fontFamilyResult,
152
+ fontSize: fontSizeResult
153
+ };
154
+ }
155
+
156
+ // src/babel/utils/attributeMatchers.ts
157
+ var DEFAULT_CLASS_ATTRIBUTES = [
158
+ "className",
159
+ "contentContainerClassName",
160
+ "columnWrapperClassName",
161
+ "ListHeaderComponentClassName",
162
+ "ListFooterComponentClassName"
163
+ ];
164
+ function buildAttributeMatchers(attributes) {
165
+ const exactMatches = /* @__PURE__ */ new Set();
166
+ const patterns = [];
167
+ for (const attr of attributes) {
168
+ if (attr.includes("*")) {
169
+ const regexPattern = "^" + attr.replace(/\*/g, ".*") + "$";
170
+ patterns.push(new RegExp(regexPattern));
171
+ } else {
172
+ exactMatches.add(attr);
173
+ }
174
+ }
175
+ return { exactMatches, patterns };
176
+ }
177
+ function isAttributeSupported(attributeName, exactMatches, patterns) {
178
+ if (exactMatches.has(attributeName)) {
179
+ return true;
180
+ }
181
+ for (const pattern of patterns) {
182
+ if (pattern.test(attributeName)) {
183
+ return true;
184
+ }
185
+ }
186
+ return false;
187
+ }
188
+ function getTargetStyleProp(attributeName) {
189
+ return attributeName.endsWith("ClassName") ? attributeName.replace("ClassName", "Style") : "style";
190
+ }
191
+
192
+ // src/babel/plugin/state.ts
193
+ var DEFAULT_STYLES_IDENTIFIER = "_twStyles";
194
+ function createInitialState(options, filename, colorSchemeImportSource, colorSchemeHookName, schemeModifierConfig) {
195
+ const attributes = options?.attributes ?? [...DEFAULT_CLASS_ATTRIBUTES];
196
+ const { exactMatches, patterns } = buildAttributeMatchers(attributes);
197
+ const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
198
+ const customTheme = extractCustomTheme(filename);
199
+ return {
200
+ styleRegistry: /* @__PURE__ */ new Map(),
201
+ hasClassNames: false,
202
+ hasStyleSheetImport: false,
203
+ hasPlatformImport: false,
204
+ needsPlatformImport: false,
205
+ hasColorSchemeImport: false,
206
+ needsColorSchemeImport: false,
207
+ colorSchemeVariableName: "_twColorScheme",
208
+ colorSchemeImportSource,
209
+ colorSchemeHookName,
210
+ colorSchemeLocalIdentifier: void 0,
211
+ hasWindowDimensionsImport: false,
212
+ needsWindowDimensionsImport: false,
213
+ windowDimensionsVariableName: "_twDimensions",
214
+ windowDimensionsLocalIdentifier: void 0,
215
+ customTheme,
216
+ schemeModifierConfig,
217
+ supportedAttributes: exactMatches,
218
+ attributePatterns: patterns,
219
+ stylesIdentifier,
220
+ twImportNames: /* @__PURE__ */ new Set(),
221
+ hasTwImport: false,
222
+ reactNativeImportPath: void 0,
223
+ functionComponentsNeedingColorScheme: /* @__PURE__ */ new Set(),
224
+ functionComponentsNeedingWindowDimensions: /* @__PURE__ */ new Set()
225
+ };
226
+ }
227
+
37
228
  // src/parser/aspectRatio.ts
38
229
  var ASPECT_RATIO_PRESETS = {
39
230
  "aspect-auto": void 0,
@@ -77,187 +268,6 @@ function parseAspectRatio(cls) {
77
268
  return null;
78
269
  }
79
270
 
80
- // src/parser/borders.ts
81
- var BORDER_WIDTH_SCALE = {
82
- "": 1,
83
- "0": 0,
84
- "2": 2,
85
- "4": 4,
86
- "8": 8
87
- };
88
- var BORDER_RADIUS_SCALE = {
89
- none: 0,
90
- sm: 2,
91
- "": 4,
92
- md: 6,
93
- lg: 8,
94
- xl: 12,
95
- "2xl": 16,
96
- "3xl": 24,
97
- full: 9999
98
- };
99
- var BORDER_WIDTH_PROP_MAP = {
100
- t: "borderTopWidth",
101
- r: "borderRightWidth",
102
- b: "borderBottomWidth",
103
- l: "borderLeftWidth"
104
- };
105
- var BORDER_RADIUS_CORNER_MAP = {
106
- tl: "borderTopLeftRadius",
107
- tr: "borderTopRightRadius",
108
- bl: "borderBottomLeftRadius",
109
- br: "borderBottomRightRadius"
110
- };
111
- var BORDER_RADIUS_SIDE_MAP = {
112
- t: ["borderTopLeftRadius", "borderTopRightRadius"],
113
- r: ["borderTopRightRadius", "borderBottomRightRadius"],
114
- b: ["borderBottomLeftRadius", "borderBottomRightRadius"],
115
- l: ["borderTopLeftRadius", "borderBottomLeftRadius"]
116
- };
117
- function parseArbitraryBorderWidth(value) {
118
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
119
- if (pxMatch) {
120
- return parseInt(pxMatch[1], 10);
121
- }
122
- if (value.startsWith("[") && value.endsWith("]")) {
123
- if (process.env.NODE_ENV !== "production") {
124
- console.warn(
125
- `[react-native-tailwind] Unsupported arbitrary border width value: ${value}. Only px values are supported (e.g., [8px] or [8]).`
126
- );
127
- }
128
- return null;
129
- }
130
- return null;
131
- }
132
- function parseArbitraryBorderRadius(value) {
133
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
134
- if (pxMatch) {
135
- return parseInt(pxMatch[1], 10);
136
- }
137
- if (value.startsWith("[") && value.endsWith("]")) {
138
- if (process.env.NODE_ENV !== "production") {
139
- console.warn(
140
- `[react-native-tailwind] Unsupported arbitrary border radius value: ${value}. Only px values are supported (e.g., [12px] or [12]).`
141
- );
142
- }
143
- return null;
144
- }
145
- return null;
146
- }
147
- function parseBorder(cls) {
148
- if (cls === "border-solid") return { borderStyle: "solid" };
149
- if (cls === "border-dotted") return { borderStyle: "dotted" };
150
- if (cls === "border-dashed") return { borderStyle: "dashed" };
151
- if (cls.startsWith("border-")) {
152
- return parseBorderWidth(cls);
153
- }
154
- if (cls === "border") {
155
- return { borderWidth: 1 };
156
- }
157
- if (cls.startsWith("rounded")) {
158
- return parseBorderRadius(cls);
159
- }
160
- return null;
161
- }
162
- function parseBorderWidth(cls) {
163
- const dirMatch = cls.match(/^border-([trbl])(?:-(.+))?$/);
164
- if (dirMatch) {
165
- const dir = dirMatch[1];
166
- const valueStr = dirMatch[2] || "";
167
- if (valueStr.startsWith("[")) {
168
- const arbitraryValue = parseArbitraryBorderWidth(valueStr);
169
- if (arbitraryValue !== null) {
170
- return { [BORDER_WIDTH_PROP_MAP[dir]]: arbitraryValue };
171
- }
172
- return null;
173
- }
174
- const scaleValue = BORDER_WIDTH_SCALE[valueStr];
175
- if (scaleValue !== void 0) {
176
- return { [BORDER_WIDTH_PROP_MAP[dir]]: scaleValue };
177
- }
178
- return null;
179
- }
180
- const allMatch = cls.match(/^border-(\d+)$/);
181
- if (allMatch) {
182
- const value = BORDER_WIDTH_SCALE[allMatch[1]];
183
- if (value !== void 0) {
184
- return { borderWidth: value };
185
- }
186
- }
187
- const allArbMatch = cls.match(/^border-(\[.+\])$/);
188
- if (allArbMatch) {
189
- const arbitraryValue = parseArbitraryBorderWidth(allArbMatch[1]);
190
- if (arbitraryValue !== null) {
191
- return { borderWidth: arbitraryValue };
192
- }
193
- }
194
- return null;
195
- }
196
- function parseBorderRadius(cls) {
197
- const withoutPrefix = cls.substring(7);
198
- if (withoutPrefix === "") {
199
- return { borderRadius: BORDER_RADIUS_SCALE[""] };
200
- }
201
- if (!withoutPrefix.startsWith("-")) {
202
- return null;
203
- }
204
- const rest = withoutPrefix.substring(1);
205
- if (rest === "") {
206
- return null;
207
- }
208
- const cornerMatch = rest.match(/^(tl|tr|bl|br)(?:-(.+))?$/);
209
- if (cornerMatch) {
210
- const corner = cornerMatch[1];
211
- const valueStr = cornerMatch[2] || "";
212
- if (valueStr.startsWith("[")) {
213
- const arbitraryValue = parseArbitraryBorderRadius(valueStr);
214
- if (arbitraryValue !== null) {
215
- return { [BORDER_RADIUS_CORNER_MAP[corner]]: arbitraryValue };
216
- }
217
- return null;
218
- }
219
- const scaleValue2 = BORDER_RADIUS_SCALE[valueStr];
220
- if (scaleValue2 !== void 0) {
221
- return { [BORDER_RADIUS_CORNER_MAP[corner]]: scaleValue2 };
222
- }
223
- return null;
224
- }
225
- const sideMatch = rest.match(/^([trbl])(?:-(.+))?$/);
226
- if (sideMatch) {
227
- const side = sideMatch[1];
228
- const valueStr = sideMatch[2] || "";
229
- let value;
230
- if (valueStr.startsWith("[")) {
231
- const arbitraryValue = parseArbitraryBorderRadius(valueStr);
232
- if (arbitraryValue !== null) {
233
- value = arbitraryValue;
234
- } else {
235
- return null;
236
- }
237
- } else {
238
- value = BORDER_RADIUS_SCALE[valueStr];
239
- }
240
- if (value !== void 0) {
241
- const result = {};
242
- BORDER_RADIUS_SIDE_MAP[side].forEach((prop) => result[prop] = value);
243
- return result;
244
- }
245
- return null;
246
- }
247
- if (rest.startsWith("[")) {
248
- const arbitraryValue = parseArbitraryBorderRadius(rest);
249
- if (arbitraryValue !== null) {
250
- return { borderRadius: arbitraryValue };
251
- }
252
- return null;
253
- }
254
- const scaleValue = BORDER_RADIUS_SCALE[rest];
255
- if (scaleValue !== void 0) {
256
- return { borderRadius: scaleValue };
257
- }
258
- return null;
259
- }
260
-
261
271
  // src/config/tailwind.ts
262
272
  var TAILWIND_COLORS = {
263
273
  red: {
@@ -548,20 +558,6 @@ var TAILWIND_COLORS = {
548
558
  }
549
559
  };
550
560
 
551
- // src/utils/flattenColors.ts
552
- function flattenColors(colors, prefix = "") {
553
- const result = {};
554
- for (const [key, value] of Object.entries(colors)) {
555
- const newKey = key === "DEFAULT" && prefix ? prefix : prefix ? `${prefix}-${key}` : key;
556
- if (typeof value === "string") {
557
- result[newKey] = value;
558
- } else if (typeof value === "object" && value !== null) {
559
- Object.assign(result, flattenColors(value, newKey));
560
- }
561
- }
562
- return result;
563
- }
564
-
565
561
  // src/parser/colors.ts
566
562
  var COLORS = {
567
563
  ...flattenColors(TAILWIND_COLORS),
@@ -663,95 +659,312 @@ function parseColor(cls, customColors) {
663
659
  return { borderColor: color };
664
660
  }
665
661
  }
662
+ const dirBorderMatch = cls.match(/^border-([trblxy])-(.+)$/);
663
+ if (dirBorderMatch) {
664
+ const dir = dirBorderMatch[1];
665
+ const colorKey = dirBorderMatch[2];
666
+ if (colorKey.startsWith("[") && !colorKey.startsWith("[#")) {
667
+ return null;
668
+ }
669
+ const color = parseColorWithOpacity(colorKey);
670
+ if (color) {
671
+ if (dir === "x") {
672
+ return {
673
+ borderLeftColor: color,
674
+ borderRightColor: color
675
+ };
676
+ }
677
+ if (dir === "y") {
678
+ return {
679
+ borderTopColor: color,
680
+ borderBottomColor: color
681
+ };
682
+ }
683
+ const propMap = {
684
+ t: "borderTopColor",
685
+ r: "borderRightColor",
686
+ b: "borderBottomColor",
687
+ l: "borderLeftColor"
688
+ };
689
+ return { [propMap[dir]]: color };
690
+ }
691
+ }
666
692
  return null;
667
693
  }
668
694
 
669
- // src/parser/layout.ts
670
- function parseArbitraryInset(value) {
671
- const pxMatch = value.match(/^\[(-?\d+)(?:px)?\]$/);
695
+ // src/parser/borders.ts
696
+ var BORDER_WIDTH_SCALE = {
697
+ "": 1,
698
+ "0": 0,
699
+ "2": 2,
700
+ "4": 4,
701
+ "8": 8
702
+ };
703
+ var BORDER_RADIUS_SCALE = {
704
+ none: 0,
705
+ sm: 2,
706
+ "": 4,
707
+ md: 6,
708
+ lg: 8,
709
+ xl: 12,
710
+ "2xl": 16,
711
+ "3xl": 24,
712
+ full: 9999
713
+ };
714
+ var BORDER_WIDTH_PROP_MAP = {
715
+ t: "borderTopWidth",
716
+ r: "borderRightWidth",
717
+ b: "borderBottomWidth",
718
+ l: "borderLeftWidth"
719
+ };
720
+ var BORDER_RADIUS_CORNER_MAP = {
721
+ tl: "borderTopLeftRadius",
722
+ tr: "borderTopRightRadius",
723
+ bl: "borderBottomLeftRadius",
724
+ br: "borderBottomRightRadius"
725
+ };
726
+ var BORDER_RADIUS_SIDE_MAP = {
727
+ t: ["borderTopLeftRadius", "borderTopRightRadius"],
728
+ r: ["borderTopRightRadius", "borderBottomRightRadius"],
729
+ b: ["borderBottomLeftRadius", "borderBottomRightRadius"],
730
+ l: ["borderTopLeftRadius", "borderBottomLeftRadius"]
731
+ };
732
+ function parseArbitraryBorderWidth(value) {
733
+ const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
672
734
  if (pxMatch) {
673
735
  return parseInt(pxMatch[1], 10);
674
736
  }
675
- const percentMatch = value.match(/^\[(-?\d+(?:\.\d+)?)%\]$/);
676
- if (percentMatch) {
677
- return `${percentMatch[1]}%`;
678
- }
679
737
  if (value.startsWith("[") && value.endsWith("]")) {
680
738
  if (process.env.NODE_ENV !== "production") {
681
739
  console.warn(
682
- `[react-native-tailwind] Unsupported arbitrary inset unit: ${value}. Only px and % are supported.`
740
+ `[react-native-tailwind] Unsupported arbitrary border width value: ${value}. Only px values are supported (e.g., [8px] or [8]).`
683
741
  );
684
742
  }
685
743
  return null;
686
744
  }
687
745
  return null;
688
746
  }
689
- function parseArbitraryZIndex(value) {
690
- const zMatch = value.match(/^\[(-?\d+)\]$/);
691
- if (zMatch) {
692
- return parseInt(zMatch[1], 10);
747
+ function parseArbitraryBorderRadius(value) {
748
+ const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
749
+ if (pxMatch) {
750
+ return parseInt(pxMatch[1], 10);
693
751
  }
694
752
  if (value.startsWith("[") && value.endsWith("]")) {
695
753
  if (process.env.NODE_ENV !== "production") {
696
754
  console.warn(
697
- `[react-native-tailwind] Invalid arbitrary z-index: ${value}. Only integers are supported.`
755
+ `[react-native-tailwind] Unsupported arbitrary border radius value: ${value}. Only px values are supported (e.g., [12px] or [12]).`
698
756
  );
699
757
  }
700
758
  return null;
701
759
  }
702
760
  return null;
703
761
  }
704
- function parseArbitraryGrowShrink(value) {
705
- const match = value.match(/^\[(\d+(?:\.\d+)?|\.\d+)\]$/);
706
- if (match) {
707
- return parseFloat(match[1]);
762
+ function parseBorder(cls, customColors) {
763
+ if (cls === "border-solid") return { borderStyle: "solid" };
764
+ if (cls === "border-dotted") return { borderStyle: "dotted" };
765
+ if (cls === "border-dashed") return { borderStyle: "dashed" };
766
+ if (cls.startsWith("border-")) {
767
+ return parseBorderWidth(cls, customColors);
708
768
  }
709
- if (value.startsWith("[") && value.endsWith("]")) {
710
- if (process.env.NODE_ENV !== "production") {
711
- console.warn(
712
- `[react-native-tailwind] Invalid arbitrary grow/shrink value: ${value}. Only non-negative numbers are supported (e.g., [1.5], [2], [0.5], [.5]).`
713
- );
769
+ if (cls === "border") {
770
+ return { borderWidth: 1 };
771
+ }
772
+ if (cls.startsWith("rounded")) {
773
+ return parseBorderRadius(cls);
774
+ }
775
+ return null;
776
+ }
777
+ function parseBorderWidth(cls, customColors) {
778
+ const dirMatch = cls.match(/^border-([trbl])(?:-(.+))?$/);
779
+ if (dirMatch) {
780
+ const dir = dirMatch[1];
781
+ const valueStr = dirMatch[2] || "";
782
+ if (valueStr) {
783
+ const colorResult = parseColor(cls, customColors);
784
+ if (colorResult !== null) {
785
+ return null;
786
+ }
787
+ }
788
+ if (valueStr.startsWith("[")) {
789
+ const arbitraryValue = parseArbitraryBorderWidth(valueStr);
790
+ if (arbitraryValue !== null) {
791
+ return { [BORDER_WIDTH_PROP_MAP[dir]]: arbitraryValue };
792
+ }
793
+ return null;
794
+ }
795
+ const scaleValue = BORDER_WIDTH_SCALE[valueStr];
796
+ if (scaleValue !== void 0) {
797
+ return { [BORDER_WIDTH_PROP_MAP[dir]]: scaleValue };
714
798
  }
715
799
  return null;
716
800
  }
801
+ const allMatch = cls.match(/^border-(\d+)$/);
802
+ if (allMatch) {
803
+ const value = BORDER_WIDTH_SCALE[allMatch[1]];
804
+ if (value !== void 0) {
805
+ return { borderWidth: value };
806
+ }
807
+ }
808
+ const allArbMatch = cls.match(/^border-(\[.+\])$/);
809
+ if (allArbMatch) {
810
+ const arbitraryValue = parseArbitraryBorderWidth(allArbMatch[1]);
811
+ if (arbitraryValue !== null) {
812
+ return { borderWidth: arbitraryValue };
813
+ }
814
+ }
717
815
  return null;
718
816
  }
719
- var DISPLAY_MAP = {
720
- flex: { display: "flex" },
721
- hidden: { display: "none" }
722
- };
723
- var FLEX_DIRECTION_MAP = {
724
- "flex-row": { flexDirection: "row" },
725
- "flex-row-reverse": { flexDirection: "row-reverse" },
726
- "flex-col": { flexDirection: "column" },
727
- "flex-col-reverse": { flexDirection: "column-reverse" }
728
- };
729
- var FLEX_WRAP_MAP = {
730
- "flex-wrap": { flexWrap: "wrap" },
731
- "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
732
- "flex-nowrap": { flexWrap: "nowrap" }
733
- };
734
- var FLEX_MAP = {
735
- "flex-1": { flex: 1 },
736
- "flex-auto": { flex: 1 },
737
- "flex-none": { flex: 0 }
738
- };
739
- var GROW_SHRINK_MAP = {
740
- grow: { flexGrow: 1 },
741
- "grow-0": { flexGrow: 0 },
742
- shrink: { flexShrink: 1 },
743
- "shrink-0": { flexShrink: 0 },
744
- // CSS-style aliases
745
- "flex-grow": { flexGrow: 1 },
746
- "flex-grow-0": { flexGrow: 0 },
747
- "flex-shrink": { flexShrink: 1 },
748
- "flex-shrink-0": { flexShrink: 0 }
749
- };
750
- var JUSTIFY_CONTENT_MAP = {
751
- "justify-start": { justifyContent: "flex-start" },
752
- "justify-end": { justifyContent: "flex-end" },
753
- "justify-center": { justifyContent: "center" },
754
- "justify-between": { justifyContent: "space-between" },
817
+ function parseBorderRadius(cls) {
818
+ const withoutPrefix = cls.substring(7);
819
+ if (withoutPrefix === "") {
820
+ return { borderRadius: BORDER_RADIUS_SCALE[""] };
821
+ }
822
+ if (!withoutPrefix.startsWith("-")) {
823
+ return null;
824
+ }
825
+ const rest = withoutPrefix.substring(1);
826
+ if (rest === "") {
827
+ return null;
828
+ }
829
+ const cornerMatch = rest.match(/^(tl|tr|bl|br)(?:-(.+))?$/);
830
+ if (cornerMatch) {
831
+ const corner = cornerMatch[1];
832
+ const valueStr = cornerMatch[2] || "";
833
+ if (valueStr.startsWith("[")) {
834
+ const arbitraryValue = parseArbitraryBorderRadius(valueStr);
835
+ if (arbitraryValue !== null) {
836
+ return { [BORDER_RADIUS_CORNER_MAP[corner]]: arbitraryValue };
837
+ }
838
+ return null;
839
+ }
840
+ const scaleValue2 = BORDER_RADIUS_SCALE[valueStr];
841
+ if (scaleValue2 !== void 0) {
842
+ return { [BORDER_RADIUS_CORNER_MAP[corner]]: scaleValue2 };
843
+ }
844
+ return null;
845
+ }
846
+ const sideMatch = rest.match(/^([trbl])(?:-(.+))?$/);
847
+ if (sideMatch) {
848
+ const side = sideMatch[1];
849
+ const valueStr = sideMatch[2] || "";
850
+ let value;
851
+ if (valueStr.startsWith("[")) {
852
+ const arbitraryValue = parseArbitraryBorderRadius(valueStr);
853
+ if (arbitraryValue !== null) {
854
+ value = arbitraryValue;
855
+ } else {
856
+ return null;
857
+ }
858
+ } else {
859
+ value = BORDER_RADIUS_SCALE[valueStr];
860
+ }
861
+ if (value !== void 0) {
862
+ const result = {};
863
+ BORDER_RADIUS_SIDE_MAP[side].forEach((prop) => result[prop] = value);
864
+ return result;
865
+ }
866
+ return null;
867
+ }
868
+ if (rest.startsWith("[")) {
869
+ const arbitraryValue = parseArbitraryBorderRadius(rest);
870
+ if (arbitraryValue !== null) {
871
+ return { borderRadius: arbitraryValue };
872
+ }
873
+ return null;
874
+ }
875
+ const scaleValue = BORDER_RADIUS_SCALE[rest];
876
+ if (scaleValue !== void 0) {
877
+ return { borderRadius: scaleValue };
878
+ }
879
+ return null;
880
+ }
881
+
882
+ // src/parser/layout.ts
883
+ function parseArbitraryInset(value) {
884
+ const pxMatch = value.match(/^\[(-?\d+)(?:px)?\]$/);
885
+ if (pxMatch) {
886
+ return parseInt(pxMatch[1], 10);
887
+ }
888
+ const percentMatch = value.match(/^\[(-?\d+(?:\.\d+)?)%\]$/);
889
+ if (percentMatch) {
890
+ return `${percentMatch[1]}%`;
891
+ }
892
+ if (value.startsWith("[") && value.endsWith("]")) {
893
+ if (process.env.NODE_ENV !== "production") {
894
+ console.warn(
895
+ `[react-native-tailwind] Unsupported arbitrary inset unit: ${value}. Only px and % are supported.`
896
+ );
897
+ }
898
+ return null;
899
+ }
900
+ return null;
901
+ }
902
+ function parseArbitraryZIndex(value) {
903
+ const zMatch = value.match(/^\[(-?\d+)\]$/);
904
+ if (zMatch) {
905
+ return parseInt(zMatch[1], 10);
906
+ }
907
+ if (value.startsWith("[") && value.endsWith("]")) {
908
+ if (process.env.NODE_ENV !== "production") {
909
+ console.warn(
910
+ `[react-native-tailwind] Invalid arbitrary z-index: ${value}. Only integers are supported.`
911
+ );
912
+ }
913
+ return null;
914
+ }
915
+ return null;
916
+ }
917
+ function parseArbitraryGrowShrink(value) {
918
+ const match = value.match(/^\[(\d+(?:\.\d+)?|\.\d+)\]$/);
919
+ if (match) {
920
+ return parseFloat(match[1]);
921
+ }
922
+ if (value.startsWith("[") && value.endsWith("]")) {
923
+ if (process.env.NODE_ENV !== "production") {
924
+ console.warn(
925
+ `[react-native-tailwind] Invalid arbitrary grow/shrink value: ${value}. Only non-negative numbers are supported (e.g., [1.5], [2], [0.5], [.5]).`
926
+ );
927
+ }
928
+ return null;
929
+ }
930
+ return null;
931
+ }
932
+ var DISPLAY_MAP = {
933
+ flex: { display: "flex" },
934
+ hidden: { display: "none" }
935
+ };
936
+ var FLEX_DIRECTION_MAP = {
937
+ "flex-row": { flexDirection: "row" },
938
+ "flex-row-reverse": { flexDirection: "row-reverse" },
939
+ "flex-col": { flexDirection: "column" },
940
+ "flex-col-reverse": { flexDirection: "column-reverse" }
941
+ };
942
+ var FLEX_WRAP_MAP = {
943
+ "flex-wrap": { flexWrap: "wrap" },
944
+ "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
945
+ "flex-nowrap": { flexWrap: "nowrap" }
946
+ };
947
+ var FLEX_MAP = {
948
+ "flex-1": { flex: 1 },
949
+ "flex-auto": { flex: 1 },
950
+ "flex-none": { flex: 0 }
951
+ };
952
+ var GROW_SHRINK_MAP = {
953
+ grow: { flexGrow: 1 },
954
+ "grow-0": { flexGrow: 0 },
955
+ shrink: { flexShrink: 1 },
956
+ "shrink-0": { flexShrink: 0 },
957
+ // CSS-style aliases
958
+ "flex-grow": { flexGrow: 1 },
959
+ "flex-grow-0": { flexGrow: 0 },
960
+ "flex-shrink": { flexShrink: 1 },
961
+ "flex-shrink-0": { flexShrink: 0 }
962
+ };
963
+ var JUSTIFY_CONTENT_MAP = {
964
+ "justify-start": { justifyContent: "flex-start" },
965
+ "justify-end": { justifyContent: "flex-end" },
966
+ "justify-center": { justifyContent: "center" },
967
+ "justify-between": { justifyContent: "space-between" },
755
968
  "justify-around": { justifyContent: "space-around" },
756
969
  "justify-evenly": { justifyContent: "space-evenly" }
757
970
  };
@@ -1018,6 +1231,9 @@ function parseShadow(cls) {
1018
1231
  return null;
1019
1232
  }
1020
1233
 
1234
+ // src/config/markers.ts
1235
+ var RUNTIME_DIMENSIONS_MARKER = "{{RUNTIME:dimensions.";
1236
+
1021
1237
  // src/parser/sizing.ts
1022
1238
  var SIZE_SCALE = {
1023
1239
  0: 0,
@@ -1095,6 +1311,9 @@ function parseArbitrarySize(value) {
1095
1311
  function parseSizing(cls) {
1096
1312
  if (cls.startsWith("w-")) {
1097
1313
  const sizeKey = cls.substring(2);
1314
+ if (sizeKey === "screen") {
1315
+ return { width: `${RUNTIME_DIMENSIONS_MARKER}width}}` };
1316
+ }
1098
1317
  const arbitrarySize = parseArbitrarySize(sizeKey);
1099
1318
  if (arbitrarySize !== null) {
1100
1319
  return { width: arbitrarySize };
@@ -1113,6 +1332,9 @@ function parseSizing(cls) {
1113
1332
  }
1114
1333
  if (cls.startsWith("h-")) {
1115
1334
  const sizeKey = cls.substring(2);
1335
+ if (sizeKey === "screen") {
1336
+ return { height: `${RUNTIME_DIMENSIONS_MARKER}height}}` };
1337
+ }
1116
1338
  const arbitrarySize = parseArbitrarySize(sizeKey);
1117
1339
  if (arbitrarySize !== null) {
1118
1340
  return { height: arbitrarySize };
@@ -1921,7 +2143,7 @@ function parseClassName(className, customTheme) {
1921
2143
  function parseClass(cls, customTheme) {
1922
2144
  const parsers = [
1923
2145
  parseSpacing,
1924
- parseBorder,
2146
+ (cls2) => parseBorder(cls2, customTheme?.colors),
1925
2147
  (cls2) => parseColor(cls2, customTheme?.colors),
1926
2148
  parseLayout,
1927
2149
  (cls2) => parseTypography(cls2, customTheme?.fontFamily, customTheme?.fontSize),
@@ -1949,173 +2171,84 @@ function generateStyleKey(className) {
1949
2171
  return key;
1950
2172
  }
1951
2173
 
1952
- // src/babel/config-loader.ts
1953
- var fs = __toESM(require("fs"), 1);
1954
- var path = __toESM(require("path"), 1);
1955
- var configCache = /* @__PURE__ */ new Map();
1956
- function findTailwindConfig(startDir) {
1957
- let currentDir = startDir;
1958
- const root = path.parse(currentDir).root;
1959
- const configNames = [
1960
- "tailwind.config.mjs",
1961
- "tailwind.config.js",
1962
- "tailwind.config.cjs",
1963
- "tailwind.config.ts"
1964
- ];
1965
- while (currentDir !== root) {
1966
- for (const configName of configNames) {
1967
- const configPath = path.join(currentDir, configName);
1968
- if (fs.existsSync(configPath)) {
1969
- return configPath;
2174
+ // src/babel/utils/windowDimensionsProcessing.ts
2175
+ function hasRuntimeDimensions(styleObject) {
2176
+ return Object.values(styleObject).some(
2177
+ (value) => typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)
2178
+ );
2179
+ }
2180
+ function createRuntimeDimensionObject(styleObject, state, t) {
2181
+ state.needsWindowDimensionsImport = true;
2182
+ const properties = [];
2183
+ for (const [key, value] of Object.entries(styleObject)) {
2184
+ let valueNode;
2185
+ if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
2186
+ const match = value.match(/dimensions\.(\w+)/);
2187
+ const prop = match?.[1];
2188
+ if (prop) {
2189
+ valueNode = t.memberExpression(t.identifier(state.windowDimensionsVariableName), t.identifier(prop));
2190
+ } else {
2191
+ valueNode = t.stringLiteral(value);
1970
2192
  }
2193
+ } else if (typeof value === "number") {
2194
+ valueNode = t.numericLiteral(value);
2195
+ } else if (typeof value === "string") {
2196
+ valueNode = t.stringLiteral(value);
2197
+ } else if (typeof value === "object" && value !== null) {
2198
+ valueNode = t.valueToNode(value);
2199
+ } else {
2200
+ valueNode = t.valueToNode(value);
1971
2201
  }
1972
- currentDir = path.dirname(currentDir);
2202
+ properties.push(t.objectProperty(t.identifier(key), valueNode));
1973
2203
  }
1974
- return null;
2204
+ return t.objectExpression(properties);
1975
2205
  }
1976
- function loadTailwindConfig(configPath) {
1977
- if (configCache.has(configPath)) {
1978
- return configCache.get(configPath);
1979
- }
1980
- try {
1981
- const resolvedPath = require.resolve(configPath);
1982
- delete require.cache[resolvedPath];
1983
- const config = require(configPath);
1984
- const resolved = "default" in config ? config.default : config;
1985
- configCache.set(configPath, resolved);
1986
- return resolved;
1987
- } catch (error) {
1988
- if (process.env.NODE_ENV !== "production") {
1989
- console.warn(`[react-native-tailwind] Failed to load config from ${configPath}:`, error);
2206
+ function splitStaticAndRuntimeStyles(styleObject) {
2207
+ const staticStyles = {};
2208
+ const runtimeStyles = {};
2209
+ for (const [key, value] of Object.entries(styleObject)) {
2210
+ if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
2211
+ runtimeStyles[key] = value;
2212
+ } else {
2213
+ staticStyles[key] = value;
1990
2214
  }
1991
- configCache.set(configPath, null);
1992
- return null;
1993
2215
  }
2216
+ return { static: staticStyles, runtime: runtimeStyles };
1994
2217
  }
1995
- function extractCustomTheme(filename) {
1996
- const projectDir = path.dirname(filename);
1997
- const configPath = findTailwindConfig(projectDir);
1998
- if (!configPath) {
1999
- return { colors: {}, fontFamily: {}, fontSize: {} };
2000
- }
2001
- const config = loadTailwindConfig(configPath);
2002
- if (!config?.theme) {
2003
- return { colors: {}, fontFamily: {}, fontSize: {} };
2004
- }
2005
- if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
2006
- console.warn(
2007
- "[react-native-tailwind] Using theme.colors will override all default colors. Use theme.extend.colors to add custom colors while keeping defaults."
2008
- );
2009
- }
2010
- const colors = config.theme.extend?.colors ?? config.theme.colors ?? {};
2011
- if (config.theme.fontFamily && !config.theme.extend?.fontFamily && process.env.NODE_ENV !== "production") {
2012
- console.warn(
2013
- "[react-native-tailwind] Using theme.fontFamily will override all default font families. Use theme.extend.fontFamily to add custom fonts while keeping defaults."
2014
- );
2015
- }
2016
- const fontFamily = config.theme.extend?.fontFamily ?? config.theme.fontFamily ?? {};
2017
- const fontFamilyResult = {};
2018
- for (const [key, value] of Object.entries(fontFamily)) {
2019
- if (Array.isArray(value)) {
2020
- fontFamilyResult[key] = value[0];
2021
- } else {
2022
- fontFamilyResult[key] = value;
2218
+
2219
+ // src/babel/utils/colorSchemeModifierProcessing.ts
2220
+ function processColorSchemeModifiers(colorSchemeModifiers, state, parseClassName2, generateStyleKey2, t) {
2221
+ state.needsColorSchemeImport = true;
2222
+ const modifiersByScheme = /* @__PURE__ */ new Map();
2223
+ for (const mod of colorSchemeModifiers) {
2224
+ const scheme = mod.modifier;
2225
+ if (!modifiersByScheme.has(scheme)) {
2226
+ modifiersByScheme.set(scheme, []);
2227
+ }
2228
+ const schemeGroup = modifiersByScheme.get(scheme);
2229
+ if (schemeGroup) {
2230
+ schemeGroup.push(mod);
2023
2231
  }
2024
2232
  }
2025
- if (config.theme.fontSize && !config.theme.extend?.fontSize && process.env.NODE_ENV !== "production") {
2026
- console.warn(
2027
- "[react-native-tailwind] Using theme.fontSize will override all default font sizes. Use theme.extend.fontSize to add custom font sizes while keeping defaults."
2233
+ const conditionalExpressions = [];
2234
+ for (const [scheme, modifiers] of modifiersByScheme) {
2235
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2236
+ const styleObject = parseClassName2(classNames, state.customTheme);
2237
+ if (hasRuntimeDimensions(styleObject)) {
2238
+ throw new Error(
2239
+ `w-screen and h-screen cannot be combined with color scheme modifiers (dark:, light:, scheme:). Found in: "${scheme}:${classNames}". Use w-screen/h-screen without modifiers instead.`
2240
+ );
2241
+ }
2242
+ const styleKey = generateStyleKey2(`${scheme}_${classNames}`);
2243
+ state.styleRegistry.set(styleKey, styleObject);
2244
+ const colorSchemeCheck = t.binaryExpression(
2245
+ "===",
2246
+ t.identifier(state.colorSchemeVariableName),
2247
+ t.stringLiteral(scheme)
2028
2248
  );
2029
- }
2030
- const fontSize = config.theme.extend?.fontSize ?? config.theme.fontSize ?? {};
2031
- const fontSizeResult = {};
2032
- for (const [key, value] of Object.entries(fontSize)) {
2033
- if (typeof value === "number") {
2034
- fontSizeResult[key] = value;
2035
- } else if (typeof value === "string") {
2036
- const parsed = parseFloat(value.replace(/px$/, ""));
2037
- if (!isNaN(parsed)) {
2038
- fontSizeResult[key] = parsed;
2039
- } else {
2040
- if (process.env.NODE_ENV !== "production") {
2041
- console.warn(
2042
- `[react-native-tailwind] Invalid fontSize value for "${key}": ${value}. Expected number or string like "18px".`
2043
- );
2044
- }
2045
- }
2046
- }
2047
- }
2048
- return {
2049
- colors: flattenColors(colors),
2050
- fontFamily: fontFamilyResult,
2051
- fontSize: fontSizeResult
2052
- };
2053
- }
2054
-
2055
- // src/babel/utils/attributeMatchers.ts
2056
- var DEFAULT_CLASS_ATTRIBUTES = [
2057
- "className",
2058
- "contentContainerClassName",
2059
- "columnWrapperClassName",
2060
- "ListHeaderComponentClassName",
2061
- "ListFooterComponentClassName"
2062
- ];
2063
- function buildAttributeMatchers(attributes) {
2064
- const exactMatches = /* @__PURE__ */ new Set();
2065
- const patterns = [];
2066
- for (const attr of attributes) {
2067
- if (attr.includes("*")) {
2068
- const regexPattern = "^" + attr.replace(/\*/g, ".*") + "$";
2069
- patterns.push(new RegExp(regexPattern));
2070
- } else {
2071
- exactMatches.add(attr);
2072
- }
2073
- }
2074
- return { exactMatches, patterns };
2075
- }
2076
- function isAttributeSupported(attributeName, exactMatches, patterns) {
2077
- if (exactMatches.has(attributeName)) {
2078
- return true;
2079
- }
2080
- for (const pattern of patterns) {
2081
- if (pattern.test(attributeName)) {
2082
- return true;
2083
- }
2084
- }
2085
- return false;
2086
- }
2087
- function getTargetStyleProp(attributeName) {
2088
- return attributeName.endsWith("ClassName") ? attributeName.replace("ClassName", "Style") : "style";
2089
- }
2090
-
2091
- // src/babel/utils/colorSchemeModifierProcessing.ts
2092
- function processColorSchemeModifiers(colorSchemeModifiers, state, parseClassName2, generateStyleKey2, t) {
2093
- state.needsColorSchemeImport = true;
2094
- const modifiersByScheme = /* @__PURE__ */ new Map();
2095
- for (const mod of colorSchemeModifiers) {
2096
- const scheme = mod.modifier;
2097
- if (!modifiersByScheme.has(scheme)) {
2098
- modifiersByScheme.set(scheme, []);
2099
- }
2100
- const schemeGroup = modifiersByScheme.get(scheme);
2101
- if (schemeGroup) {
2102
- schemeGroup.push(mod);
2103
- }
2104
- }
2105
- const conditionalExpressions = [];
2106
- for (const [scheme, modifiers] of modifiersByScheme) {
2107
- const classNames = modifiers.map((m) => m.baseClass).join(" ");
2108
- const styleObject = parseClassName2(classNames, state.customTheme);
2109
- const styleKey = generateStyleKey2(`${scheme}_${classNames}`);
2110
- state.styleRegistry.set(styleKey, styleObject);
2111
- const colorSchemeCheck = t.binaryExpression(
2112
- "===",
2113
- t.identifier(state.colorSchemeVariableName),
2114
- t.stringLiteral(scheme)
2115
- );
2116
- const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2117
- const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
2118
- conditionalExpressions.push(conditionalExpression);
2249
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2250
+ const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
2251
+ conditionalExpressions.push(conditionalExpression);
2119
2252
  }
2120
2253
  return conditionalExpressions;
2121
2254
  }
@@ -2142,6 +2275,8 @@ function getComponentModifierSupport(jsxElement, t) {
2142
2275
  switch (componentName) {
2143
2276
  case "Pressable":
2144
2277
  return { component: "Pressable", supportedModifiers: ["active", "hover", "focus", "disabled"] };
2278
+ case "TouchableOpacity":
2279
+ return { component: "TouchableOpacity", supportedModifiers: ["active", "disabled"] };
2145
2280
  case "TextInput":
2146
2281
  return { component: "TextInput", supportedModifiers: ["focus", "disabled", "placeholder"] };
2147
2282
  default:
@@ -2482,6 +2617,11 @@ function processStaticClassNameWithModifiers(className, state, parseClassName2,
2482
2617
  if (baseClasses.length > 0) {
2483
2618
  const baseClassName = baseClasses.join(" ");
2484
2619
  const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
2620
+ if (hasRuntimeDimensions(baseStyleObject)) {
2621
+ throw new Error(
2622
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). Found in: "${baseClassName}". Use w-screen/h-screen without modifiers instead.`
2623
+ );
2624
+ }
2485
2625
  const baseStyleKey = generateStyleKey2(baseClassName);
2486
2626
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
2487
2627
  baseStyleExpression = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey));
@@ -2491,75 +2631,695 @@ function processStaticClassNameWithModifiers(className, state, parseClassName2,
2491
2631
  if (!modifiersByType.has(mod.modifier)) {
2492
2632
  modifiersByType.set(mod.modifier, []);
2493
2633
  }
2494
- const modGroup = modifiersByType.get(mod.modifier);
2495
- if (modGroup) {
2496
- modGroup.push(mod);
2634
+ const modGroup = modifiersByType.get(mod.modifier);
2635
+ if (modGroup) {
2636
+ modGroup.push(mod);
2637
+ }
2638
+ }
2639
+ const styleArrayElements = [];
2640
+ if (baseStyleExpression) {
2641
+ styleArrayElements.push(baseStyleExpression);
2642
+ }
2643
+ for (const [modifierType, modifiers] of modifiersByType) {
2644
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
2645
+ const modifierStyleObject = parseClassName2(modifierClassNames, state.customTheme);
2646
+ if (hasRuntimeDimensions(modifierStyleObject)) {
2647
+ throw new Error(
2648
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). Found in: "${modifierType}:${modifierClassNames}". Use w-screen/h-screen without modifiers instead.`
2649
+ );
2650
+ }
2651
+ const modifierStyleKey = generateStyleKey2(`${modifierType}_${modifierClassNames}`);
2652
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
2653
+ const stateProperty = getStatePropertyForModifier(modifierType);
2654
+ const conditionalExpression = t.logicalExpression(
2655
+ "&&",
2656
+ t.identifier(stateProperty),
2657
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey))
2658
+ );
2659
+ styleArrayElements.push(conditionalExpression);
2660
+ }
2661
+ if (styleArrayElements.length === 1) {
2662
+ return styleArrayElements[0];
2663
+ }
2664
+ return t.arrayExpression(styleArrayElements);
2665
+ }
2666
+ function createStyleFunction(styleExpression, modifierTypes, t) {
2667
+ const paramProperties = [];
2668
+ const usedStateProps = /* @__PURE__ */ new Set();
2669
+ for (const modifierType of modifierTypes) {
2670
+ const stateProperty = getStatePropertyForModifier(modifierType);
2671
+ if (!usedStateProps.has(stateProperty)) {
2672
+ usedStateProps.add(stateProperty);
2673
+ paramProperties.push(
2674
+ t.objectProperty(t.identifier(stateProperty), t.identifier(stateProperty), false, true)
2675
+ );
2676
+ }
2677
+ }
2678
+ const param = t.objectPattern(paramProperties);
2679
+ return t.arrowFunctionExpression([param], styleExpression);
2680
+ }
2681
+
2682
+ // src/babel/utils/platformModifierProcessing.ts
2683
+ function processPlatformModifiers(platformModifiers, state, parseClassName2, generateStyleKey2, t) {
2684
+ state.needsPlatformImport = true;
2685
+ const modifiersByPlatform = /* @__PURE__ */ new Map();
2686
+ for (const mod of platformModifiers) {
2687
+ const platform = mod.modifier;
2688
+ if (!modifiersByPlatform.has(platform)) {
2689
+ modifiersByPlatform.set(platform, []);
2690
+ }
2691
+ const platformGroup = modifiersByPlatform.get(platform);
2692
+ if (platformGroup) {
2693
+ platformGroup.push(mod);
2694
+ }
2695
+ }
2696
+ const selectProperties = [];
2697
+ for (const [platform, modifiers] of modifiersByPlatform) {
2698
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2699
+ const styleObject = parseClassName2(classNames, state.customTheme);
2700
+ if (hasRuntimeDimensions(styleObject)) {
2701
+ throw new Error(
2702
+ `w-screen and h-screen cannot be combined with platform modifiers (ios:, android:, web:). Found in: "${platform}:${classNames}". Use w-screen/h-screen without modifiers instead.`
2703
+ );
2704
+ }
2705
+ const styleKey = generateStyleKey2(`${platform}_${classNames}`);
2706
+ state.styleRegistry.set(styleKey, styleObject);
2707
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2708
+ selectProperties.push(t.objectProperty(t.identifier(platform), styleReference));
2709
+ }
2710
+ return t.callExpression(t.memberExpression(t.identifier("Platform"), t.identifier("select")), [
2711
+ t.objectExpression(selectProperties)
2712
+ ]);
2713
+ }
2714
+
2715
+ // src/babel/utils/styleTransforms.ts
2716
+ function getStyleExpression(styleAttribute, t) {
2717
+ const value = styleAttribute.value;
2718
+ if (!t.isJSXExpressionContainer(value)) return null;
2719
+ const expression = value.expression;
2720
+ if (t.isJSXEmptyExpression(expression)) return null;
2721
+ return expression;
2722
+ }
2723
+ function findStyleAttribute(path2, targetStyleProp, t) {
2724
+ const parent = path2.parent;
2725
+ return parent.attributes.find(
2726
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === targetStyleProp
2727
+ );
2728
+ }
2729
+ function replaceWithStyleAttribute(classNamePath, styleKey, targetStyleProp, stylesIdentifier, t) {
2730
+ const styleAttribute = t.jsxAttribute(
2731
+ t.jsxIdentifier(targetStyleProp),
2732
+ t.jsxExpressionContainer(t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)))
2733
+ );
2734
+ classNamePath.replaceWith(styleAttribute);
2735
+ }
2736
+ function mergeStyleAttribute(classNamePath, styleAttribute, styleKey, stylesIdentifier, t) {
2737
+ const existingStyle = getStyleExpression(styleAttribute, t);
2738
+ if (!existingStyle) return;
2739
+ if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2740
+ const paramIdentifier = t.identifier("_state");
2741
+ const functionCall = t.callExpression(existingStyle, [paramIdentifier]);
2742
+ const mergedArray = t.arrayExpression([
2743
+ t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)),
2744
+ functionCall
2745
+ ]);
2746
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2747
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2748
+ } else {
2749
+ const styleArray = t.arrayExpression([
2750
+ t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)),
2751
+ existingStyle
2752
+ ]);
2753
+ styleAttribute.value = t.jsxExpressionContainer(styleArray);
2754
+ }
2755
+ classNamePath.remove();
2756
+ }
2757
+ function replaceDynamicWithStyleAttribute(classNamePath, result, targetStyleProp, t) {
2758
+ const styleAttribute = t.jsxAttribute(
2759
+ t.jsxIdentifier(targetStyleProp),
2760
+ t.jsxExpressionContainer(result.expression)
2761
+ );
2762
+ classNamePath.replaceWith(styleAttribute);
2763
+ }
2764
+ function mergeDynamicStyleAttribute(classNamePath, styleAttribute, result, t) {
2765
+ const existingStyle = getStyleExpression(styleAttribute, t);
2766
+ if (!existingStyle) return;
2767
+ if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2768
+ const paramIdentifier = t.identifier("_state");
2769
+ const functionCall = t.callExpression(existingStyle, [paramIdentifier]);
2770
+ const mergedArray = t.arrayExpression([result.expression, functionCall]);
2771
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2772
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2773
+ } else {
2774
+ let styleArray;
2775
+ if (t.isArrayExpression(existingStyle)) {
2776
+ styleArray = t.arrayExpression([result.expression, ...existingStyle.elements]);
2777
+ } else {
2778
+ styleArray = t.arrayExpression([result.expression, existingStyle]);
2779
+ }
2780
+ styleAttribute.value = t.jsxExpressionContainer(styleArray);
2781
+ }
2782
+ classNamePath.remove();
2783
+ }
2784
+ function replaceWithStyleFunctionAttribute(classNamePath, styleFunctionExpression, targetStyleProp, t) {
2785
+ const styleAttribute = t.jsxAttribute(
2786
+ t.jsxIdentifier(targetStyleProp),
2787
+ t.jsxExpressionContainer(styleFunctionExpression)
2788
+ );
2789
+ classNamePath.replaceWith(styleAttribute);
2790
+ }
2791
+ function mergeStyleFunctionAttribute(classNamePath, styleAttribute, styleFunctionExpression, t) {
2792
+ const existingStyle = getStyleExpression(styleAttribute, t);
2793
+ if (!existingStyle) return;
2794
+ if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2795
+ const paramIdentifier = t.identifier("_state");
2796
+ const newFunctionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
2797
+ const existingFunctionCall = t.callExpression(existingStyle, [paramIdentifier]);
2798
+ const mergedArray = t.arrayExpression([newFunctionCall, existingFunctionCall]);
2799
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2800
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2801
+ } else {
2802
+ const paramIdentifier = t.identifier("_state");
2803
+ const functionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
2804
+ const mergedArray = t.arrayExpression([functionCall, existingStyle]);
2805
+ const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2806
+ styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2807
+ }
2808
+ classNamePath.remove();
2809
+ }
2810
+ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2811
+ const existingProp = jsxOpeningElement.attributes.find(
2812
+ (attr) => t.isJSXAttribute(attr) && attr.name.name === "placeholderTextColor"
2813
+ );
2814
+ if (existingProp) {
2815
+ if (process.env.NODE_ENV !== "production") {
2816
+ console.warn(
2817
+ `[react-native-tailwind] placeholderTextColor prop will be overridden by className placeholder: modifier. Remove the explicit prop or the placeholder: modifier to avoid confusion.`
2818
+ );
2819
+ }
2820
+ existingProp.value = t.stringLiteral(color);
2821
+ } else {
2822
+ const newProp = t.jsxAttribute(t.jsxIdentifier("placeholderTextColor"), t.stringLiteral(color));
2823
+ jsxOpeningElement.attributes.push(newProp);
2824
+ }
2825
+ }
2826
+
2827
+ // src/babel/plugin/componentScope.ts
2828
+ function isComponentScope(functionPath, t) {
2829
+ const node = functionPath.node;
2830
+ const parent = functionPath.parent;
2831
+ const parentPath = functionPath.parentPath;
2832
+ if (t.isClassMethod(parent)) {
2833
+ return false;
2834
+ }
2835
+ if (functionPath.findParent((p) => t.isClassBody(p.node))) {
2836
+ return false;
2837
+ }
2838
+ if (t.isFunctionDeclaration(node)) {
2839
+ if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
2840
+ return true;
2841
+ }
2842
+ }
2843
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
2844
+ if (t.isVariableDeclarator(parent)) {
2845
+ const varDeclarationPath = parentPath?.parentPath;
2846
+ if (varDeclarationPath && t.isVariableDeclaration(varDeclarationPath.node) && (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))) {
2847
+ if (t.isIdentifier(parent.id)) {
2848
+ const name = parent.id.name;
2849
+ return /^[A-Z]/.test(name);
2850
+ }
2851
+ }
2852
+ }
2853
+ }
2854
+ return false;
2855
+ }
2856
+ function findComponentScope(path2, t) {
2857
+ let current = path2.getFunctionParent();
2858
+ while (current) {
2859
+ if (t.isFunction(current.node) && isComponentScope(current, t)) {
2860
+ return current;
2861
+ }
2862
+ current = current.getFunctionParent();
2863
+ }
2864
+ return null;
2865
+ }
2866
+
2867
+ // src/babel/plugin/visitors/className.ts
2868
+ function jsxAttributeVisitor(path2, state, t) {
2869
+ const node = path2.node;
2870
+ if (!t.isJSXIdentifier(node.name)) {
2871
+ return;
2872
+ }
2873
+ const attributeName = node.name.name;
2874
+ if (!isAttributeSupported(attributeName, state.supportedAttributes, state.attributePatterns)) {
2875
+ return;
2876
+ }
2877
+ const value = node.value;
2878
+ const targetStyleProp = getTargetStyleProp(attributeName);
2879
+ const processStaticClassName = (className) => {
2880
+ const trimmedClassName = className.trim();
2881
+ if (!trimmedClassName) {
2882
+ path2.remove();
2883
+ return true;
2884
+ }
2885
+ state.hasClassNames = true;
2886
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
2887
+ const modifierClasses = [];
2888
+ for (const modifier of rawModifierClasses) {
2889
+ if (isSchemeModifier(modifier.modifier)) {
2890
+ const expanded = expandSchemeModifier(
2891
+ modifier,
2892
+ state.customTheme.colors ?? {},
2893
+ state.schemeModifierConfig.darkSuffix,
2894
+ state.schemeModifierConfig.lightSuffix
2895
+ );
2896
+ modifierClasses.push(...expanded);
2897
+ } else {
2898
+ modifierClasses.push(modifier);
2899
+ }
2900
+ }
2901
+ const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
2902
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
2903
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
2904
+ const stateModifiers = modifierClasses.filter(
2905
+ (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
2906
+ );
2907
+ if (placeholderModifiers.length > 0) {
2908
+ const jsxOpeningElement = path2.parent;
2909
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2910
+ if (componentSupport?.supportedModifiers.includes("placeholder")) {
2911
+ const placeholderClasses = placeholderModifiers.map((m) => m.baseClass).join(" ");
2912
+ const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.customTheme.colors);
2913
+ if (placeholderColor) {
2914
+ addOrMergePlaceholderTextColorProp(jsxOpeningElement, placeholderColor, t);
2915
+ }
2916
+ } else {
2917
+ if (process.env.NODE_ENV !== "production") {
2918
+ console.warn(
2919
+ `[react-native-tailwind] placeholder: modifier can only be used on TextInput component at ${state.file.opts.filename ?? "unknown"}`
2920
+ );
2921
+ }
2922
+ }
2923
+ }
2924
+ const hasPlatformModifiers = platformModifiers.length > 0;
2925
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
2926
+ const hasStateModifiers = stateModifiers.length > 0;
2927
+ const hasBaseClasses = baseClasses.length > 0;
2928
+ let componentScope = null;
2929
+ if (hasColorSchemeModifiers) {
2930
+ componentScope = findComponentScope(path2, t);
2931
+ if (componentScope) {
2932
+ state.functionComponentsNeedingColorScheme.add(componentScope);
2933
+ } else {
2934
+ if (process.env.NODE_ENV !== "production") {
2935
+ console.warn(
2936
+ `[react-native-tailwind] dark:/light: modifiers require a function component scope. Found in non-component context at ${state.file.opts.filename ?? "unknown"}. These modifiers are not supported in class components or nested callbacks.`
2937
+ );
2938
+ }
2939
+ }
2940
+ }
2941
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
2942
+ const jsxOpeningElement = path2.parent;
2943
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2944
+ if (componentSupport) {
2945
+ const styleArrayElements = [];
2946
+ if (hasBaseClasses) {
2947
+ const baseClassName = baseClasses.join(" ");
2948
+ const baseStyleObject = parseClassName(baseClassName, state.customTheme);
2949
+ if (hasRuntimeDimensions(baseStyleObject)) {
2950
+ throw path2.buildCodeFrameError(
2951
+ `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with state, platform, or color scheme modifiers. Use w-screen/h-screen without modifiers instead.`
2952
+ );
2953
+ }
2954
+ const baseStyleKey = generateStyleKey(baseClassName);
2955
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2956
+ styleArrayElements.push(
2957
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2958
+ );
2959
+ }
2960
+ if (hasPlatformModifiers) {
2961
+ const platformSelectExpression = processPlatformModifiers(
2962
+ platformModifiers,
2963
+ state,
2964
+ parseClassName,
2965
+ generateStyleKey,
2966
+ t
2967
+ );
2968
+ styleArrayElements.push(platformSelectExpression);
2969
+ }
2970
+ if (hasColorSchemeModifiers && componentScope) {
2971
+ const colorSchemeConditionals = processColorSchemeModifiers(
2972
+ colorSchemeModifiers,
2973
+ state,
2974
+ parseClassName,
2975
+ generateStyleKey,
2976
+ t
2977
+ );
2978
+ styleArrayElements.push(...colorSchemeConditionals);
2979
+ }
2980
+ const modifiersByType = /* @__PURE__ */ new Map();
2981
+ for (const mod of stateModifiers) {
2982
+ const modType = mod.modifier;
2983
+ if (!modifiersByType.has(modType)) {
2984
+ modifiersByType.set(modType, []);
2985
+ }
2986
+ modifiersByType.get(modType)?.push(mod);
2987
+ }
2988
+ for (const [modifierType, modifiers] of modifiersByType) {
2989
+ if (!componentSupport.supportedModifiers.includes(modifierType)) {
2990
+ continue;
2991
+ }
2992
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
2993
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
2994
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
2995
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
2996
+ const stateProperty = getStatePropertyForModifier(modifierType);
2997
+ const conditionalExpression = t.logicalExpression(
2998
+ "&&",
2999
+ t.identifier(stateProperty),
3000
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey))
3001
+ );
3002
+ styleArrayElements.push(conditionalExpression);
3003
+ }
3004
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter(
3005
+ (mod) => componentSupport.supportedModifiers.includes(mod)
3006
+ );
3007
+ const styleArrayExpression = t.arrayExpression(styleArrayElements);
3008
+ const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
3009
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3010
+ if (styleAttribute2) {
3011
+ mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3012
+ } else {
3013
+ replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3014
+ }
3015
+ return true;
3016
+ } else {
3017
+ }
3018
+ }
3019
+ if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
3020
+ const styleExpressions = [];
3021
+ if (hasBaseClasses) {
3022
+ const baseClassName = baseClasses.join(" ");
3023
+ const baseStyleObject = parseClassName(baseClassName, state.customTheme);
3024
+ if (hasRuntimeDimensions(baseStyleObject)) {
3025
+ throw path2.buildCodeFrameError(
3026
+ `w-screen and h-screen cannot be combined with modifiers. Found: "${baseClassName}" with platform or color scheme modifiers. Use w-screen/h-screen without modifiers instead.`
3027
+ );
3028
+ }
3029
+ const baseStyleKey = generateStyleKey(baseClassName);
3030
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
3031
+ styleExpressions.push(
3032
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
3033
+ );
3034
+ }
3035
+ if (hasPlatformModifiers) {
3036
+ const platformSelectExpression = processPlatformModifiers(
3037
+ platformModifiers,
3038
+ state,
3039
+ parseClassName,
3040
+ generateStyleKey,
3041
+ t
3042
+ );
3043
+ styleExpressions.push(platformSelectExpression);
3044
+ }
3045
+ if (hasColorSchemeModifiers && componentScope) {
3046
+ const colorSchemeConditionals = processColorSchemeModifiers(
3047
+ colorSchemeModifiers,
3048
+ state,
3049
+ parseClassName,
3050
+ generateStyleKey,
3051
+ t
3052
+ );
3053
+ styleExpressions.push(...colorSchemeConditionals);
3054
+ }
3055
+ const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
3056
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3057
+ if (styleAttribute2) {
3058
+ const existingStyle = styleAttribute2.value;
3059
+ if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
3060
+ const existing = existingStyle.expression;
3061
+ const mergedArray = t.isArrayExpression(existing) ? t.arrayExpression([styleExpression, ...existing.elements]) : t.arrayExpression([styleExpression, existing]);
3062
+ styleAttribute2.value = t.jsxExpressionContainer(mergedArray);
3063
+ } else {
3064
+ styleAttribute2.value = t.jsxExpressionContainer(styleExpression);
3065
+ }
3066
+ path2.remove();
3067
+ } else {
3068
+ path2.node.name = t.jsxIdentifier(targetStyleProp);
3069
+ path2.node.value = t.jsxExpressionContainer(styleExpression);
3070
+ }
3071
+ return true;
3072
+ }
3073
+ if (hasStateModifiers) {
3074
+ const jsxOpeningElement = path2.parent;
3075
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
3076
+ if (componentSupport) {
3077
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
3078
+ const unsupportedModifiers = usedModifiers.filter(
3079
+ (mod) => !componentSupport.supportedModifiers.includes(mod)
3080
+ );
3081
+ if (unsupportedModifiers.length > 0) {
3082
+ if (process.env.NODE_ENV !== "production") {
3083
+ console.warn(
3084
+ `[react-native-tailwind] Modifiers (${unsupportedModifiers.map((m) => `${m}:`).join(", ")}) are not supported on ${componentSupport.component} component at ${state.file.opts.filename ?? "unknown"}. Supported modifiers: ${componentSupport.supportedModifiers.join(", ")}`
3085
+ );
3086
+ }
3087
+ const supportedModifierClasses = stateModifiers.filter(
3088
+ (m) => componentSupport.supportedModifiers.includes(m.modifier)
3089
+ );
3090
+ if (supportedModifierClasses.length === 0) {
3091
+ } else {
3092
+ const filteredClassName = baseClasses.join(" ") + " " + supportedModifierClasses.map((m) => `${m.modifier}:${m.baseClass}`).join(" ");
3093
+ const styleExpression = processStaticClassNameWithModifiers(
3094
+ filteredClassName.trim(),
3095
+ state,
3096
+ parseClassName,
3097
+ generateStyleKey,
3098
+ splitModifierClasses,
3099
+ t
3100
+ );
3101
+ const modifierTypes = Array.from(new Set(supportedModifierClasses.map((m) => m.modifier)));
3102
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
3103
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3104
+ if (styleAttribute2) {
3105
+ mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3106
+ } else {
3107
+ replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3108
+ }
3109
+ return true;
3110
+ }
3111
+ } else {
3112
+ const styleExpression = processStaticClassNameWithModifiers(
3113
+ trimmedClassName,
3114
+ state,
3115
+ parseClassName,
3116
+ generateStyleKey,
3117
+ splitModifierClasses,
3118
+ t
3119
+ );
3120
+ const modifierTypes = usedModifiers;
3121
+ const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
3122
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3123
+ if (styleAttribute2) {
3124
+ mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3125
+ } else {
3126
+ replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3127
+ }
3128
+ return true;
3129
+ }
3130
+ } else {
3131
+ if (process.env.NODE_ENV !== "production") {
3132
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
3133
+ console.warn(
3134
+ `[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"}`
3135
+ );
3136
+ }
3137
+ }
3138
+ }
3139
+ const classNameForStyle = baseClasses.join(" ");
3140
+ if (!classNameForStyle) {
3141
+ path2.remove();
3142
+ return true;
3143
+ }
3144
+ const styleObject = parseClassName(classNameForStyle, state.customTheme);
3145
+ if (hasRuntimeDimensions(styleObject)) {
3146
+ const { static: staticStyles, runtime: runtimeStyles } = splitStaticAndRuntimeStyles(styleObject);
3147
+ const componentScope2 = findComponentScope(path2, t);
3148
+ if (componentScope2) {
3149
+ state.hasClassNames = true;
3150
+ state.functionComponentsNeedingWindowDimensions.add(componentScope2);
3151
+ state.needsWindowDimensionsImport = true;
3152
+ const styleExpressions = [];
3153
+ if (Object.keys(staticStyles).length > 0) {
3154
+ const styleKey2 = generateStyleKey(classNameForStyle);
3155
+ state.styleRegistry.set(styleKey2, staticStyles);
3156
+ styleExpressions.push(
3157
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey2))
3158
+ );
3159
+ }
3160
+ const runtimeDimensionObject = createRuntimeDimensionObject(runtimeStyles, state, t);
3161
+ styleExpressions.push(runtimeDimensionObject);
3162
+ const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
3163
+ const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3164
+ if (styleAttribute2) {
3165
+ const existingStyle = styleAttribute2.value;
3166
+ if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
3167
+ const existing = existingStyle.expression;
3168
+ if (t.isArrowFunctionExpression(existing) || t.isFunctionExpression(existing)) {
3169
+ const paramIdentifier = t.identifier("_state");
3170
+ const functionCall = t.callExpression(existing, [paramIdentifier]);
3171
+ const mergedArray = t.arrayExpression([styleExpression, functionCall]);
3172
+ const wrappedFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
3173
+ styleAttribute2.value = t.jsxExpressionContainer(wrappedFunction);
3174
+ } else {
3175
+ const mergedArray = t.isArrayExpression(existing) ? t.arrayExpression([styleExpression, ...existing.elements]) : t.arrayExpression([styleExpression, existing]);
3176
+ styleAttribute2.value = t.jsxExpressionContainer(mergedArray);
3177
+ }
3178
+ } else {
3179
+ styleAttribute2.value = t.jsxExpressionContainer(styleExpression);
3180
+ }
3181
+ path2.remove();
3182
+ } else {
3183
+ path2.node.name = t.jsxIdentifier(targetStyleProp);
3184
+ path2.node.value = t.jsxExpressionContainer(styleExpression);
3185
+ }
3186
+ return true;
3187
+ } else {
3188
+ if (process.env.NODE_ENV !== "production") {
3189
+ console.warn(
3190
+ `[react-native-tailwind] w-screen/h-screen classes require a function component scope. Found in non-component context at ${state.file.opts.filename ?? "unknown"}. These classes are not supported in class components or nested callbacks.`
3191
+ );
3192
+ }
3193
+ }
3194
+ }
3195
+ const styleKey = generateStyleKey(classNameForStyle);
3196
+ state.styleRegistry.set(styleKey, styleObject);
3197
+ const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);
3198
+ if (styleAttribute) {
3199
+ mergeStyleAttribute(path2, styleAttribute, styleKey, state.stylesIdentifier, t);
3200
+ } else {
3201
+ replaceWithStyleAttribute(path2, styleKey, targetStyleProp, state.stylesIdentifier, t);
3202
+ }
3203
+ return true;
3204
+ };
3205
+ if (t.isStringLiteral(value)) {
3206
+ if (processStaticClassName(value.value)) {
3207
+ return;
2497
3208
  }
2498
3209
  }
2499
- const styleArrayElements = [];
2500
- if (baseStyleExpression) {
2501
- styleArrayElements.push(baseStyleExpression);
2502
- }
2503
- for (const [modifierType, modifiers] of modifiersByType) {
2504
- const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
2505
- const modifierStyleObject = parseClassName2(modifierClassNames, state.customTheme);
2506
- const modifierStyleKey = generateStyleKey2(`${modifierType}_${modifierClassNames}`);
2507
- state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
2508
- const stateProperty = getStatePropertyForModifier(modifierType);
2509
- const conditionalExpression = t.logicalExpression(
2510
- "&&",
2511
- t.identifier(stateProperty),
2512
- t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey))
2513
- );
2514
- styleArrayElements.push(conditionalExpression);
2515
- }
2516
- if (styleArrayElements.length === 1) {
2517
- return styleArrayElements[0];
2518
- }
2519
- return t.arrayExpression(styleArrayElements);
2520
- }
2521
- function createStyleFunction(styleExpression, modifierTypes, t) {
2522
- const paramProperties = [];
2523
- const usedStateProps = /* @__PURE__ */ new Set();
2524
- for (const modifierType of modifierTypes) {
2525
- const stateProperty = getStatePropertyForModifier(modifierType);
2526
- if (!usedStateProps.has(stateProperty)) {
2527
- usedStateProps.add(stateProperty);
2528
- paramProperties.push(
2529
- t.objectProperty(t.identifier(stateProperty), t.identifier(stateProperty), false, true)
3210
+ if (t.isJSXExpressionContainer(value)) {
3211
+ const expression = value.expression;
3212
+ if (t.isJSXEmptyExpression(expression)) {
3213
+ return;
3214
+ }
3215
+ if (t.isStringLiteral(expression)) {
3216
+ if (processStaticClassName(expression.value)) {
3217
+ return;
3218
+ }
3219
+ }
3220
+ try {
3221
+ const componentScope = findComponentScope(path2, t);
3222
+ const result = processDynamicExpression(
3223
+ expression,
3224
+ state,
3225
+ parseClassName,
3226
+ generateStyleKey,
3227
+ splitModifierClasses,
3228
+ processPlatformModifiers,
3229
+ processColorSchemeModifiers,
3230
+ componentScope,
3231
+ isPlatformModifier,
3232
+ isColorSchemeModifier,
3233
+ isSchemeModifier,
3234
+ expandSchemeModifier,
3235
+ t
2530
3236
  );
3237
+ if (result) {
3238
+ state.hasClassNames = true;
3239
+ const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);
3240
+ if (styleAttribute) {
3241
+ mergeDynamicStyleAttribute(path2, styleAttribute, result, t);
3242
+ } else {
3243
+ replaceDynamicWithStyleAttribute(path2, result, targetStyleProp, t);
3244
+ }
3245
+ return;
3246
+ }
3247
+ } catch (error) {
3248
+ if (process.env.NODE_ENV !== "production") {
3249
+ console.warn(
3250
+ `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`
3251
+ );
3252
+ }
2531
3253
  }
2532
3254
  }
2533
- const param = t.objectPattern(paramProperties);
2534
- return t.arrowFunctionExpression([param], styleExpression);
3255
+ if (process.env.NODE_ENV !== "production") {
3256
+ const filename = state.file.opts.filename ?? "unknown";
3257
+ console.warn(
3258
+ `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. Use the ${targetStyleProp} prop for dynamic values.`
3259
+ );
3260
+ }
2535
3261
  }
2536
3262
 
2537
- // src/babel/utils/platformModifierProcessing.ts
2538
- function processPlatformModifiers(platformModifiers, state, parseClassName2, generateStyleKey2, t) {
2539
- state.needsPlatformImport = true;
2540
- const modifiersByPlatform = /* @__PURE__ */ new Map();
2541
- for (const mod of platformModifiers) {
2542
- const platform = mod.modifier;
2543
- if (!modifiersByPlatform.has(platform)) {
2544
- modifiersByPlatform.set(platform, []);
3263
+ // src/babel/plugin/visitors/imports.ts
3264
+ function importDeclarationVisitor(path2, state, t) {
3265
+ const node = path2.node;
3266
+ if (node.source.value === "react-native") {
3267
+ const specifiers = node.specifiers;
3268
+ const hasStyleSheet = specifiers.some((spec) => {
3269
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3270
+ return spec.imported.name === "StyleSheet";
3271
+ }
3272
+ return false;
3273
+ });
3274
+ const hasPlatform = specifiers.some((spec) => {
3275
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3276
+ return spec.imported.name === "Platform";
3277
+ }
3278
+ return false;
3279
+ });
3280
+ if (node.importKind !== "type") {
3281
+ for (const spec of specifiers) {
3282
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3283
+ if (spec.imported.name === "useWindowDimensions") {
3284
+ state.hasWindowDimensionsImport = true;
3285
+ state.windowDimensionsLocalIdentifier = spec.local.name;
3286
+ break;
3287
+ }
3288
+ }
3289
+ }
2545
3290
  }
2546
- const platformGroup = modifiersByPlatform.get(platform);
2547
- if (platformGroup) {
2548
- platformGroup.push(mod);
3291
+ if (hasStyleSheet) {
3292
+ state.hasStyleSheetImport = true;
2549
3293
  }
3294
+ if (hasPlatform) {
3295
+ state.hasPlatformImport = true;
3296
+ }
3297
+ state.reactNativeImportPath = path2;
2550
3298
  }
2551
- const selectProperties = [];
2552
- for (const [platform, modifiers] of modifiersByPlatform) {
2553
- const classNames = modifiers.map((m) => m.baseClass).join(" ");
2554
- const styleObject = parseClassName2(classNames, state.customTheme);
2555
- const styleKey = generateStyleKey2(`${platform}_${classNames}`);
2556
- state.styleRegistry.set(styleKey, styleObject);
2557
- const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2558
- selectProperties.push(t.objectProperty(t.identifier(platform), styleReference));
3299
+ if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
3300
+ const specifiers = node.specifiers;
3301
+ for (const spec of specifiers) {
3302
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3303
+ if (spec.imported.name === state.colorSchemeHookName) {
3304
+ state.hasColorSchemeImport = true;
3305
+ state.colorSchemeLocalIdentifier = spec.local.name;
3306
+ break;
3307
+ }
3308
+ }
3309
+ }
3310
+ }
3311
+ if (node.source.value === "@mgcrea/react-native-tailwind") {
3312
+ const specifiers = node.specifiers;
3313
+ specifiers.forEach((spec) => {
3314
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3315
+ const importedName = spec.imported.name;
3316
+ if (importedName === "tw" || importedName === "twStyle") {
3317
+ const localName = spec.local.name;
3318
+ state.twImportNames.add(localName);
3319
+ }
3320
+ }
3321
+ });
2559
3322
  }
2560
- return t.callExpression(t.memberExpression(t.identifier("Platform"), t.identifier("select")), [
2561
- t.objectExpression(selectProperties)
2562
- ]);
2563
3323
  }
2564
3324
 
2565
3325
  // src/babel/utils/styleInjection.ts
@@ -2687,152 +3447,100 @@ function injectColorSchemeHook(functionPath, colorSchemeVariableName, hookName,
2687
3447
  body.body.unshift(hookCall);
2688
3448
  return true;
2689
3449
  }
2690
- function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
2691
- const styleProperties = [];
2692
- for (const [key, styleObject] of styleRegistry) {
2693
- const properties = Object.entries(styleObject).map(([styleProp, styleValue]) => {
2694
- let valueNode;
2695
- if (typeof styleValue === "number") {
2696
- valueNode = t.numericLiteral(styleValue);
2697
- } else if (typeof styleValue === "string") {
2698
- valueNode = t.stringLiteral(styleValue);
2699
- } else {
2700
- valueNode = t.valueToNode(styleValue);
2701
- }
2702
- return t.objectProperty(t.identifier(styleProp), valueNode);
2703
- });
2704
- styleProperties.push(t.objectProperty(t.identifier(key), t.objectExpression(properties)));
2705
- }
2706
- const styleSheet = t.variableDeclaration("const", [
2707
- t.variableDeclarator(
2708
- t.identifier(stylesIdentifier),
2709
- t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
2710
- t.objectExpression(styleProperties)
2711
- ])
2712
- )
2713
- ]);
3450
+ function addWindowDimensionsImport(path2, t) {
2714
3451
  const body = path2.node.body;
2715
- let insertIndex = 0;
2716
- for (let i = 0; i < body.length; i++) {
2717
- if (t.isImportDeclaration(body[i])) {
2718
- insertIndex = i + 1;
2719
- } else {
2720
- break;
3452
+ let existingValueImport = null;
3453
+ for (const statement of body) {
3454
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
3455
+ if (statement.importKind !== "type") {
3456
+ existingValueImport = statement;
3457
+ break;
3458
+ }
2721
3459
  }
2722
3460
  }
2723
- body.splice(insertIndex, 0, styleSheet);
2724
- }
2725
-
2726
- // src/babel/utils/styleTransforms.ts
2727
- function getStyleExpression(styleAttribute, t) {
2728
- const value = styleAttribute.value;
2729
- if (!t.isJSXExpressionContainer(value)) return null;
2730
- const expression = value.expression;
2731
- if (t.isJSXEmptyExpression(expression)) return null;
2732
- return expression;
2733
- }
2734
- function findStyleAttribute(path2, targetStyleProp, t) {
2735
- const parent = path2.parent;
2736
- return parent.attributes.find(
2737
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === targetStyleProp
2738
- );
2739
- }
2740
- function replaceWithStyleAttribute(classNamePath, styleKey, targetStyleProp, stylesIdentifier, t) {
2741
- const styleAttribute = t.jsxAttribute(
2742
- t.jsxIdentifier(targetStyleProp),
2743
- t.jsxExpressionContainer(t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)))
2744
- );
2745
- classNamePath.replaceWith(styleAttribute);
2746
- }
2747
- function mergeStyleAttribute(classNamePath, styleAttribute, styleKey, stylesIdentifier, t) {
2748
- const existingStyle = getStyleExpression(styleAttribute, t);
2749
- if (!existingStyle) return;
2750
- if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2751
- const paramIdentifier = t.identifier("_state");
2752
- const functionCall = t.callExpression(existingStyle, [paramIdentifier]);
2753
- const mergedArray = t.arrayExpression([
2754
- t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)),
2755
- functionCall
2756
- ]);
2757
- const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2758
- styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2759
- } else {
2760
- const styleArray = t.arrayExpression([
2761
- t.memberExpression(t.identifier(stylesIdentifier), t.identifier(styleKey)),
2762
- existingStyle
2763
- ]);
2764
- styleAttribute.value = t.jsxExpressionContainer(styleArray);
2765
- }
2766
- classNamePath.remove();
2767
- }
2768
- function replaceDynamicWithStyleAttribute(classNamePath, result, targetStyleProp, t) {
2769
- const styleAttribute = t.jsxAttribute(
2770
- t.jsxIdentifier(targetStyleProp),
2771
- t.jsxExpressionContainer(result.expression)
2772
- );
2773
- classNamePath.replaceWith(styleAttribute);
2774
- }
2775
- function mergeDynamicStyleAttribute(classNamePath, styleAttribute, result, t) {
2776
- const existingStyle = getStyleExpression(styleAttribute, t);
2777
- if (!existingStyle) return;
2778
- if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2779
- const paramIdentifier = t.identifier("_state");
2780
- const functionCall = t.callExpression(existingStyle, [paramIdentifier]);
2781
- const mergedArray = t.arrayExpression([result.expression, functionCall]);
2782
- const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2783
- styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2784
- } else {
2785
- let styleArray;
2786
- if (t.isArrayExpression(existingStyle)) {
2787
- styleArray = t.arrayExpression([result.expression, ...existingStyle.elements]);
2788
- } else {
2789
- styleArray = t.arrayExpression([result.expression, existingStyle]);
3461
+ if (existingValueImport) {
3462
+ const hasHook = existingValueImport.specifiers.some(
3463
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "useWindowDimensions"
3464
+ );
3465
+ if (!hasHook) {
3466
+ existingValueImport.specifiers.push(
3467
+ t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions"))
3468
+ );
2790
3469
  }
2791
- styleAttribute.value = t.jsxExpressionContainer(styleArray);
3470
+ } else {
3471
+ const importDeclaration = t.importDeclaration(
3472
+ [t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions"))],
3473
+ t.stringLiteral("react-native")
3474
+ );
3475
+ path2.unshiftContainer("body", importDeclaration);
2792
3476
  }
2793
- classNamePath.remove();
2794
- }
2795
- function replaceWithStyleFunctionAttribute(classNamePath, styleFunctionExpression, targetStyleProp, t) {
2796
- const styleAttribute = t.jsxAttribute(
2797
- t.jsxIdentifier(targetStyleProp),
2798
- t.jsxExpressionContainer(styleFunctionExpression)
2799
- );
2800
- classNamePath.replaceWith(styleAttribute);
2801
3477
  }
2802
- function mergeStyleFunctionAttribute(classNamePath, styleAttribute, styleFunctionExpression, t) {
2803
- const existingStyle = getStyleExpression(styleAttribute, t);
2804
- if (!existingStyle) return;
2805
- if (t.isArrowFunctionExpression(existingStyle) || t.isFunctionExpression(existingStyle)) {
2806
- const paramIdentifier = t.identifier("_state");
2807
- const newFunctionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
2808
- const existingFunctionCall = t.callExpression(existingStyle, [paramIdentifier]);
2809
- const mergedArray = t.arrayExpression([newFunctionCall, existingFunctionCall]);
2810
- const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2811
- styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
2812
- } else {
2813
- const paramIdentifier = t.identifier("_state");
2814
- const functionCall = t.callExpression(styleFunctionExpression, [paramIdentifier]);
2815
- const mergedArray = t.arrayExpression([functionCall, existingStyle]);
2816
- const wrapperFunction = t.arrowFunctionExpression([paramIdentifier], mergedArray);
2817
- styleAttribute.value = t.jsxExpressionContainer(wrapperFunction);
3478
+ function injectWindowDimensionsHook(functionPath, dimensionsVariableName, hookName, localIdentifier, t) {
3479
+ let body = functionPath.node.body;
3480
+ if (!t.isBlockStatement(body)) {
3481
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
3482
+ const returnStatement = t.returnStatement(body);
3483
+ const blockStatement = t.blockStatement([returnStatement]);
3484
+ functionPath.node.body = blockStatement;
3485
+ body = blockStatement;
3486
+ } else {
3487
+ return false;
3488
+ }
2818
3489
  }
2819
- classNamePath.remove();
3490
+ const hasHook = body.body.some((statement) => {
3491
+ if (t.isVariableDeclaration(statement) && statement.declarations.length > 0 && t.isVariableDeclarator(statement.declarations[0])) {
3492
+ const declarator = statement.declarations[0];
3493
+ return t.isIdentifier(declarator.id) && declarator.id.name === dimensionsVariableName;
3494
+ }
3495
+ return false;
3496
+ });
3497
+ if (hasHook) {
3498
+ return false;
3499
+ }
3500
+ const identifierToCall = localIdentifier ?? hookName;
3501
+ const hookCall = t.variableDeclaration("const", [
3502
+ t.variableDeclarator(
3503
+ t.identifier(dimensionsVariableName),
3504
+ t.callExpression(t.identifier(identifierToCall), [])
3505
+ )
3506
+ ]);
3507
+ body.body.unshift(hookCall);
3508
+ return true;
2820
3509
  }
2821
- function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2822
- const existingProp = jsxOpeningElement.attributes.find(
2823
- (attr) => t.isJSXAttribute(attr) && attr.name.name === "placeholderTextColor"
2824
- );
2825
- if (existingProp) {
2826
- if (process.env.NODE_ENV !== "production") {
2827
- console.warn(
2828
- `[react-native-tailwind] placeholderTextColor prop will be overridden by className placeholder: modifier. Remove the explicit prop or the placeholder: modifier to avoid confusion.`
2829
- );
3510
+ function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
3511
+ const styleProperties = [];
3512
+ for (const [key, styleObject] of styleRegistry) {
3513
+ const properties = Object.entries(styleObject).map(([styleProp, styleValue]) => {
3514
+ let valueNode;
3515
+ if (typeof styleValue === "number") {
3516
+ valueNode = t.numericLiteral(styleValue);
3517
+ } else if (typeof styleValue === "string") {
3518
+ valueNode = t.stringLiteral(styleValue);
3519
+ } else {
3520
+ valueNode = t.valueToNode(styleValue);
3521
+ }
3522
+ return t.objectProperty(t.identifier(styleProp), valueNode);
3523
+ });
3524
+ styleProperties.push(t.objectProperty(t.identifier(key), t.objectExpression(properties)));
3525
+ }
3526
+ const styleSheet = t.variableDeclaration("const", [
3527
+ t.variableDeclarator(
3528
+ t.identifier(stylesIdentifier),
3529
+ t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
3530
+ t.objectExpression(styleProperties)
3531
+ ])
3532
+ )
3533
+ ]);
3534
+ const body = path2.node.body;
3535
+ let insertIndex = 0;
3536
+ for (let i = 0; i < body.length; i++) {
3537
+ if (t.isImportDeclaration(body[i])) {
3538
+ insertIndex = i + 1;
3539
+ } else {
3540
+ break;
2830
3541
  }
2831
- existingProp.value = t.stringLiteral(color);
2832
- } else {
2833
- const newProp = t.jsxAttribute(t.jsxIdentifier("placeholderTextColor"), t.stringLiteral(color));
2834
- jsxOpeningElement.attributes.push(newProp);
2835
3542
  }
3543
+ body.splice(insertIndex, 0, styleSheet);
2836
3544
  }
2837
3545
 
2838
3546
  // src/babel/utils/twProcessing.ts
@@ -2856,6 +3564,11 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
2856
3564
  if (baseClasses.length > 0) {
2857
3565
  const baseClassName = baseClasses.join(" ");
2858
3566
  const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
3567
+ if (hasRuntimeDimensions(baseStyleObject)) {
3568
+ throw path2.buildCodeFrameError(
3569
+ `w-screen and h-screen are not supported in tw\`\` or twStyle() calls. Found: "${baseClassName}". Use them in className attributes instead.`
3570
+ );
3571
+ }
2859
3572
  const baseStyleKey = generateStyleKey2(baseClassName);
2860
3573
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
2861
3574
  objectProperties.push(
@@ -3053,586 +3766,180 @@ function removeTwImports(path2, t) {
3053
3766
  });
3054
3767
  }
3055
3768
 
3056
- // src/babel/plugin.ts
3057
- var DEFAULT_STYLES_IDENTIFIER = "_twStyles";
3058
- function isComponentScope(functionPath, t) {
3059
- const node = functionPath.node;
3060
- const parent = functionPath.parent;
3061
- const parentPath = functionPath.parentPath;
3062
- if (t.isClassMethod(parent)) {
3063
- return false;
3769
+ // src/babel/plugin/visitors/program.ts
3770
+ function programEnter(_path, _state) {
3771
+ }
3772
+ function programExit(path2, state, t) {
3773
+ if (state.hasTwImport) {
3774
+ removeTwImports(path2, t);
3064
3775
  }
3065
- if (functionPath.findParent((p) => t.isClassBody(p.node))) {
3066
- return false;
3776
+ if (!state.hasClassNames && !state.needsWindowDimensionsImport && !state.needsColorSchemeImport) {
3777
+ return;
3067
3778
  }
3068
- if (t.isFunctionDeclaration(node)) {
3069
- if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
3070
- return true;
3779
+ if (!state.hasStyleSheetImport && state.styleRegistry.size > 0) {
3780
+ addStyleSheetImport(path2, t);
3781
+ }
3782
+ if (state.needsPlatformImport && !state.hasPlatformImport) {
3783
+ addPlatformImport(path2, t);
3784
+ }
3785
+ if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
3786
+ addColorSchemeImport(path2, state.colorSchemeImportSource, state.colorSchemeHookName, t);
3787
+ }
3788
+ if (state.needsColorSchemeImport) {
3789
+ for (const functionPath of state.functionComponentsNeedingColorScheme) {
3790
+ injectColorSchemeHook(
3791
+ functionPath,
3792
+ state.colorSchemeVariableName,
3793
+ state.colorSchemeHookName,
3794
+ state.colorSchemeLocalIdentifier,
3795
+ t
3796
+ );
3071
3797
  }
3072
3798
  }
3073
- if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
3074
- if (t.isVariableDeclarator(parent)) {
3075
- const varDeclarationPath = parentPath?.parentPath;
3076
- if (varDeclarationPath && t.isVariableDeclaration(varDeclarationPath.node) && (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))) {
3077
- if (t.isIdentifier(parent.id)) {
3078
- const name = parent.id.name;
3079
- return /^[A-Z]/.test(name);
3080
- }
3081
- }
3799
+ if (state.needsWindowDimensionsImport && !state.hasWindowDimensionsImport) {
3800
+ addWindowDimensionsImport(path2, t);
3801
+ }
3802
+ if (state.needsWindowDimensionsImport) {
3803
+ for (const functionPath of state.functionComponentsNeedingWindowDimensions) {
3804
+ injectWindowDimensionsHook(
3805
+ functionPath,
3806
+ state.windowDimensionsVariableName,
3807
+ "useWindowDimensions",
3808
+ state.windowDimensionsLocalIdentifier,
3809
+ t
3810
+ );
3082
3811
  }
3083
3812
  }
3084
- return false;
3813
+ if (state.styleRegistry.size > 0) {
3814
+ injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
3815
+ }
3085
3816
  }
3086
- function findComponentScope(path2, t) {
3087
- let current = path2.getFunctionParent();
3088
- while (current) {
3089
- if (t.isFunction(current.node) && isComponentScope(current, t)) {
3090
- return current;
3817
+
3818
+ // src/babel/plugin/visitors/tw.ts
3819
+ function taggedTemplateVisitor(path2, state, t) {
3820
+ const node = path2.node;
3821
+ if (!t.isIdentifier(node.tag)) {
3822
+ return;
3823
+ }
3824
+ const tagName = node.tag.name;
3825
+ if (!state.twImportNames.has(tagName)) {
3826
+ return;
3827
+ }
3828
+ const quasi = node.quasi;
3829
+ if (!t.isTemplateLiteral(quasi)) {
3830
+ return;
3831
+ }
3832
+ if (quasi.expressions.length > 0) {
3833
+ if (process.env.NODE_ENV !== "production") {
3834
+ console.warn(
3835
+ `[react-native-tailwind] Dynamic tw\`...\` with interpolations is not supported at ${state.file.opts.filename ?? "unknown"}. Use style prop for dynamic values.`
3836
+ );
3091
3837
  }
3092
- current = current.getFunctionParent();
3838
+ return;
3093
3839
  }
3094
- return null;
3840
+ const className = quasi.quasis[0]?.value.cooked?.trim() ?? "";
3841
+ if (!className) {
3842
+ path2.replaceWith(t.objectExpression([t.objectProperty(t.identifier("style"), t.objectExpression([]))]));
3843
+ state.hasTwImport = true;
3844
+ return;
3845
+ }
3846
+ state.hasClassNames = true;
3847
+ processTwCall(
3848
+ className,
3849
+ path2,
3850
+ state,
3851
+ parseClassName,
3852
+ generateStyleKey,
3853
+ splitModifierClasses,
3854
+ findComponentScope,
3855
+ t
3856
+ );
3857
+ state.hasTwImport = true;
3858
+ }
3859
+ function callExpressionVisitor(path2, state, t) {
3860
+ const node = path2.node;
3861
+ if (!t.isIdentifier(node.callee)) {
3862
+ return;
3863
+ }
3864
+ const calleeName = node.callee.name;
3865
+ if (!state.twImportNames.has(calleeName)) {
3866
+ return;
3867
+ }
3868
+ if (node.arguments.length !== 1) {
3869
+ if (process.env.NODE_ENV !== "production") {
3870
+ console.warn(
3871
+ `[react-native-tailwind] twStyle() expects exactly one argument at ${state.file.opts.filename ?? "unknown"}`
3872
+ );
3873
+ }
3874
+ return;
3875
+ }
3876
+ const arg = node.arguments[0];
3877
+ if (!t.isStringLiteral(arg)) {
3878
+ if (process.env.NODE_ENV !== "production") {
3879
+ console.warn(
3880
+ `[react-native-tailwind] twStyle() only supports static string literals at ${state.file.opts.filename ?? "unknown"}. Use style prop for dynamic values.`
3881
+ );
3882
+ }
3883
+ return;
3884
+ }
3885
+ const className = arg.value.trim();
3886
+ if (!className) {
3887
+ path2.replaceWith(t.identifier("undefined"));
3888
+ state.hasTwImport = true;
3889
+ return;
3890
+ }
3891
+ state.hasClassNames = true;
3892
+ processTwCall(
3893
+ className,
3894
+ path2,
3895
+ state,
3896
+ parseClassName,
3897
+ generateStyleKey,
3898
+ splitModifierClasses,
3899
+ findComponentScope,
3900
+ t
3901
+ );
3902
+ state.hasTwImport = true;
3095
3903
  }
3904
+
3905
+ // src/babel/plugin.ts
3096
3906
  function reactNativeTailwindBabelPlugin({ types: t }, options) {
3097
- const attributes = options?.attributes ?? [...DEFAULT_CLASS_ATTRIBUTES];
3098
- const { exactMatches, patterns } = buildAttributeMatchers(attributes);
3099
- const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
3907
+ const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
3908
+ const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
3100
3909
  const schemeModifierConfig = {
3101
3910
  darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
3102
3911
  lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light"
3103
3912
  };
3104
- const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
3105
- const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
3106
3913
  return {
3107
3914
  name: "react-native-tailwind",
3108
3915
  visitor: {
3109
3916
  Program: {
3110
- enter(_path, state) {
3111
- state.styleRegistry = /* @__PURE__ */ new Map();
3112
- state.hasClassNames = false;
3113
- state.hasStyleSheetImport = false;
3114
- state.hasPlatformImport = false;
3115
- state.needsPlatformImport = false;
3116
- state.hasColorSchemeImport = false;
3117
- state.needsColorSchemeImport = false;
3118
- state.colorSchemeVariableName = "_twColorScheme";
3119
- state.colorSchemeImportSource = colorSchemeImportSource;
3120
- state.colorSchemeHookName = colorSchemeHookName;
3121
- state.supportedAttributes = exactMatches;
3122
- state.attributePatterns = patterns;
3123
- state.stylesIdentifier = stylesIdentifier;
3124
- state.twImportNames = /* @__PURE__ */ new Set();
3125
- state.hasTwImport = false;
3126
- state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
3127
- state.hasColorSchemeImport = false;
3128
- state.colorSchemeLocalIdentifier = void 0;
3129
- state.needsPlatformImport = false;
3130
- state.hasPlatformImport = false;
3131
- state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
3132
- state.schemeModifierConfig = schemeModifierConfig;
3917
+ enter(path2, state) {
3918
+ const initialState = createInitialState(
3919
+ options,
3920
+ state.file.opts.filename ?? "",
3921
+ colorSchemeImportSource,
3922
+ colorSchemeHookName,
3923
+ schemeModifierConfig
3924
+ );
3925
+ Object.assign(state, initialState);
3926
+ programEnter(path2, state);
3133
3927
  },
3134
3928
  exit(path2, state) {
3135
- if (state.hasTwImport) {
3136
- removeTwImports(path2, t);
3137
- }
3138
- if (!state.hasClassNames || state.styleRegistry.size === 0) {
3139
- return;
3140
- }
3141
- if (!state.hasStyleSheetImport) {
3142
- addStyleSheetImport(path2, t);
3143
- }
3144
- if (state.needsPlatformImport && !state.hasPlatformImport) {
3145
- addPlatformImport(path2, t);
3146
- }
3147
- if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
3148
- addColorSchemeImport(path2, state.colorSchemeImportSource, state.colorSchemeHookName, t);
3149
- }
3150
- if (state.needsColorSchemeImport) {
3151
- for (const functionPath of state.functionComponentsNeedingColorScheme) {
3152
- injectColorSchemeHook(
3153
- functionPath,
3154
- state.colorSchemeVariableName,
3155
- state.colorSchemeHookName,
3156
- state.colorSchemeLocalIdentifier,
3157
- t
3158
- );
3159
- }
3160
- }
3161
- injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
3929
+ programExit(path2, state, t);
3162
3930
  }
3163
3931
  },
3164
- // Check if StyleSheet/Platform are already imported and track tw/twStyle imports
3165
3932
  ImportDeclaration(path2, state) {
3166
- const node = path2.node;
3167
- if (node.source.value === "react-native") {
3168
- const specifiers = node.specifiers;
3169
- const hasStyleSheet = specifiers.some((spec) => {
3170
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3171
- return spec.imported.name === "StyleSheet";
3172
- }
3173
- return false;
3174
- });
3175
- const hasPlatform = specifiers.some((spec) => {
3176
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3177
- return spec.imported.name === "Platform";
3178
- }
3179
- return false;
3180
- });
3181
- if (hasStyleSheet) {
3182
- state.hasStyleSheetImport = true;
3183
- }
3184
- if (hasPlatform) {
3185
- state.hasPlatformImport = true;
3186
- }
3187
- state.reactNativeImportPath = path2;
3188
- }
3189
- if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
3190
- const specifiers = node.specifiers;
3191
- for (const spec of specifiers) {
3192
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3193
- if (spec.imported.name === state.colorSchemeHookName) {
3194
- state.hasColorSchemeImport = true;
3195
- state.colorSchemeLocalIdentifier = spec.local.name;
3196
- break;
3197
- }
3198
- }
3199
- }
3200
- }
3201
- if (node.source.value === "@mgcrea/react-native-tailwind") {
3202
- const specifiers = node.specifiers;
3203
- specifiers.forEach((spec) => {
3204
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3205
- const importedName = spec.imported.name;
3206
- if (importedName === "tw" || importedName === "twStyle") {
3207
- const localName = spec.local.name;
3208
- state.twImportNames.add(localName);
3209
- }
3210
- }
3211
- });
3212
- }
3933
+ importDeclarationVisitor(path2, state, t);
3213
3934
  },
3214
- // Handle tw`...` tagged template expressions
3215
3935
  TaggedTemplateExpression(path2, state) {
3216
- const node = path2.node;
3217
- if (!t.isIdentifier(node.tag)) {
3218
- return;
3219
- }
3220
- const tagName = node.tag.name;
3221
- if (!state.twImportNames.has(tagName)) {
3222
- return;
3223
- }
3224
- const quasi = node.quasi;
3225
- if (!t.isTemplateLiteral(quasi)) {
3226
- return;
3227
- }
3228
- if (quasi.expressions.length > 0) {
3229
- if (process.env.NODE_ENV !== "production") {
3230
- console.warn(
3231
- `[react-native-tailwind] Dynamic tw\`...\` with interpolations is not supported at ${state.file.opts.filename ?? "unknown"}. Use style prop for dynamic values.`
3232
- );
3233
- }
3234
- return;
3235
- }
3236
- const className = quasi.quasis[0]?.value.cooked?.trim() ?? "";
3237
- if (!className) {
3238
- path2.replaceWith(
3239
- t.objectExpression([t.objectProperty(t.identifier("style"), t.objectExpression([]))])
3240
- );
3241
- state.hasTwImport = true;
3242
- return;
3243
- }
3244
- state.hasClassNames = true;
3245
- processTwCall(
3246
- className,
3247
- path2,
3248
- state,
3249
- parseClassName,
3250
- generateStyleKey,
3251
- splitModifierClasses,
3252
- findComponentScope,
3253
- t
3254
- );
3255
- state.hasTwImport = true;
3936
+ taggedTemplateVisitor(path2, state, t);
3256
3937
  },
3257
- // Handle twStyle('...') call expressions
3258
3938
  CallExpression(path2, state) {
3259
- const node = path2.node;
3260
- if (!t.isIdentifier(node.callee)) {
3261
- return;
3262
- }
3263
- const calleeName = node.callee.name;
3264
- if (!state.twImportNames.has(calleeName)) {
3265
- return;
3266
- }
3267
- if (node.arguments.length !== 1) {
3268
- if (process.env.NODE_ENV !== "production") {
3269
- console.warn(
3270
- `[react-native-tailwind] twStyle() expects exactly one argument at ${state.file.opts.filename ?? "unknown"}`
3271
- );
3272
- }
3273
- return;
3274
- }
3275
- const arg = node.arguments[0];
3276
- if (!t.isStringLiteral(arg)) {
3277
- if (process.env.NODE_ENV !== "production") {
3278
- console.warn(
3279
- `[react-native-tailwind] twStyle() only supports static string literals at ${state.file.opts.filename ?? "unknown"}. Use style prop for dynamic values.`
3280
- );
3281
- }
3282
- return;
3283
- }
3284
- const className = arg.value.trim();
3285
- if (!className) {
3286
- path2.replaceWith(t.identifier("undefined"));
3287
- state.hasTwImport = true;
3288
- return;
3289
- }
3290
- state.hasClassNames = true;
3291
- processTwCall(
3292
- className,
3293
- path2,
3294
- state,
3295
- parseClassName,
3296
- generateStyleKey,
3297
- splitModifierClasses,
3298
- findComponentScope,
3299
- t
3300
- );
3301
- state.hasTwImport = true;
3939
+ callExpressionVisitor(path2, state, t);
3302
3940
  },
3303
3941
  JSXAttribute(path2, state) {
3304
- const node = path2.node;
3305
- if (!t.isJSXIdentifier(node.name)) {
3306
- return;
3307
- }
3308
- const attributeName = node.name.name;
3309
- if (!isAttributeSupported(attributeName, state.supportedAttributes, state.attributePatterns)) {
3310
- return;
3311
- }
3312
- const value = node.value;
3313
- const targetStyleProp = getTargetStyleProp(attributeName);
3314
- const processStaticClassName = (className) => {
3315
- const trimmedClassName = className.trim();
3316
- if (!trimmedClassName) {
3317
- path2.remove();
3318
- return true;
3319
- }
3320
- state.hasClassNames = true;
3321
- const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
3322
- const modifierClasses = [];
3323
- for (const modifier of rawModifierClasses) {
3324
- if (isSchemeModifier(modifier.modifier)) {
3325
- const expanded = expandSchemeModifier(
3326
- modifier,
3327
- state.customTheme.colors ?? {},
3328
- state.schemeModifierConfig.darkSuffix,
3329
- state.schemeModifierConfig.lightSuffix
3330
- );
3331
- modifierClasses.push(...expanded);
3332
- } else {
3333
- modifierClasses.push(modifier);
3334
- }
3335
- }
3336
- const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
3337
- const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
3338
- const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
3339
- const stateModifiers = modifierClasses.filter(
3340
- (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
3341
- );
3342
- if (placeholderModifiers.length > 0) {
3343
- const jsxOpeningElement = path2.parent;
3344
- const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
3345
- if (componentSupport?.supportedModifiers.includes("placeholder")) {
3346
- const placeholderClasses = placeholderModifiers.map((m) => m.baseClass).join(" ");
3347
- const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.customTheme.colors);
3348
- if (placeholderColor) {
3349
- addOrMergePlaceholderTextColorProp(jsxOpeningElement, placeholderColor, t);
3350
- }
3351
- } else {
3352
- if (process.env.NODE_ENV !== "production") {
3353
- console.warn(
3354
- `[react-native-tailwind] placeholder: modifier can only be used on TextInput component at ${state.file.opts.filename ?? "unknown"}`
3355
- );
3356
- }
3357
- }
3358
- }
3359
- const hasPlatformModifiers = platformModifiers.length > 0;
3360
- const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
3361
- const hasStateModifiers = stateModifiers.length > 0;
3362
- const hasBaseClasses = baseClasses.length > 0;
3363
- let componentScope = null;
3364
- if (hasColorSchemeModifiers) {
3365
- componentScope = findComponentScope(path2, t);
3366
- if (componentScope) {
3367
- state.functionComponentsNeedingColorScheme.add(componentScope);
3368
- } else {
3369
- if (process.env.NODE_ENV !== "production") {
3370
- console.warn(
3371
- `[react-native-tailwind] dark:/light: modifiers require a function component scope. Found in non-component context at ${state.file.opts.filename ?? "unknown"}. These modifiers are not supported in class components or nested callbacks.`
3372
- );
3373
- }
3374
- }
3375
- }
3376
- if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
3377
- const jsxOpeningElement = path2.parent;
3378
- const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
3379
- if (componentSupport) {
3380
- const styleArrayElements = [];
3381
- if (hasBaseClasses) {
3382
- const baseClassName = baseClasses.join(" ");
3383
- const baseStyleObject = parseClassName(baseClassName, state.customTheme);
3384
- const baseStyleKey = generateStyleKey(baseClassName);
3385
- state.styleRegistry.set(baseStyleKey, baseStyleObject);
3386
- styleArrayElements.push(
3387
- t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
3388
- );
3389
- }
3390
- if (hasPlatformModifiers) {
3391
- const platformSelectExpression = processPlatformModifiers(
3392
- platformModifiers,
3393
- state,
3394
- parseClassName,
3395
- generateStyleKey,
3396
- t
3397
- );
3398
- styleArrayElements.push(platformSelectExpression);
3399
- }
3400
- if (hasColorSchemeModifiers && componentScope) {
3401
- const colorSchemeConditionals = processColorSchemeModifiers(
3402
- colorSchemeModifiers,
3403
- state,
3404
- parseClassName,
3405
- generateStyleKey,
3406
- t
3407
- );
3408
- styleArrayElements.push(...colorSchemeConditionals);
3409
- }
3410
- const modifiersByType = /* @__PURE__ */ new Map();
3411
- for (const mod of stateModifiers) {
3412
- const modType = mod.modifier;
3413
- if (!modifiersByType.has(modType)) {
3414
- modifiersByType.set(modType, []);
3415
- }
3416
- modifiersByType.get(modType)?.push(mod);
3417
- }
3418
- for (const [modifierType, modifiers] of modifiersByType) {
3419
- if (!componentSupport.supportedModifiers.includes(modifierType)) {
3420
- continue;
3421
- }
3422
- const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
3423
- const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
3424
- const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
3425
- state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
3426
- const stateProperty = getStatePropertyForModifier(modifierType);
3427
- const conditionalExpression = t.logicalExpression(
3428
- "&&",
3429
- t.identifier(stateProperty),
3430
- t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey))
3431
- );
3432
- styleArrayElements.push(conditionalExpression);
3433
- }
3434
- const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter(
3435
- (mod) => componentSupport.supportedModifiers.includes(mod)
3436
- );
3437
- const styleArrayExpression = t.arrayExpression(styleArrayElements);
3438
- const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
3439
- const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3440
- if (styleAttribute2) {
3441
- mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3442
- } else {
3443
- replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3444
- }
3445
- return true;
3446
- } else {
3447
- }
3448
- }
3449
- if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
3450
- const styleExpressions = [];
3451
- if (hasBaseClasses) {
3452
- const baseClassName = baseClasses.join(" ");
3453
- const baseStyleObject = parseClassName(baseClassName, state.customTheme);
3454
- const baseStyleKey = generateStyleKey(baseClassName);
3455
- state.styleRegistry.set(baseStyleKey, baseStyleObject);
3456
- styleExpressions.push(
3457
- t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
3458
- );
3459
- }
3460
- if (hasPlatformModifiers) {
3461
- const platformSelectExpression = processPlatformModifiers(
3462
- platformModifiers,
3463
- state,
3464
- parseClassName,
3465
- generateStyleKey,
3466
- t
3467
- );
3468
- styleExpressions.push(platformSelectExpression);
3469
- }
3470
- if (hasColorSchemeModifiers && componentScope) {
3471
- const colorSchemeConditionals = processColorSchemeModifiers(
3472
- colorSchemeModifiers,
3473
- state,
3474
- parseClassName,
3475
- generateStyleKey,
3476
- t
3477
- );
3478
- styleExpressions.push(...colorSchemeConditionals);
3479
- }
3480
- const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
3481
- const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3482
- if (styleAttribute2) {
3483
- const existingStyle = styleAttribute2.value;
3484
- if (t.isJSXExpressionContainer(existingStyle) && !t.isJSXEmptyExpression(existingStyle.expression)) {
3485
- const existing = existingStyle.expression;
3486
- const mergedArray = t.isArrayExpression(existing) ? t.arrayExpression([styleExpression, ...existing.elements]) : t.arrayExpression([styleExpression, existing]);
3487
- styleAttribute2.value = t.jsxExpressionContainer(mergedArray);
3488
- } else {
3489
- styleAttribute2.value = t.jsxExpressionContainer(styleExpression);
3490
- }
3491
- path2.remove();
3492
- } else {
3493
- path2.node.name = t.jsxIdentifier(targetStyleProp);
3494
- path2.node.value = t.jsxExpressionContainer(styleExpression);
3495
- }
3496
- return true;
3497
- }
3498
- if (hasStateModifiers) {
3499
- const jsxOpeningElement = path2.parent;
3500
- const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
3501
- if (componentSupport) {
3502
- const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
3503
- const unsupportedModifiers = usedModifiers.filter(
3504
- (mod) => !componentSupport.supportedModifiers.includes(mod)
3505
- );
3506
- if (unsupportedModifiers.length > 0) {
3507
- if (process.env.NODE_ENV !== "production") {
3508
- console.warn(
3509
- `[react-native-tailwind] Modifiers (${unsupportedModifiers.map((m) => `${m}:`).join(", ")}) are not supported on ${componentSupport.component} component at ${state.file.opts.filename ?? "unknown"}. Supported modifiers: ${componentSupport.supportedModifiers.join(", ")}`
3510
- );
3511
- }
3512
- const supportedModifierClasses = stateModifiers.filter(
3513
- (m) => componentSupport.supportedModifiers.includes(m.modifier)
3514
- );
3515
- if (supportedModifierClasses.length === 0) {
3516
- } else {
3517
- const filteredClassName = baseClasses.join(" ") + " " + supportedModifierClasses.map((m) => `${m.modifier}:${m.baseClass}`).join(" ");
3518
- const styleExpression = processStaticClassNameWithModifiers(
3519
- filteredClassName.trim(),
3520
- state,
3521
- parseClassName,
3522
- generateStyleKey,
3523
- splitModifierClasses,
3524
- t
3525
- );
3526
- const modifierTypes = Array.from(new Set(supportedModifierClasses.map((m) => m.modifier)));
3527
- const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
3528
- const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3529
- if (styleAttribute2) {
3530
- mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3531
- } else {
3532
- replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3533
- }
3534
- return true;
3535
- }
3536
- } else {
3537
- const styleExpression = processStaticClassNameWithModifiers(
3538
- trimmedClassName,
3539
- state,
3540
- parseClassName,
3541
- generateStyleKey,
3542
- splitModifierClasses,
3543
- t
3544
- );
3545
- const modifierTypes = usedModifiers;
3546
- const styleFunctionExpression = createStyleFunction(styleExpression, modifierTypes, t);
3547
- const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
3548
- if (styleAttribute2) {
3549
- mergeStyleFunctionAttribute(path2, styleAttribute2, styleFunctionExpression, t);
3550
- } else {
3551
- replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3552
- }
3553
- return true;
3554
- }
3555
- } else {
3556
- if (process.env.NODE_ENV !== "production") {
3557
- const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier)));
3558
- console.warn(
3559
- `[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"}`
3560
- );
3561
- }
3562
- }
3563
- }
3564
- const classNameForStyle = baseClasses.join(" ");
3565
- if (!classNameForStyle) {
3566
- path2.remove();
3567
- return true;
3568
- }
3569
- const styleObject = parseClassName(classNameForStyle, state.customTheme);
3570
- const styleKey = generateStyleKey(classNameForStyle);
3571
- state.styleRegistry.set(styleKey, styleObject);
3572
- const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);
3573
- if (styleAttribute) {
3574
- mergeStyleAttribute(path2, styleAttribute, styleKey, state.stylesIdentifier, t);
3575
- } else {
3576
- replaceWithStyleAttribute(path2, styleKey, targetStyleProp, state.stylesIdentifier, t);
3577
- }
3578
- return true;
3579
- };
3580
- if (t.isStringLiteral(value)) {
3581
- if (processStaticClassName(value.value)) {
3582
- return;
3583
- }
3584
- }
3585
- if (t.isJSXExpressionContainer(value)) {
3586
- const expression = value.expression;
3587
- if (t.isJSXEmptyExpression(expression)) {
3588
- return;
3589
- }
3590
- if (t.isStringLiteral(expression)) {
3591
- if (processStaticClassName(expression.value)) {
3592
- return;
3593
- }
3594
- }
3595
- try {
3596
- const componentScope = findComponentScope(path2, t);
3597
- const result = processDynamicExpression(
3598
- expression,
3599
- state,
3600
- parseClassName,
3601
- generateStyleKey,
3602
- splitModifierClasses,
3603
- processPlatformModifiers,
3604
- processColorSchemeModifiers,
3605
- componentScope,
3606
- isPlatformModifier,
3607
- isColorSchemeModifier,
3608
- isSchemeModifier,
3609
- expandSchemeModifier,
3610
- t
3611
- );
3612
- if (result) {
3613
- state.hasClassNames = true;
3614
- const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);
3615
- if (styleAttribute) {
3616
- mergeDynamicStyleAttribute(path2, styleAttribute, result, t);
3617
- } else {
3618
- replaceDynamicWithStyleAttribute(path2, result, targetStyleProp, t);
3619
- }
3620
- return;
3621
- }
3622
- } catch (error) {
3623
- if (process.env.NODE_ENV !== "production") {
3624
- console.warn(
3625
- `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`
3626
- );
3627
- }
3628
- }
3629
- }
3630
- if (process.env.NODE_ENV !== "production") {
3631
- const filename = state.file.opts.filename ?? "unknown";
3632
- console.warn(
3633
- `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. Use the ${targetStyleProp} prop for dynamic values.`
3634
- );
3635
- }
3942
+ jsxAttributeVisitor(path2, state, t);
3636
3943
  }
3637
3944
  }
3638
3945
  };