@mgcrea/react-native-tailwind 0.12.0 → 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 (97) hide show
  1. package/README.md +29 -2014
  2. package/dist/babel/config-loader.d.ts +3 -0
  3. package/dist/babel/config-loader.test.ts +2 -2
  4. package/dist/babel/config-loader.ts +37 -2
  5. package/dist/babel/index.cjs +2855 -2434
  6. package/dist/babel/plugin/componentScope.d.ts +26 -0
  7. package/dist/babel/plugin/componentScope.ts +87 -0
  8. package/dist/babel/plugin/state.d.ts +119 -0
  9. package/dist/babel/plugin/state.ts +177 -0
  10. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  11. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +74 -674
  12. package/dist/babel/plugin/visitors/className.ts +624 -0
  13. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  14. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  15. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  16. package/dist/babel/plugin/visitors/imports.ts +101 -0
  17. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  18. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  19. package/dist/babel/plugin/visitors/program.ts +99 -0
  20. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  21. package/dist/babel/plugin/visitors/tw.test.ts +620 -0
  22. package/dist/babel/plugin/visitors/tw.ts +148 -0
  23. package/dist/babel/plugin.d.ts +3 -96
  24. package/dist/babel/plugin.test.ts +470 -0
  25. package/dist/babel/plugin.ts +28 -953
  26. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  27. package/dist/babel/utils/componentSupport.test.ts +20 -7
  28. package/dist/babel/utils/componentSupport.ts +2 -0
  29. package/dist/babel/utils/modifierProcessing.ts +21 -0
  30. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  31. package/dist/babel/utils/styleInjection.d.ts +15 -0
  32. package/dist/babel/utils/styleInjection.ts +172 -17
  33. package/dist/babel/utils/twProcessing.ts +11 -0
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +1 -0
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/sizing.js +1 -1
  54. package/dist/parser/typography.d.ts +2 -1
  55. package/dist/parser/typography.js +1 -1
  56. package/dist/parser/typography.test.js +1 -1
  57. package/dist/runtime.cjs +1 -1
  58. package/dist/runtime.cjs.map +4 -4
  59. package/dist/runtime.js +1 -1
  60. package/dist/runtime.js.map +4 -4
  61. package/package.json +1 -1
  62. package/src/babel/config-loader.test.ts +2 -2
  63. package/src/babel/config-loader.ts +37 -2
  64. package/src/babel/plugin/componentScope.ts +87 -0
  65. package/src/babel/plugin/state.ts +177 -0
  66. package/src/babel/plugin/visitors/className.test.ts +1312 -0
  67. package/src/babel/plugin/visitors/className.ts +624 -0
  68. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  69. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  70. package/src/babel/plugin/visitors/imports.ts +101 -0
  71. package/src/babel/plugin/visitors/program.test.ts +325 -0
  72. package/src/babel/plugin/visitors/program.ts +99 -0
  73. package/src/babel/plugin/visitors/tw.test.ts +620 -0
  74. package/src/babel/plugin/visitors/tw.ts +148 -0
  75. package/src/babel/plugin.ts +28 -953
  76. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  77. package/src/babel/utils/componentSupport.test.ts +20 -7
  78. package/src/babel/utils/componentSupport.ts +2 -0
  79. package/src/babel/utils/modifierProcessing.ts +21 -0
  80. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  81. package/src/babel/utils/styleInjection.ts +172 -17
  82. package/src/babel/utils/twProcessing.ts +11 -0
  83. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  84. package/src/components/TouchableOpacity.tsx +71 -0
  85. package/src/components/index.ts +3 -0
  86. package/src/config/markers.ts +5 -0
  87. package/src/index.ts +4 -5
  88. package/src/parser/borders.test.ts +58 -0
  89. package/src/parser/borders.ts +18 -3
  90. package/src/parser/colors.test.ts +249 -0
  91. package/src/parser/colors.ts +38 -0
  92. package/src/parser/index.ts +4 -3
  93. package/src/parser/layout.test.ts +61 -0
  94. package/src/parser/layout.ts +55 -1
  95. package/src/parser/sizing.ts +11 -0
  96. package/src/parser/typography.test.ts +102 -0
  97. package/src/parser/typography.ts +61 -15
@@ -5,6 +5,7 @@
5
5
  import type * as BabelTypes from "@babel/types";
6
6
  import type { ColorSchemeModifierType, CustomTheme, ParsedModifier } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
8
9
 
9
10
  /**
10
11
  * Plugin state interface (subset needed for color scheme modifier processing)
@@ -66,6 +67,16 @@ export function processColorSchemeModifiers(
66
67
  // Parse all classes for this color scheme together
67
68
  const classNames = modifiers.map((m) => m.baseClass).join(" ");
68
69
  const styleObject = parseClassName(classNames, state.customTheme);
70
+
71
+ // Check for runtime dimensions (w-screen, h-screen)
72
+ if (hasRuntimeDimensions(styleObject)) {
73
+ throw new Error(
74
+ `w-screen and h-screen cannot be combined with color scheme modifiers (dark:, light:, scheme:). ` +
75
+ `Found in: "${scheme}:${classNames}". ` +
76
+ `Use w-screen/h-screen without modifiers instead.`,
77
+ );
78
+ }
79
+
69
80
  const styleKey = generateStyleKey(`${scheme}_${classNames}`);
70
81
 
71
82
  // Register style in the registry
@@ -86,6 +86,26 @@ describe("getComponentModifierSupport", () => {
86
86
  supportedModifiers: ["focus", "disabled", "placeholder"],
87
87
  });
88
88
  });
89
+
90
+ it("should recognize TouchableOpacity component", () => {
91
+ const element = createJSXElement("<TouchableOpacity />");
92
+ const result = getComponentModifierSupport(element, t);
93
+
94
+ expect(result).toEqual({
95
+ component: "TouchableOpacity",
96
+ supportedModifiers: ["active", "disabled"],
97
+ });
98
+ });
99
+
100
+ it("should recognize TouchableOpacity with attributes", () => {
101
+ const element = createJSXElement('<TouchableOpacity className="m-4" onPress={handlePress} />');
102
+ const result = getComponentModifierSupport(element, t);
103
+
104
+ expect(result).toEqual({
105
+ component: "TouchableOpacity",
106
+ supportedModifiers: ["active", "disabled"],
107
+ });
108
+ });
89
109
  });
90
110
 
91
111
  describe("Member expressions", () => {
@@ -139,13 +159,6 @@ describe("getComponentModifierSupport", () => {
139
159
  expect(result).toBeNull();
140
160
  });
141
161
 
142
- it("should return null for TouchableOpacity", () => {
143
- const element = createJSXElement("<TouchableOpacity />");
144
- const result = getComponentModifierSupport(element, t);
145
-
146
- expect(result).toBeNull();
147
- });
148
-
149
162
  it("should return null for custom components", () => {
150
163
  const element = createJSXElement("<CustomButton />");
151
164
  const result = getComponentModifierSupport(element, t);
@@ -41,6 +41,8 @@ export function getComponentModifierSupport(
41
41
  switch (componentName) {
42
42
  case "Pressable":
43
43
  return { component: "Pressable", supportedModifiers: ["active", "hover", "focus", "disabled"] };
44
+ case "TouchableOpacity":
45
+ return { component: "TouchableOpacity", supportedModifiers: ["active", "disabled"] };
44
46
  case "TextInput":
45
47
  return { component: "TextInput", supportedModifiers: ["focus", "disabled", "placeholder"] };
46
48
  default:
@@ -6,6 +6,7 @@ import type * as BabelTypes from "@babel/types";
6
6
  import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
8
  import { getStatePropertyForModifier } from "./componentSupport.js";
9
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
9
10
 
10
11
  /**
11
12
  * Plugin state interface (subset needed for modifier processing)
@@ -36,6 +37,16 @@ export function processStaticClassNameWithModifiers(
36
37
  if (baseClasses.length > 0) {
37
38
  const baseClassName = baseClasses.join(" ");
38
39
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
40
+
41
+ // Check for runtime dimensions (w-screen, h-screen) in base classes
42
+ if (hasRuntimeDimensions(baseStyleObject)) {
43
+ throw new Error(
44
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). ` +
45
+ `Found in: "${baseClassName}". ` +
46
+ `Use w-screen/h-screen without modifiers instead.`,
47
+ );
48
+ }
49
+
39
50
  const baseStyleKey = generateStyleKey(baseClassName);
40
51
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
41
52
  baseStyleExpression = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey));
@@ -67,6 +78,16 @@ export function processStaticClassNameWithModifiers(
67
78
  // Parse all modifier classes together
68
79
  const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
69
80
  const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
81
+
82
+ // Check for runtime dimensions (w-screen, h-screen) in modifier classes
83
+ if (hasRuntimeDimensions(modifierStyleObject)) {
84
+ throw new Error(
85
+ `w-screen and h-screen cannot be combined with state modifiers (active:, hover:, focus:, etc.) or platform modifiers (ios:, android:, web:). ` +
86
+ `Found in: "${modifierType}:${modifierClassNames}". ` +
87
+ `Use w-screen/h-screen without modifiers instead.`,
88
+ );
89
+ }
90
+
70
91
  const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
71
92
  state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
72
93
 
@@ -5,6 +5,7 @@
5
5
  import type * as BabelTypes from "@babel/types";
6
6
  import type { CustomTheme, ParsedModifier, PlatformModifierType } from "../../parser/index.js";
7
7
  import type { StyleObject } from "../../types/core.js";
8
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
8
9
 
9
10
  /**
10
11
  * Plugin state interface (subset needed for platform modifier processing)
@@ -62,6 +63,16 @@ export function processPlatformModifiers(
62
63
  // Parse all classes for this platform together
63
64
  const classNames = modifiers.map((m) => m.baseClass).join(" ");
64
65
  const styleObject = parseClassName(classNames, state.customTheme);
66
+
67
+ // Check for runtime dimensions (w-screen, h-screen)
68
+ if (hasRuntimeDimensions(styleObject)) {
69
+ throw new Error(
70
+ `w-screen and h-screen cannot be combined with platform modifiers (ios:, android:, web:). ` +
71
+ `Found in: "${platform}:${classNames}". ` +
72
+ `Use w-screen/h-screen without modifiers instead.`,
73
+ );
74
+ }
75
+
65
76
  const styleKey = generateStyleKey(`${platform}_${classNames}`);
66
77
 
67
78
  // Register style in the registry
@@ -27,6 +27,21 @@ export declare function addColorSchemeImport(path: NodePath<BabelTypes.Program>,
27
27
  * @returns true if hook was injected, false if already exists
28
28
  */
29
29
  export declare function injectColorSchemeHook(functionPath: NodePath<BabelTypes.Function>, colorSchemeVariableName: string, hookName: string, localIdentifier: string | undefined, t: typeof BabelTypes): boolean;
30
+ /**
31
+ * Add useWindowDimensions import to the file or merge with existing react-native import
32
+ */
33
+ export declare function addWindowDimensionsImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void;
34
+ /**
35
+ * Inject useWindowDimensions hook call at the top of a function component
36
+ *
37
+ * @param functionPath - Path to the function component
38
+ * @param dimensionsVariableName - Name for the dimensions variable
39
+ * @param hookName - Name of the hook to call (e.g., 'useWindowDimensions')
40
+ * @param localIdentifier - Local identifier if hook is already imported with an alias
41
+ * @param t - Babel types
42
+ * @returns true if hook was injected, false if already exists
43
+ */
44
+ export declare function injectWindowDimensionsHook(functionPath: NodePath<BabelTypes.Function>, dimensionsVariableName: string, hookName: string, localIdentifier: string | undefined, t: typeof BabelTypes): boolean;
30
45
  /**
31
46
  * Inject StyleSheet.create with all collected styles at the top of the file
32
47
  * This ensures the styles object is defined before any code that references it
@@ -10,24 +10,44 @@ import type { StyleObject } from "../../types/core.js";
10
10
  * Add StyleSheet import to the file or merge with existing react-native import
11
11
  */
12
12
  export function addStyleSheetImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
13
- // Check if there's already a react-native import
13
+ // Check if there's already a value import from react-native
14
14
  const body = path.node.body;
15
- let reactNativeImport: BabelTypes.ImportDeclaration | null = null;
15
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
16
16
 
17
17
  for (const statement of body) {
18
18
  if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
19
- reactNativeImport = statement;
20
- break;
19
+ // Skip type-only imports (they get erased at runtime)
20
+ if (statement.importKind === "type") {
21
+ continue;
22
+ }
23
+ // Skip namespace imports (import * as RN) - can't add named specifiers to them
24
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
25
+ if (hasNamespaceImport) {
26
+ continue;
27
+ }
28
+ existingValueImport = statement;
29
+ break; // Found a value import, we can stop
21
30
  }
22
31
  }
23
32
 
24
- if (reactNativeImport) {
25
- // Add StyleSheet to existing react-native import
26
- reactNativeImport.specifiers.push(
27
- t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")),
33
+ if (existingValueImport) {
34
+ // Check if StyleSheet is already imported
35
+ const hasStyleSheet = existingValueImport.specifiers.some(
36
+ (spec) =>
37
+ t.isImportSpecifier(spec) &&
38
+ spec.imported.type === "Identifier" &&
39
+ spec.imported.name === "StyleSheet",
28
40
  );
41
+
42
+ if (!hasStyleSheet) {
43
+ // Add StyleSheet to existing value import
44
+ existingValueImport.specifiers.push(
45
+ t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")),
46
+ );
47
+ }
29
48
  } else {
30
- // Create new react-native import with StyleSheet
49
+ // No value import exists - create a new one
50
+ // (Don't merge with type-only or namespace imports)
31
51
  const importDeclaration = t.importDeclaration(
32
52
  [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
33
53
  t.stringLiteral("react-native"),
@@ -40,22 +60,42 @@ export function addStyleSheetImport(path: NodePath<BabelTypes.Program>, t: typeo
40
60
  * Add Platform import to the file or merge with existing react-native import
41
61
  */
42
62
  export function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
43
- // Check if there's already a react-native import
63
+ // Check if there's already a value import from react-native
44
64
  const body = path.node.body;
45
- let reactNativeImport: BabelTypes.ImportDeclaration | null = null;
65
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
46
66
 
47
67
  for (const statement of body) {
48
68
  if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
49
- reactNativeImport = statement;
50
- break;
69
+ // Skip type-only imports (they get erased at runtime)
70
+ if (statement.importKind === "type") {
71
+ continue;
72
+ }
73
+ // Skip namespace imports (import * as RN) - can't add named specifiers to them
74
+ const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
75
+ if (hasNamespaceImport) {
76
+ continue;
77
+ }
78
+ existingValueImport = statement;
79
+ break; // Found a value import, we can stop
51
80
  }
52
81
  }
53
82
 
54
- if (reactNativeImport) {
55
- // Add Platform to existing react-native import
56
- reactNativeImport.specifiers.push(t.importSpecifier(t.identifier("Platform"), t.identifier("Platform")));
83
+ if (existingValueImport) {
84
+ // Check if Platform is already imported
85
+ const hasPlatform = existingValueImport.specifiers.some(
86
+ (spec) =>
87
+ t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "Platform",
88
+ );
89
+
90
+ if (!hasPlatform) {
91
+ // Add Platform to existing value import
92
+ existingValueImport.specifiers.push(
93
+ t.importSpecifier(t.identifier("Platform"), t.identifier("Platform")),
94
+ );
95
+ }
57
96
  } else {
58
- // Create new react-native import with Platform
97
+ // No value import exists - create a new one
98
+ // (Don't merge with type-only or namespace imports)
59
99
  const importDeclaration = t.importDeclaration(
60
100
  [t.importSpecifier(t.identifier("Platform"), t.identifier("Platform"))],
61
101
  t.stringLiteral("react-native"),
@@ -180,6 +220,121 @@ export function injectColorSchemeHook(
180
220
  return true;
181
221
  }
182
222
 
223
+ /**
224
+ * Add useWindowDimensions import to the file or merge with existing react-native import
225
+ */
226
+ export function addWindowDimensionsImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
227
+ // Check if there's already an import from react-native
228
+ const body = path.node.body;
229
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
230
+
231
+ for (const statement of body) {
232
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
233
+ // Only consider value imports (not type-only imports which get erased)
234
+ if (statement.importKind !== "type") {
235
+ existingValueImport = statement;
236
+ break; // Found a value import, we can stop
237
+ }
238
+ }
239
+ }
240
+
241
+ // If we found a value import (not type-only), merge with it
242
+ if (existingValueImport) {
243
+ // Check if the hook is already imported
244
+ const hasHook = existingValueImport.specifiers.some(
245
+ (spec) =>
246
+ t.isImportSpecifier(spec) &&
247
+ spec.imported.type === "Identifier" &&
248
+ spec.imported.name === "useWindowDimensions",
249
+ );
250
+
251
+ if (!hasHook) {
252
+ // Add hook to existing value import
253
+ existingValueImport.specifiers.push(
254
+ t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions")),
255
+ );
256
+ }
257
+ } else {
258
+ // No value import exists - create a new one
259
+ // (Don't merge with type-only imports as they get erased by Babel/TypeScript)
260
+ const importDeclaration = t.importDeclaration(
261
+ [t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions"))],
262
+ t.stringLiteral("react-native"),
263
+ );
264
+ path.unshiftContainer("body", importDeclaration);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Inject useWindowDimensions hook call at the top of a function component
270
+ *
271
+ * @param functionPath - Path to the function component
272
+ * @param dimensionsVariableName - Name for the dimensions variable
273
+ * @param hookName - Name of the hook to call (e.g., 'useWindowDimensions')
274
+ * @param localIdentifier - Local identifier if hook is already imported with an alias
275
+ * @param t - Babel types
276
+ * @returns true if hook was injected, false if already exists
277
+ */
278
+ export function injectWindowDimensionsHook(
279
+ functionPath: NodePath<BabelTypes.Function>,
280
+ dimensionsVariableName: string,
281
+ hookName: string,
282
+ localIdentifier: string | undefined,
283
+ t: typeof BabelTypes,
284
+ ): boolean {
285
+ let body = functionPath.node.body;
286
+
287
+ // Handle concise arrow functions: () => <JSX />
288
+ // Convert to block statement: () => { const _twDimensions = useWindowDimensions(); return <JSX />; }
289
+ if (!t.isBlockStatement(body)) {
290
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
291
+ // Convert concise body to block statement with return
292
+ const returnStatement = t.returnStatement(body);
293
+ const blockStatement = t.blockStatement([returnStatement]);
294
+ functionPath.node.body = blockStatement;
295
+ body = blockStatement;
296
+ } else {
297
+ // Other non-block functions (shouldn't happen for components, but be safe)
298
+ return false;
299
+ }
300
+ }
301
+
302
+ // Check if hook is already injected
303
+ const hasHook = body.body.some((statement) => {
304
+ if (
305
+ t.isVariableDeclaration(statement) &&
306
+ statement.declarations.length > 0 &&
307
+ t.isVariableDeclarator(statement.declarations[0])
308
+ ) {
309
+ const declarator = statement.declarations[0];
310
+ return t.isIdentifier(declarator.id) && declarator.id.name === dimensionsVariableName;
311
+ }
312
+ return false;
313
+ });
314
+
315
+ if (hasHook) {
316
+ return false; // Already injected
317
+ }
318
+
319
+ // Use the local identifier if hook was already imported with an alias,
320
+ // otherwise use the configured hook name
321
+ // e.g., import { useWindowDimensions as useDims } → call useDims()
322
+ const identifierToCall = localIdentifier ?? hookName;
323
+
324
+ // Create: const _twDimensions = useWindowDimensions(); (or aliased name if already imported)
325
+ const hookCall = t.variableDeclaration("const", [
326
+ t.variableDeclarator(
327
+ t.identifier(dimensionsVariableName),
328
+ t.callExpression(t.identifier(identifierToCall), []),
329
+ ),
330
+ ]);
331
+
332
+ // Insert at the beginning of function body
333
+ body.body.unshift(hookCall);
334
+
335
+ return true;
336
+ }
337
+
183
338
  /**
184
339
  * Inject StyleSheet.create with all collected styles at the top of the file
185
340
  * This ensures the styles object is defined before any code that references it
@@ -15,6 +15,7 @@ import type { SchemeModifierConfig } from "../../types/config.js";
15
15
  import type { StyleObject } from "../../types/core.js";
16
16
  import { processColorSchemeModifiers } from "./colorSchemeModifierProcessing.js";
17
17
  import { processPlatformModifiers } from "./platformModifierProcessing.js";
18
+ import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
18
19
 
19
20
  /**
20
21
  * Plugin state interface (subset needed for tw processing)
@@ -77,6 +78,16 @@ export function processTwCall(
77
78
  if (baseClasses.length > 0) {
78
79
  const baseClassName = baseClasses.join(" ");
79
80
  const baseStyleObject = parseClassName(baseClassName, state.customTheme);
81
+
82
+ // Check for runtime dimensions (w-screen, h-screen)
83
+ if (hasRuntimeDimensions(baseStyleObject)) {
84
+ throw path.buildCodeFrameError(
85
+ `w-screen and h-screen are not supported in tw\`\` or twStyle() calls. ` +
86
+ `Found: "${baseClassName}". ` +
87
+ `Use them in className attributes instead.`,
88
+ );
89
+ }
90
+
80
91
  const baseStyleKey = generateStyleKey(baseClassName);
81
92
  state.styleRegistry.set(baseStyleKey, baseStyleObject);
82
93
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Utility functions for processing window dimensions (w-screen, h-screen)
3
+ */
4
+ import type * as BabelTypes from "@babel/types";
5
+ import type { StyleObject } from "../../types/core.js";
6
+ /**
7
+ * Plugin state interface (subset needed for window dimensions processing)
8
+ */
9
+ export interface WindowDimensionsProcessingState {
10
+ needsWindowDimensionsImport: boolean;
11
+ windowDimensionsVariableName: string;
12
+ }
13
+ /**
14
+ * Check if a style object contains runtime dimension markers
15
+ *
16
+ * @param styleObject - Style object to check
17
+ * @returns true if the style object contains runtime dimension markers
18
+ *
19
+ * @example
20
+ * hasRuntimeDimensions({ width: "{{RUNTIME:dimensions.width}}" }) // true
21
+ * hasRuntimeDimensions({ width: 100 }) // false
22
+ */
23
+ export declare function hasRuntimeDimensions(styleObject: StyleObject): boolean;
24
+ /**
25
+ * Create an inline style object with runtime dimension access
26
+ *
27
+ * Converts runtime markers like "{{RUNTIME:dimensions.width}}" to
28
+ * AST nodes like: { width: _twDimensions.width }
29
+ *
30
+ * @param styleObject - Style object with runtime markers
31
+ * @param state - Plugin state
32
+ * @param t - Babel types
33
+ * @returns AST object expression for inline style
34
+ *
35
+ * @example
36
+ * Input: { width: "{{RUNTIME:dimensions.width}}", height: "{{RUNTIME:dimensions.height}}" }
37
+ * Output: { width: _twDimensions.width, height: _twDimensions.height }
38
+ */
39
+ export declare function createRuntimeDimensionObject(styleObject: StyleObject, state: WindowDimensionsProcessingState, t: typeof BabelTypes): BabelTypes.ObjectExpression;
40
+ /**
41
+ * Split a style object into static and runtime parts
42
+ *
43
+ * @param styleObject - Style object to split
44
+ * @returns Object with static and runtime style objects
45
+ *
46
+ * @example
47
+ * Input: { width: "{{RUNTIME:dimensions.width}}", padding: 16, backgroundColor: "#fff" }
48
+ * Output: {
49
+ * static: { padding: 16, backgroundColor: "#fff" },
50
+ * runtime: { width: "{{RUNTIME:dimensions.width}}" }
51
+ * }
52
+ */
53
+ export declare function splitStaticAndRuntimeStyles(styleObject: StyleObject): {
54
+ static: StyleObject;
55
+ runtime: StyleObject;
56
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Utility functions for processing window dimensions (w-screen, h-screen)
3
+ */
4
+
5
+ import type * as BabelTypes from "@babel/types";
6
+ import { RUNTIME_DIMENSIONS_MARKER } from "../../config/markers.js";
7
+ import type { StyleObject } from "../../types/core.js";
8
+
9
+ /**
10
+ * Plugin state interface (subset needed for window dimensions processing)
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
13
+ export interface WindowDimensionsProcessingState {
14
+ needsWindowDimensionsImport: boolean;
15
+ windowDimensionsVariableName: string;
16
+ }
17
+
18
+ /**
19
+ * Check if a style object contains runtime dimension markers
20
+ *
21
+ * @param styleObject - Style object to check
22
+ * @returns true if the style object contains runtime dimension markers
23
+ *
24
+ * @example
25
+ * hasRuntimeDimensions({ width: "{{RUNTIME:dimensions.width}}" }) // true
26
+ * hasRuntimeDimensions({ width: 100 }) // false
27
+ */
28
+ export function hasRuntimeDimensions(styleObject: StyleObject): boolean {
29
+ return Object.values(styleObject).some(
30
+ (value) => typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER),
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Create an inline style object with runtime dimension access
36
+ *
37
+ * Converts runtime markers like "{{RUNTIME:dimensions.width}}" to
38
+ * AST nodes like: { width: _twDimensions.width }
39
+ *
40
+ * @param styleObject - Style object with runtime markers
41
+ * @param state - Plugin state
42
+ * @param t - Babel types
43
+ * @returns AST object expression for inline style
44
+ *
45
+ * @example
46
+ * Input: { width: "{{RUNTIME:dimensions.width}}", height: "{{RUNTIME:dimensions.height}}" }
47
+ * Output: { width: _twDimensions.width, height: _twDimensions.height }
48
+ */
49
+ export function createRuntimeDimensionObject(
50
+ styleObject: StyleObject,
51
+ state: WindowDimensionsProcessingState,
52
+ t: typeof BabelTypes,
53
+ ): BabelTypes.ObjectExpression {
54
+ // Mark that we need useWindowDimensions import and hook injection
55
+ state.needsWindowDimensionsImport = true;
56
+
57
+ const properties: BabelTypes.ObjectProperty[] = [];
58
+
59
+ for (const [key, value] of Object.entries(styleObject)) {
60
+ let valueNode: BabelTypes.Expression;
61
+
62
+ if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
63
+ // Extract property name: "{{RUNTIME:dimensions.width}}" -> "width"
64
+ const match = value.match(/dimensions\.(\w+)/);
65
+ const prop = match?.[1];
66
+
67
+ if (prop) {
68
+ // Generate: _twDimensions.width or _twDimensions.height
69
+ valueNode = t.memberExpression(t.identifier(state.windowDimensionsVariableName), t.identifier(prop));
70
+ } else {
71
+ // Fallback: shouldn't happen, but handle gracefully
72
+ valueNode = t.stringLiteral(value);
73
+ }
74
+ } else if (typeof value === "number") {
75
+ valueNode = t.numericLiteral(value);
76
+ } else if (typeof value === "string") {
77
+ valueNode = t.stringLiteral(value);
78
+ } else if (typeof value === "object" && value !== null) {
79
+ // Handle nested objects (e.g., transform arrays)
80
+ valueNode = t.valueToNode(value);
81
+ } else {
82
+ // Handle other types
83
+ valueNode = t.valueToNode(value);
84
+ }
85
+
86
+ properties.push(t.objectProperty(t.identifier(key), valueNode));
87
+ }
88
+
89
+ return t.objectExpression(properties);
90
+ }
91
+
92
+ /**
93
+ * Split a style object into static and runtime parts
94
+ *
95
+ * @param styleObject - Style object to split
96
+ * @returns Object with static and runtime style objects
97
+ *
98
+ * @example
99
+ * Input: { width: "{{RUNTIME:dimensions.width}}", padding: 16, backgroundColor: "#fff" }
100
+ * Output: {
101
+ * static: { padding: 16, backgroundColor: "#fff" },
102
+ * runtime: { width: "{{RUNTIME:dimensions.width}}" }
103
+ * }
104
+ */
105
+ export function splitStaticAndRuntimeStyles(styleObject: StyleObject): {
106
+ static: StyleObject;
107
+ runtime: StyleObject;
108
+ } {
109
+ const staticStyles: StyleObject = {};
110
+ const runtimeStyles: StyleObject = {};
111
+
112
+ for (const [key, value] of Object.entries(styleObject)) {
113
+ if (typeof value === "string" && value.startsWith(RUNTIME_DIMENSIONS_MARKER)) {
114
+ runtimeStyles[key] = value;
115
+ } else {
116
+ staticStyles[key] = value;
117
+ }
118
+ }
119
+
120
+ return { static: staticStyles, runtime: runtimeStyles };
121
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Enhanced TouchableOpacity component with modifier support
3
+ * Adds active state support for active: modifier via onPressIn/onPressOut
4
+ */
5
+ import { type TouchableOpacityProps as RNTouchableOpacityProps, type StyleProp, type ViewStyle } from "react-native";
6
+ type TouchableOpacityState = {
7
+ active: boolean;
8
+ disabled: boolean | null | undefined;
9
+ };
10
+ export type TouchableOpacityProps = Omit<RNTouchableOpacityProps, "style"> & {
11
+ /**
12
+ * Style can be a static style object/array or a function that receives TouchableOpacity state
13
+ */
14
+ style?: StyleProp<ViewStyle> | ((state: TouchableOpacityState) => StyleProp<ViewStyle>);
15
+ className?: string;
16
+ };
17
+ /**
18
+ * Enhanced TouchableOpacity that supports active: and disabled: modifiers
19
+ *
20
+ * @example
21
+ * <TouchableOpacity
22
+ * disabled={isLoading}
23
+ * className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400"
24
+ * >
25
+ * <Text>Submit</Text>
26
+ * </TouchableOpacity>
27
+ */
28
+ export declare const TouchableOpacity: import("react").ForwardRefExoticComponent<Omit<RNTouchableOpacityProps, "style"> & {
29
+ /**
30
+ * Style can be a static style object/array or a function that receives TouchableOpacity state
31
+ */
32
+ style?: StyleProp<ViewStyle> | ((state: TouchableOpacityState) => StyleProp<ViewStyle>);
33
+ className?: string;
34
+ } & import("react").RefAttributes<import("react-native").View>>;
35
+ export {};
@@ -0,0 +1 @@
1
+ var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.TouchableOpacity=void 0;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));var _objectWithoutProperties2=_interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));var _react=require("react");var _reactNative=require("react-native");var _jsxRuntime=require("react/jsx-runtime");var _jsxFileName="/Users/olivier/Projects/github/react-native-tailwind/src/components/TouchableOpacity.tsx";var _excluded=["style","disabled","onPressIn","onPressOut"];var TouchableOpacity=exports.TouchableOpacity=(0,_react.forwardRef)(function TouchableOpacity(_ref,ref){var style=_ref.style,_ref$disabled=_ref.disabled,disabled=_ref$disabled===void 0?false:_ref$disabled,onPressIn=_ref.onPressIn,onPressOut=_ref.onPressOut,props=(0,_objectWithoutProperties2.default)(_ref,_excluded);var _useState=(0,_react.useState)(false),_useState2=(0,_slicedToArray2.default)(_useState,2),isActive=_useState2[0],setIsActive=_useState2[1];var handlePressIn=(0,_react.useCallback)(function(event){setIsActive(true);onPressIn==null||onPressIn(event);},[onPressIn]);var handlePressOut=(0,_react.useCallback)(function(event){setIsActive(false);onPressOut==null||onPressOut(event);},[onPressOut]);var resolvedStyle=typeof style==="function"?style({active:isActive,disabled:disabled}):style;return(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,Object.assign({ref:ref,disabled:disabled,style:resolvedStyle,onPressIn:handlePressIn,onPressOut:handlePressOut},props));});
@@ -0,0 +1,3 @@
1
+ export * from "./Pressable";
2
+ export * from "./TextInput";
3
+ export * from "./TouchableOpacity";
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,"__esModule",{value:true});var _Pressable=require("./Pressable");Object.keys(_Pressable).forEach(function(key){if(key==="default"||key==="__esModule")return;if(key in exports&&exports[key]===_Pressable[key])return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _Pressable[key];}});});var _TextInput=require("./TextInput");Object.keys(_TextInput).forEach(function(key){if(key==="default"||key==="__esModule")return;if(key in exports&&exports[key]===_TextInput[key])return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _TextInput[key];}});});var _TouchableOpacity=require("./TouchableOpacity");Object.keys(_TouchableOpacity).forEach(function(key){if(key==="default"||key==="__esModule")return;if(key in exports&&exports[key]===_TouchableOpacity[key])return;Object.defineProperty(exports,key,{enumerable:true,get:function get(){return _TouchableOpacity[key];}});});
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Runtime marker prefix for window dimension values
3
+ * Used to mark style values that need runtime evaluation via useWindowDimensions()
4
+ */
5
+ export declare const RUNTIME_DIMENSIONS_MARKER = "{{RUNTIME:dimensions.";
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,"__esModule",{value:true});exports.RUNTIME_DIMENSIONS_MARKER=void 0;var RUNTIME_DIMENSIONS_MARKER=exports.RUNTIME_DIMENSIONS_MARKER="{{RUNTIME:dimensions.";