@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.
- package/README.md +29 -2014
- package/dist/babel/config-loader.d.ts +3 -0
- package/dist/babel/config-loader.test.ts +2 -2
- package/dist/babel/config-loader.ts +37 -2
- package/dist/babel/index.cjs +2855 -2434
- package/dist/babel/plugin/componentScope.d.ts +26 -0
- package/dist/babel/plugin/componentScope.ts +87 -0
- package/dist/babel/plugin/state.d.ts +119 -0
- package/dist/babel/plugin/state.ts +177 -0
- package/dist/babel/plugin/visitors/className.d.ts +11 -0
- package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +74 -674
- package/dist/babel/plugin/visitors/className.ts +624 -0
- package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/dist/babel/plugin/visitors/imports.d.ts +11 -0
- package/dist/babel/plugin/visitors/imports.test.ts +88 -0
- package/dist/babel/plugin/visitors/imports.ts +101 -0
- package/dist/babel/plugin/visitors/program.d.ts +15 -0
- package/dist/babel/plugin/visitors/program.test.ts +325 -0
- package/dist/babel/plugin/visitors/program.ts +99 -0
- package/dist/babel/plugin/visitors/tw.d.ts +16 -0
- package/dist/babel/plugin/visitors/tw.test.ts +620 -0
- package/dist/babel/plugin/visitors/tw.ts +148 -0
- package/dist/babel/plugin.d.ts +3 -96
- package/dist/babel/plugin.test.ts +470 -0
- package/dist/babel/plugin.ts +28 -953
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/dist/babel/utils/componentSupport.test.ts +20 -7
- package/dist/babel/utils/componentSupport.ts +2 -0
- package/dist/babel/utils/modifierProcessing.ts +21 -0
- package/dist/babel/utils/platformModifierProcessing.ts +11 -0
- package/dist/babel/utils/styleInjection.d.ts +15 -0
- package/dist/babel/utils/styleInjection.ts +172 -17
- package/dist/babel/utils/twProcessing.ts +11 -0
- package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
- package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/dist/components/TouchableOpacity.d.ts +35 -0
- package/dist/components/TouchableOpacity.js +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +1 -0
- package/dist/config/markers.d.ts +5 -0
- package/dist/config/markers.js +1 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +1 -1
- package/dist/parser/borders.d.ts +3 -1
- package/dist/parser/borders.js +1 -1
- package/dist/parser/borders.test.js +1 -1
- package/dist/parser/colors.js +1 -1
- package/dist/parser/colors.test.js +1 -1
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.js +1 -1
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.js +1 -1
- package/dist/parser/sizing.js +1 -1
- package/dist/parser/typography.d.ts +2 -1
- package/dist/parser/typography.js +1 -1
- package/dist/parser/typography.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +4 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +4 -4
- package/package.json +1 -1
- package/src/babel/config-loader.test.ts +2 -2
- package/src/babel/config-loader.ts +37 -2
- package/src/babel/plugin/componentScope.ts +87 -0
- package/src/babel/plugin/state.ts +177 -0
- package/src/babel/plugin/visitors/className.test.ts +1312 -0
- package/src/babel/plugin/visitors/className.ts +624 -0
- package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
- package/src/babel/plugin/visitors/imports.test.ts +88 -0
- package/src/babel/plugin/visitors/imports.ts +101 -0
- package/src/babel/plugin/visitors/program.test.ts +325 -0
- package/src/babel/plugin/visitors/program.ts +99 -0
- package/src/babel/plugin/visitors/tw.test.ts +620 -0
- package/src/babel/plugin/visitors/tw.ts +148 -0
- package/src/babel/plugin.ts +28 -953
- package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
- package/src/babel/utils/componentSupport.test.ts +20 -7
- package/src/babel/utils/componentSupport.ts +2 -0
- package/src/babel/utils/modifierProcessing.ts +21 -0
- package/src/babel/utils/platformModifierProcessing.ts +11 -0
- package/src/babel/utils/styleInjection.ts +172 -17
- package/src/babel/utils/twProcessing.ts +11 -0
- package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
- package/src/components/TouchableOpacity.tsx +71 -0
- package/src/components/index.ts +3 -0
- package/src/config/markers.ts +5 -0
- package/src/index.ts +4 -5
- package/src/parser/borders.test.ts +58 -0
- package/src/parser/borders.ts +18 -3
- package/src/parser/colors.test.ts +249 -0
- package/src/parser/colors.ts +38 -0
- package/src/parser/index.ts +4 -3
- package/src/parser/layout.test.ts +61 -0
- package/src/parser/layout.ts +55 -1
- package/src/parser/sizing.ts +11 -0
- package/src/parser/typography.test.ts +102 -0
- 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
|
|
@@ -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
|
|
13
|
+
// Check if there's already a value import from react-native
|
|
14
14
|
const body = path.node.body;
|
|
15
|
-
let
|
|
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
|
-
|
|
20
|
-
|
|
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 (
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
//
|
|
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
|
|
63
|
+
// Check if there's already a value import from react-native
|
|
44
64
|
const body = path.node.body;
|
|
45
|
-
let
|
|
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
|
-
|
|
50
|
-
|
|
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 (
|
|
55
|
-
//
|
|
56
|
-
|
|
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
|
-
//
|
|
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,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,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced TouchableOpacity component with modifier support
|
|
3
|
+
* Adds active state support for active: modifier via onPressIn/onPressOut
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentRef } from "react";
|
|
7
|
+
import { forwardRef, useCallback, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
TouchableOpacity as RNTouchableOpacity,
|
|
10
|
+
type TouchableOpacityProps as RNTouchableOpacityProps,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
type ViewStyle,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
|
|
15
|
+
// TouchableOpacity state for style function
|
|
16
|
+
type TouchableOpacityState = { active: boolean; disabled: boolean | null | undefined };
|
|
17
|
+
|
|
18
|
+
export type TouchableOpacityProps = Omit<RNTouchableOpacityProps, "style"> & {
|
|
19
|
+
/**
|
|
20
|
+
* Style can be a static style object/array or a function that receives TouchableOpacity state
|
|
21
|
+
*/
|
|
22
|
+
style?: StyleProp<ViewStyle> | ((state: TouchableOpacityState) => StyleProp<ViewStyle>);
|
|
23
|
+
className?: string; // compile-time only
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Enhanced TouchableOpacity that supports active: and disabled: modifiers
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* <TouchableOpacity
|
|
31
|
+
* disabled={isLoading}
|
|
32
|
+
* className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400"
|
|
33
|
+
* >
|
|
34
|
+
* <Text>Submit</Text>
|
|
35
|
+
* </TouchableOpacity>
|
|
36
|
+
*/
|
|
37
|
+
export const TouchableOpacity = forwardRef<ComponentRef<typeof RNTouchableOpacity>, TouchableOpacityProps>(
|
|
38
|
+
function TouchableOpacity({ style, disabled = false, onPressIn, onPressOut, ...props }, ref) {
|
|
39
|
+
const [isActive, setIsActive] = useState(false);
|
|
40
|
+
|
|
41
|
+
const handlePressIn = useCallback(
|
|
42
|
+
(event: Parameters<NonNullable<RNTouchableOpacityProps["onPressIn"]>>[0]) => {
|
|
43
|
+
setIsActive(true);
|
|
44
|
+
onPressIn?.(event);
|
|
45
|
+
},
|
|
46
|
+
[onPressIn],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const handlePressOut = useCallback(
|
|
50
|
+
(event: Parameters<NonNullable<RNTouchableOpacityProps["onPressOut"]>>[0]) => {
|
|
51
|
+
setIsActive(false);
|
|
52
|
+
onPressOut?.(event);
|
|
53
|
+
},
|
|
54
|
+
[onPressOut],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Inject active and disabled state into style function context
|
|
58
|
+
const resolvedStyle = typeof style === "function" ? style({ active: isActive, disabled }) : style;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RNTouchableOpacity
|
|
62
|
+
ref={ref}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
style={resolvedStyle}
|
|
65
|
+
onPressIn={handlePressIn}
|
|
66
|
+
onPressOut={handlePressOut}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ export { generateStyleKey } from "./utils/styleKey";
|
|
|
15
15
|
export type { StyleObject } from "./types/core";
|
|
16
16
|
export type { NativeStyle, TwStyle } from "./types/runtime";
|
|
17
17
|
|
|
18
|
+
// Re-export colors
|
|
19
|
+
export { TAILWIND_COLORS } from "./config/tailwind";
|
|
20
|
+
|
|
18
21
|
// Re-export individual parsers for advanced usage
|
|
19
22
|
export {
|
|
20
23
|
parseAspectRatio,
|
|
@@ -39,8 +42,4 @@ export { SPACING_SCALE } from "./parser/spacing";
|
|
|
39
42
|
export { FONT_SIZES, LETTER_SPACING_SCALE } from "./parser/typography";
|
|
40
43
|
|
|
41
44
|
// Re-export enhanced components with modifier support
|
|
42
|
-
export
|
|
43
|
-
export type { PressableProps } from "./components/Pressable";
|
|
44
|
-
export { TextInput } from "./components/TextInput";
|
|
45
|
-
export type { TextInputProps } from "./components/TextInput";
|
|
46
|
-
export { TAILWIND_COLORS } from "./config/tailwind";
|
|
45
|
+
export * from "./components";
|