@mgcrea/react-native-tailwind 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2031
- package/dist/babel/index.cjs +1726 -1094
- package/dist/babel/plugin/componentScope.d.ts +26 -0
- package/dist/babel/plugin/componentScope.ts +87 -0
- package/dist/babel/plugin/state.d.ts +123 -0
- package/dist/babel/plugin/state.ts +185 -0
- package/dist/babel/plugin/visitors/className.d.ts +11 -0
- package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
- package/dist/babel/plugin/visitors/className.ts +652 -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 +116 -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 +116 -0
- package/dist/babel/plugin/visitors/tw.d.ts +16 -0
- package/dist/babel/plugin/visitors/tw.test.ts +771 -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 -963
- 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/directionalModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
- package/dist/babel/utils/modifierProcessing.ts +21 -0
- package/dist/babel/utils/platformModifierProcessing.ts +11 -0
- package/dist/babel/utils/styleInjection.d.ts +31 -0
- package/dist/babel/utils/styleInjection.ts +253 -7
- package/dist/babel/utils/twProcessing.d.ts +2 -0
- package/dist/babel/utils/twProcessing.ts +103 -3
- 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 +2 -2
- 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/modifiers.d.ts +32 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/parser/modifiers.test.js +1 -1
- package/dist/parser/sizing.js +1 -1
- package/dist/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.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 +6 -6
- package/src/babel/plugin/componentScope.ts +87 -0
- package/src/babel/plugin/state.ts +185 -0
- package/src/babel/plugin/visitors/className.test.ts +1625 -0
- package/src/babel/plugin/visitors/className.ts +652 -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 +116 -0
- package/src/babel/plugin/visitors/program.test.ts +325 -0
- package/src/babel/plugin/visitors/program.ts +116 -0
- package/src/babel/plugin/visitors/tw.test.ts +771 -0
- package/src/babel/plugin/visitors/tw.ts +148 -0
- package/src/babel/plugin.ts +28 -963
- 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/directionalModifierProcessing.ts +99 -0
- package/src/babel/utils/modifierProcessing.ts +21 -0
- package/src/babel/utils/platformModifierProcessing.ts +11 -0
- package/src/babel/utils/styleInjection.ts +253 -7
- package/src/babel/utils/twProcessing.ts +103 -3
- 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 +162 -0
- package/src/parser/borders.ts +67 -9
- package/src/parser/colors.test.ts +249 -0
- package/src/parser/colors.ts +38 -0
- package/src/parser/index.ts +4 -2
- package/src/parser/layout.test.ts +74 -0
- package/src/parser/layout.ts +94 -0
- package/src/parser/modifiers.test.ts +206 -0
- package/src/parser/modifiers.ts +62 -3
- package/src/parser/sizing.ts +11 -0
- package/src/parser/spacing.test.ts +66 -0
- package/src/parser/spacing.ts +15 -5
- package/src/parser/typography.test.ts +8 -0
- package/src/parser/typography.ts +4 -0
|
@@ -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:
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing directional modifiers (rtl:, ltr:)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { CustomTheme, DirectionalModifierType, ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { StyleObject } from "../../types/core.js";
|
|
8
|
+
import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Plugin state interface (subset needed for directional modifier processing)
|
|
12
|
+
*/
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
14
|
+
export interface DirectionalModifierProcessingState {
|
|
15
|
+
styleRegistry: Map<string, StyleObject>;
|
|
16
|
+
customTheme: CustomTheme;
|
|
17
|
+
stylesIdentifier: string;
|
|
18
|
+
needsI18nManagerImport: boolean;
|
|
19
|
+
i18nManagerVariableName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Process directional modifiers and generate conditional style expressions
|
|
24
|
+
*
|
|
25
|
+
* @param directionalModifiers - Array of parsed directional modifiers
|
|
26
|
+
* @param state - Plugin state
|
|
27
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
28
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
29
|
+
* @param t - Babel types
|
|
30
|
+
* @returns Array of AST nodes for conditional expressions
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* Input: [{ modifier: "rtl", baseClass: "mr-4" }, { modifier: "ltr", baseClass: "ml-4" }]
|
|
34
|
+
* Output: [
|
|
35
|
+
* _twIsRTL && styles._rtl_mr_4,
|
|
36
|
+
* !_twIsRTL && styles._ltr_ml_4
|
|
37
|
+
* ]
|
|
38
|
+
*/
|
|
39
|
+
export function processDirectionalModifiers(
|
|
40
|
+
directionalModifiers: ParsedModifier[],
|
|
41
|
+
state: DirectionalModifierProcessingState,
|
|
42
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
43
|
+
generateStyleKey: (className: string) => string,
|
|
44
|
+
t: typeof BabelTypes,
|
|
45
|
+
): BabelTypes.Expression[] {
|
|
46
|
+
// Mark that we need I18nManager import
|
|
47
|
+
state.needsI18nManagerImport = true;
|
|
48
|
+
|
|
49
|
+
// Group modifiers by direction (rtl, ltr)
|
|
50
|
+
const modifiersByDirection = new Map<DirectionalModifierType, ParsedModifier[]>();
|
|
51
|
+
|
|
52
|
+
for (const mod of directionalModifiers) {
|
|
53
|
+
const direction = mod.modifier as DirectionalModifierType;
|
|
54
|
+
if (!modifiersByDirection.has(direction)) {
|
|
55
|
+
modifiersByDirection.set(direction, []);
|
|
56
|
+
}
|
|
57
|
+
const directionGroup = modifiersByDirection.get(direction);
|
|
58
|
+
if (directionGroup) {
|
|
59
|
+
directionGroup.push(mod);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build conditional expressions for each direction
|
|
64
|
+
const conditionalExpressions: BabelTypes.Expression[] = [];
|
|
65
|
+
|
|
66
|
+
for (const [direction, modifiers] of modifiersByDirection) {
|
|
67
|
+
// Parse all classes for this direction together
|
|
68
|
+
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
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 directional modifiers (rtl:, ltr:). ` +
|
|
75
|
+
`Found in: "${direction}:${classNames}". ` +
|
|
76
|
+
`Use w-screen/h-screen without modifiers instead.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const styleKey = generateStyleKey(`${direction}_${classNames}`);
|
|
81
|
+
|
|
82
|
+
// Register style in the registry
|
|
83
|
+
state.styleRegistry.set(styleKey, styleObject);
|
|
84
|
+
|
|
85
|
+
// Create conditional:
|
|
86
|
+
// - For rtl: _twIsRTL && styles._rtl_...
|
|
87
|
+
// - For ltr: !_twIsRTL && styles._ltr_...
|
|
88
|
+
const rtlVariable = t.identifier(state.i18nManagerVariableName);
|
|
89
|
+
const directionCheck = direction === "rtl" ? rtlVariable : t.unaryExpression("!", rtlVariable);
|
|
90
|
+
|
|
91
|
+
const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
|
|
92
|
+
|
|
93
|
+
const conditionalExpression = t.logicalExpression("&&", directionCheck, styleReference);
|
|
94
|
+
|
|
95
|
+
conditionalExpressions.push(conditionalExpression);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return conditionalExpressions;
|
|
99
|
+
}
|
|
@@ -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
|
|
@@ -220,6 +220,243 @@ export function injectColorSchemeHook(
|
|
|
220
220
|
return true;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Add I18nManager import to the file or merge with existing react-native import
|
|
225
|
+
*/
|
|
226
|
+
export function addI18nManagerImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
|
|
227
|
+
// Check if there's already a value 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
|
+
// Skip type-only imports (they get erased at runtime)
|
|
234
|
+
if (statement.importKind === "type") {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
// Skip namespace imports (import * as RN) - can't add named specifiers to them
|
|
238
|
+
const hasNamespaceImport = statement.specifiers.some((spec) => t.isImportNamespaceSpecifier(spec));
|
|
239
|
+
if (hasNamespaceImport) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
existingValueImport = statement;
|
|
243
|
+
break; // Found a value import, we can stop
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (existingValueImport) {
|
|
248
|
+
// Check if I18nManager is already imported
|
|
249
|
+
const hasI18nManager = existingValueImport.specifiers.some(
|
|
250
|
+
(spec) =>
|
|
251
|
+
t.isImportSpecifier(spec) &&
|
|
252
|
+
spec.imported.type === "Identifier" &&
|
|
253
|
+
spec.imported.name === "I18nManager",
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (!hasI18nManager) {
|
|
257
|
+
// Add I18nManager to existing value import
|
|
258
|
+
existingValueImport.specifiers.push(
|
|
259
|
+
t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager")),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// No value import exists - create a new one
|
|
264
|
+
// (Don't merge with type-only or namespace imports)
|
|
265
|
+
const importDeclaration = t.importDeclaration(
|
|
266
|
+
[t.importSpecifier(t.identifier("I18nManager"), t.identifier("I18nManager"))],
|
|
267
|
+
t.stringLiteral("react-native"),
|
|
268
|
+
);
|
|
269
|
+
path.unshiftContainer("body", importDeclaration);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Inject I18nManager.isRTL variable at the top of the file (after imports and directives)
|
|
275
|
+
*
|
|
276
|
+
* Unlike hooks (useColorScheme, useWindowDimensions), I18nManager.isRTL is not a hook
|
|
277
|
+
* and can be accessed at module level. This is injected once per file.
|
|
278
|
+
*
|
|
279
|
+
* @param path - Program path
|
|
280
|
+
* @param variableName - Name for the RTL variable (e.g., '_twIsRTL')
|
|
281
|
+
* @param localIdentifier - Local identifier if I18nManager is already imported with an alias
|
|
282
|
+
* @param t - Babel types
|
|
283
|
+
*/
|
|
284
|
+
export function injectI18nManagerVariable(
|
|
285
|
+
path: NodePath<BabelTypes.Program>,
|
|
286
|
+
variableName: string,
|
|
287
|
+
localIdentifier: string | undefined,
|
|
288
|
+
t: typeof BabelTypes,
|
|
289
|
+
): void {
|
|
290
|
+
const body = path.node.body;
|
|
291
|
+
|
|
292
|
+
// Check if variable is already declared
|
|
293
|
+
for (const statement of body) {
|
|
294
|
+
if (
|
|
295
|
+
t.isVariableDeclaration(statement) &&
|
|
296
|
+
statement.declarations.length > 0 &&
|
|
297
|
+
t.isVariableDeclarator(statement.declarations[0])
|
|
298
|
+
) {
|
|
299
|
+
const declarator = statement.declarations[0];
|
|
300
|
+
if (t.isIdentifier(declarator.id) && declarator.id.name === variableName) {
|
|
301
|
+
return; // Already injected
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Use the local identifier if I18nManager was already imported with an alias,
|
|
307
|
+
// otherwise use 'I18nManager'
|
|
308
|
+
// e.g., import { I18nManager as RTL } → use RTL.isRTL
|
|
309
|
+
const identifierToUse = localIdentifier ?? "I18nManager";
|
|
310
|
+
|
|
311
|
+
// Create: const _twIsRTL = I18nManager.isRTL; (or aliased name if already imported)
|
|
312
|
+
const i18nVariable = t.variableDeclaration("const", [
|
|
313
|
+
t.variableDeclarator(
|
|
314
|
+
t.identifier(variableName),
|
|
315
|
+
t.memberExpression(t.identifier(identifierToUse), t.identifier("isRTL")),
|
|
316
|
+
),
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
// Find the index to insert after all imports and directives ('use client', 'use strict', etc.)
|
|
320
|
+
let insertIndex = 0;
|
|
321
|
+
|
|
322
|
+
for (let i = 0; i < body.length; i++) {
|
|
323
|
+
const statement = body[i];
|
|
324
|
+
|
|
325
|
+
// Skip directives ('use client', 'use strict', etc.)
|
|
326
|
+
if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
|
|
327
|
+
insertIndex = i + 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Skip imports
|
|
332
|
+
if (t.isImportDeclaration(statement)) {
|
|
333
|
+
insertIndex = i + 1;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Stop at the first non-directive, non-import statement
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Insert after imports and directives
|
|
342
|
+
body.splice(insertIndex, 0, i18nVariable);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Add useWindowDimensions import to the file or merge with existing react-native import
|
|
347
|
+
*/
|
|
348
|
+
export function addWindowDimensionsImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
|
|
349
|
+
// Check if there's already an import from react-native
|
|
350
|
+
const body = path.node.body;
|
|
351
|
+
let existingValueImport: BabelTypes.ImportDeclaration | null = null;
|
|
352
|
+
|
|
353
|
+
for (const statement of body) {
|
|
354
|
+
if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
|
|
355
|
+
// Only consider value imports (not type-only imports which get erased)
|
|
356
|
+
if (statement.importKind !== "type") {
|
|
357
|
+
existingValueImport = statement;
|
|
358
|
+
break; // Found a value import, we can stop
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// If we found a value import (not type-only), merge with it
|
|
364
|
+
if (existingValueImport) {
|
|
365
|
+
// Check if the hook is already imported
|
|
366
|
+
const hasHook = existingValueImport.specifiers.some(
|
|
367
|
+
(spec) =>
|
|
368
|
+
t.isImportSpecifier(spec) &&
|
|
369
|
+
spec.imported.type === "Identifier" &&
|
|
370
|
+
spec.imported.name === "useWindowDimensions",
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
if (!hasHook) {
|
|
374
|
+
// Add hook to existing value import
|
|
375
|
+
existingValueImport.specifiers.push(
|
|
376
|
+
t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions")),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
// No value import exists - create a new one
|
|
381
|
+
// (Don't merge with type-only imports as they get erased by Babel/TypeScript)
|
|
382
|
+
const importDeclaration = t.importDeclaration(
|
|
383
|
+
[t.importSpecifier(t.identifier("useWindowDimensions"), t.identifier("useWindowDimensions"))],
|
|
384
|
+
t.stringLiteral("react-native"),
|
|
385
|
+
);
|
|
386
|
+
path.unshiftContainer("body", importDeclaration);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Inject useWindowDimensions hook call at the top of a function component
|
|
392
|
+
*
|
|
393
|
+
* @param functionPath - Path to the function component
|
|
394
|
+
* @param dimensionsVariableName - Name for the dimensions variable
|
|
395
|
+
* @param hookName - Name of the hook to call (e.g., 'useWindowDimensions')
|
|
396
|
+
* @param localIdentifier - Local identifier if hook is already imported with an alias
|
|
397
|
+
* @param t - Babel types
|
|
398
|
+
* @returns true if hook was injected, false if already exists
|
|
399
|
+
*/
|
|
400
|
+
export function injectWindowDimensionsHook(
|
|
401
|
+
functionPath: NodePath<BabelTypes.Function>,
|
|
402
|
+
dimensionsVariableName: string,
|
|
403
|
+
hookName: string,
|
|
404
|
+
localIdentifier: string | undefined,
|
|
405
|
+
t: typeof BabelTypes,
|
|
406
|
+
): boolean {
|
|
407
|
+
let body = functionPath.node.body;
|
|
408
|
+
|
|
409
|
+
// Handle concise arrow functions: () => <JSX />
|
|
410
|
+
// Convert to block statement: () => { const _twDimensions = useWindowDimensions(); return <JSX />; }
|
|
411
|
+
if (!t.isBlockStatement(body)) {
|
|
412
|
+
if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
|
|
413
|
+
// Convert concise body to block statement with return
|
|
414
|
+
const returnStatement = t.returnStatement(body);
|
|
415
|
+
const blockStatement = t.blockStatement([returnStatement]);
|
|
416
|
+
functionPath.node.body = blockStatement;
|
|
417
|
+
body = blockStatement;
|
|
418
|
+
} else {
|
|
419
|
+
// Other non-block functions (shouldn't happen for components, but be safe)
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check if hook is already injected
|
|
425
|
+
const hasHook = body.body.some((statement) => {
|
|
426
|
+
if (
|
|
427
|
+
t.isVariableDeclaration(statement) &&
|
|
428
|
+
statement.declarations.length > 0 &&
|
|
429
|
+
t.isVariableDeclarator(statement.declarations[0])
|
|
430
|
+
) {
|
|
431
|
+
const declarator = statement.declarations[0];
|
|
432
|
+
return t.isIdentifier(declarator.id) && declarator.id.name === dimensionsVariableName;
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
if (hasHook) {
|
|
438
|
+
return false; // Already injected
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Use the local identifier if hook was already imported with an alias,
|
|
442
|
+
// otherwise use the configured hook name
|
|
443
|
+
// e.g., import { useWindowDimensions as useDims } → call useDims()
|
|
444
|
+
const identifierToCall = localIdentifier ?? hookName;
|
|
445
|
+
|
|
446
|
+
// Create: const _twDimensions = useWindowDimensions(); (or aliased name if already imported)
|
|
447
|
+
const hookCall = t.variableDeclaration("const", [
|
|
448
|
+
t.variableDeclarator(
|
|
449
|
+
t.identifier(dimensionsVariableName),
|
|
450
|
+
t.callExpression(t.identifier(identifierToCall), []),
|
|
451
|
+
),
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
// Insert at the beginning of function body
|
|
455
|
+
body.body.unshift(hookCall);
|
|
456
|
+
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
|
|
223
460
|
/**
|
|
224
461
|
* Inject StyleSheet.create with all collected styles at the top of the file
|
|
225
462
|
* This ensures the styles object is defined before any code that references it
|
|
@@ -262,20 +499,29 @@ export function injectStylesAtTop(
|
|
|
262
499
|
),
|
|
263
500
|
]);
|
|
264
501
|
|
|
265
|
-
// Find the index to insert after all imports
|
|
502
|
+
// Find the index to insert after all imports and directives ('use client', 'use strict', etc.)
|
|
266
503
|
const body = path.node.body;
|
|
267
504
|
let insertIndex = 0;
|
|
268
505
|
|
|
269
|
-
// Find the last import statement
|
|
270
506
|
for (let i = 0; i < body.length; i++) {
|
|
271
|
-
|
|
507
|
+
const statement = body[i];
|
|
508
|
+
|
|
509
|
+
// Skip directives ('use client', 'use strict', etc.)
|
|
510
|
+
if (t.isExpressionStatement(statement) && t.isStringLiteral(statement.expression)) {
|
|
272
511
|
insertIndex = i + 1;
|
|
273
|
-
|
|
274
|
-
// Stop at the first non-import statement
|
|
275
|
-
break;
|
|
512
|
+
continue;
|
|
276
513
|
}
|
|
514
|
+
|
|
515
|
+
// Skip imports
|
|
516
|
+
if (t.isImportDeclaration(statement)) {
|
|
517
|
+
insertIndex = i + 1;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Stop at the first non-directive, non-import statement
|
|
522
|
+
break;
|
|
277
523
|
}
|
|
278
524
|
|
|
279
|
-
// Insert StyleSheet.create after imports
|
|
525
|
+
// Insert StyleSheet.create after imports and directives
|
|
280
526
|
body.splice(insertIndex, 0, styleSheet);
|
|
281
527
|
}
|
|
@@ -8,13 +8,16 @@ import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/ind
|
|
|
8
8
|
import {
|
|
9
9
|
expandSchemeModifier,
|
|
10
10
|
isColorSchemeModifier,
|
|
11
|
+
isDirectionalModifier,
|
|
11
12
|
isPlatformModifier,
|
|
12
13
|
isSchemeModifier,
|
|
13
14
|
} from "../../parser/index.js";
|
|
14
15
|
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
15
16
|
import type { StyleObject } from "../../types/core.js";
|
|
16
17
|
import { processColorSchemeModifiers } from "./colorSchemeModifierProcessing.js";
|
|
18
|
+
import { processDirectionalModifiers } from "./directionalModifierProcessing.js";
|
|
17
19
|
import { processPlatformModifiers } from "./platformModifierProcessing.js";
|
|
20
|
+
import { hasRuntimeDimensions } from "./windowDimensionsProcessing.js";
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Plugin state interface (subset needed for tw processing)
|
|
@@ -32,6 +35,9 @@ export interface TwProcessingState {
|
|
|
32
35
|
colorSchemeLocalIdentifier?: string;
|
|
33
36
|
// Platform support (for ios:/android:/web: modifiers)
|
|
34
37
|
needsPlatformImport: boolean;
|
|
38
|
+
// Directional support (for rtl:/ltr: modifiers)
|
|
39
|
+
needsI18nManagerImport: boolean;
|
|
40
|
+
i18nManagerVariableName: string;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/**
|
|
@@ -77,6 +83,16 @@ export function processTwCall(
|
|
|
77
83
|
if (baseClasses.length > 0) {
|
|
78
84
|
const baseClassName = baseClasses.join(" ");
|
|
79
85
|
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
86
|
+
|
|
87
|
+
// Check for runtime dimensions (w-screen, h-screen)
|
|
88
|
+
if (hasRuntimeDimensions(baseStyleObject)) {
|
|
89
|
+
throw path.buildCodeFrameError(
|
|
90
|
+
`w-screen and h-screen are not supported in tw\`\` or twStyle() calls. ` +
|
|
91
|
+
`Found: "${baseClassName}". ` +
|
|
92
|
+
`Use them in className attributes instead.`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
81
97
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
82
98
|
|
|
@@ -91,11 +107,15 @@ export function processTwCall(
|
|
|
91
107
|
objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
// Separate color-scheme and
|
|
110
|
+
// Separate color-scheme, platform, and directional modifiers from other modifiers
|
|
95
111
|
const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
|
|
96
112
|
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
113
|
+
const directionalModifiers = modifierClasses.filter((m) => isDirectionalModifier(m.modifier));
|
|
97
114
|
const otherModifiers = modifierClasses.filter(
|
|
98
|
-
(m) =>
|
|
115
|
+
(m) =>
|
|
116
|
+
!isColorSchemeModifier(m.modifier) &&
|
|
117
|
+
!isPlatformModifier(m.modifier) &&
|
|
118
|
+
!isDirectionalModifier(m.modifier),
|
|
99
119
|
);
|
|
100
120
|
|
|
101
121
|
// Check if we need color scheme support
|
|
@@ -282,7 +302,87 @@ export function processTwCall(
|
|
|
282
302
|
}
|
|
283
303
|
}
|
|
284
304
|
|
|
285
|
-
//
|
|
305
|
+
// Process directional modifiers if present
|
|
306
|
+
const hasDirectionalModifiers = directionalModifiers.length > 0;
|
|
307
|
+
|
|
308
|
+
if (hasDirectionalModifiers) {
|
|
309
|
+
// Mark that we need I18nManager import
|
|
310
|
+
state.needsI18nManagerImport = true;
|
|
311
|
+
|
|
312
|
+
// Generate directional conditional expressions
|
|
313
|
+
const directionalConditionals = processDirectionalModifiers(
|
|
314
|
+
directionalModifiers,
|
|
315
|
+
state,
|
|
316
|
+
parseClassName,
|
|
317
|
+
generateStyleKey,
|
|
318
|
+
t,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// If we already have a style array (from color scheme or platform modifiers), add to it
|
|
322
|
+
// Otherwise, convert style property to an array
|
|
323
|
+
const styleProperty = objectProperties.find(
|
|
324
|
+
(prop) => t.isIdentifier(prop.key) && prop.key.name === "style",
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (styleProperty && t.isArrayExpression(styleProperty.value)) {
|
|
328
|
+
// Already have style array, add directional conditionals to it
|
|
329
|
+
styleProperty.value.elements.push(...directionalConditionals);
|
|
330
|
+
} else {
|
|
331
|
+
// No existing array, create style array with base + directional conditionals
|
|
332
|
+
const styleArrayElements: BabelTypes.Expression[] = [];
|
|
333
|
+
|
|
334
|
+
// Add base style if present
|
|
335
|
+
if (baseClasses.length > 0) {
|
|
336
|
+
const baseClassName = baseClasses.join(" ");
|
|
337
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
338
|
+
const baseStyleKey = generateStyleKey(baseClassName);
|
|
339
|
+
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
340
|
+
styleArrayElements.push(
|
|
341
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Add directional conditionals
|
|
346
|
+
styleArrayElements.push(...directionalConditionals);
|
|
347
|
+
|
|
348
|
+
// Replace style property with array
|
|
349
|
+
objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Also add rtlStyle/ltrStyle properties for manual processing
|
|
353
|
+
const rtlModifiers = directionalModifiers.filter((m) => m.modifier === "rtl");
|
|
354
|
+
const ltrModifiers = directionalModifiers.filter((m) => m.modifier === "ltr");
|
|
355
|
+
|
|
356
|
+
if (rtlModifiers.length > 0) {
|
|
357
|
+
const rtlClassNames = rtlModifiers.map((m) => m.baseClass).join(" ");
|
|
358
|
+
const rtlStyleObject = parseClassName(rtlClassNames, state.customTheme);
|
|
359
|
+
const rtlStyleKey = generateStyleKey(`rtl_${rtlClassNames}`);
|
|
360
|
+
state.styleRegistry.set(rtlStyleKey, rtlStyleObject);
|
|
361
|
+
|
|
362
|
+
objectProperties.push(
|
|
363
|
+
t.objectProperty(
|
|
364
|
+
t.identifier("rtlStyle"),
|
|
365
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(rtlStyleKey)),
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (ltrModifiers.length > 0) {
|
|
371
|
+
const ltrClassNames = ltrModifiers.map((m) => m.baseClass).join(" ");
|
|
372
|
+
const ltrStyleObject = parseClassName(ltrClassNames, state.customTheme);
|
|
373
|
+
const ltrStyleKey = generateStyleKey(`ltr_${ltrClassNames}`);
|
|
374
|
+
state.styleRegistry.set(ltrStyleKey, ltrStyleObject);
|
|
375
|
+
|
|
376
|
+
objectProperties.push(
|
|
377
|
+
t.objectProperty(
|
|
378
|
+
t.identifier("ltrStyle"),
|
|
379
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(ltrStyleKey)),
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Group other modifiers by type (non-color-scheme, non-platform, and non-directional modifiers)
|
|
286
386
|
const modifiersByType = new Map<ModifierType, ParsedModifier[]>();
|
|
287
387
|
for (const mod of otherModifiers) {
|
|
288
388
|
if (!modifiersByType.has(mod.modifier)) {
|