@mgcrea/react-native-tailwind 0.9.1 → 0.11.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 +386 -43
- package/dist/babel/config-loader.d.ts +12 -3
- package/dist/babel/config-loader.test.ts +154 -0
- package/dist/babel/config-loader.ts +41 -9
- package/dist/babel/index.cjs +592 -69
- package/dist/babel/plugin.d.ts +23 -1
- package/dist/babel/plugin.test.ts +331 -0
- package/dist/babel/plugin.ts +268 -37
- package/dist/babel/utils/colorSchemeModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +89 -0
- package/dist/babel/utils/dynamicProcessing.d.ts +34 -3
- package/dist/babel/utils/dynamicProcessing.ts +358 -39
- package/dist/babel/utils/modifierProcessing.d.ts +3 -3
- package/dist/babel/utils/modifierProcessing.ts +5 -5
- package/dist/babel/utils/platformModifierProcessing.d.ts +3 -3
- package/dist/babel/utils/platformModifierProcessing.ts +4 -4
- package/dist/babel/utils/styleInjection.d.ts +13 -0
- package/dist/babel/utils/styleInjection.ts +101 -0
- package/dist/babel/utils/styleTransforms.test.ts +56 -0
- package/dist/babel/utils/twProcessing.d.ts +5 -3
- package/dist/babel/utils/twProcessing.ts +27 -6
- package/dist/parser/index.d.ts +13 -6
- package/dist/parser/index.js +1 -1
- package/dist/parser/modifiers.d.ts +48 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/parser/modifiers.test.js +1 -1
- package/dist/parser/typography.d.ts +3 -1
- package/dist/parser/typography.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.d.ts +8 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/runtime.test.js +1 -1
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.js +0 -0
- package/package.json +3 -2
- package/src/babel/config-loader.test.ts +154 -0
- package/src/babel/config-loader.ts +41 -9
- package/src/babel/plugin.test.ts +331 -0
- package/src/babel/plugin.ts +268 -37
- package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
- package/src/babel/utils/dynamicProcessing.ts +358 -39
- package/src/babel/utils/modifierProcessing.ts +5 -5
- package/src/babel/utils/platformModifierProcessing.ts +4 -4
- package/src/babel/utils/styleInjection.ts +101 -0
- package/src/babel/utils/styleTransforms.test.ts +56 -0
- package/src/babel/utils/twProcessing.ts +27 -6
- package/src/parser/index.ts +28 -9
- package/src/parser/modifiers.test.ts +151 -1
- package/src/parser/modifiers.ts +139 -4
- package/src/parser/typography.ts +14 -2
- package/src/runtime.test.ts +7 -7
- package/src/runtime.ts +37 -14
- package/src/types/config.ts +7 -0
package/dist/babel/plugin.ts
CHANGED
|
@@ -7,7 +7,10 @@ import type { NodePath, PluginObj, PluginPass } from "@babel/core";
|
|
|
7
7
|
import * as BabelTypes from "@babel/types";
|
|
8
8
|
import type { ParsedModifier, StateModifierType } from "../parser/index.js";
|
|
9
9
|
import {
|
|
10
|
+
expandSchemeModifier,
|
|
11
|
+
isColorSchemeModifier,
|
|
10
12
|
isPlatformModifier,
|
|
13
|
+
isSchemeModifier,
|
|
11
14
|
isStateModifier,
|
|
12
15
|
parseClassName,
|
|
13
16
|
parsePlaceholderClasses,
|
|
@@ -15,20 +18,29 @@ import {
|
|
|
15
18
|
} from "../parser/index.js";
|
|
16
19
|
import type { StyleObject } from "../types/core.js";
|
|
17
20
|
import { generateStyleKey } from "../utils/styleKey.js";
|
|
18
|
-
import {
|
|
21
|
+
import type { CustomTheme } from "./config-loader.js";
|
|
22
|
+
import { extractCustomTheme } from "./config-loader.js";
|
|
19
23
|
|
|
20
24
|
// Import utility functions
|
|
25
|
+
import type { SchemeModifierConfig } from "../types/config.js";
|
|
21
26
|
import {
|
|
22
27
|
DEFAULT_CLASS_ATTRIBUTES,
|
|
23
28
|
buildAttributeMatchers,
|
|
24
29
|
getTargetStyleProp,
|
|
25
30
|
isAttributeSupported,
|
|
26
31
|
} from "./utils/attributeMatchers.js";
|
|
32
|
+
import { processColorSchemeModifiers } from "./utils/colorSchemeModifierProcessing.js";
|
|
27
33
|
import { getComponentModifierSupport, getStatePropertyForModifier } from "./utils/componentSupport.js";
|
|
28
34
|
import { processDynamicExpression } from "./utils/dynamicProcessing.js";
|
|
29
35
|
import { createStyleFunction, processStaticClassNameWithModifiers } from "./utils/modifierProcessing.js";
|
|
30
36
|
import { processPlatformModifiers } from "./utils/platformModifierProcessing.js";
|
|
31
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
addColorSchemeImport,
|
|
39
|
+
addPlatformImport,
|
|
40
|
+
addStyleSheetImport,
|
|
41
|
+
injectColorSchemeHook,
|
|
42
|
+
injectStylesAtTop,
|
|
43
|
+
} from "./utils/styleInjection.js";
|
|
32
44
|
import {
|
|
33
45
|
addOrMergePlaceholderTextColorProp,
|
|
34
46
|
findStyleAttribute,
|
|
@@ -61,6 +73,22 @@ export type PluginOptions = {
|
|
|
61
73
|
* @default '_twStyles'
|
|
62
74
|
*/
|
|
63
75
|
stylesIdentifier?: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Configuration for the scheme: modifier that expands to both dark: and light: modifiers
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* {
|
|
82
|
+
* darkSuffix: '-dark', // scheme:bg-primary -> dark:bg-primary-dark
|
|
83
|
+
* lightSuffix: '-light' // scheme:bg-primary -> light:bg-primary-light
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* @default { darkSuffix: '-dark', lightSuffix: '-light' }
|
|
87
|
+
*/
|
|
88
|
+
schemeModifier?: {
|
|
89
|
+
darkSuffix?: string;
|
|
90
|
+
lightSuffix?: string;
|
|
91
|
+
};
|
|
64
92
|
};
|
|
65
93
|
|
|
66
94
|
type PluginState = PluginPass & {
|
|
@@ -69,7 +97,11 @@ type PluginState = PluginPass & {
|
|
|
69
97
|
hasStyleSheetImport: boolean;
|
|
70
98
|
hasPlatformImport: boolean;
|
|
71
99
|
needsPlatformImport: boolean;
|
|
72
|
-
|
|
100
|
+
hasColorSchemeImport: boolean;
|
|
101
|
+
needsColorSchemeImport: boolean;
|
|
102
|
+
colorSchemeVariableName: string;
|
|
103
|
+
customTheme: CustomTheme;
|
|
104
|
+
schemeModifierConfig: SchemeModifierConfig;
|
|
73
105
|
supportedAttributes: Set<string>;
|
|
74
106
|
attributePatterns: RegExp[];
|
|
75
107
|
stylesIdentifier: string;
|
|
@@ -78,11 +110,91 @@ type PluginState = PluginPass & {
|
|
|
78
110
|
hasTwImport: boolean;
|
|
79
111
|
// Track react-native import path for conditional StyleSheet/Platform injection
|
|
80
112
|
reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
|
|
113
|
+
// Track function components that need colorScheme hook injection
|
|
114
|
+
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
|
|
81
115
|
};
|
|
82
116
|
|
|
83
117
|
// Default identifier for the generated StyleSheet constant
|
|
84
118
|
const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
|
|
85
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Check if a function path represents a valid component scope for hook injection
|
|
122
|
+
* Valid scopes:
|
|
123
|
+
* - Top-level FunctionDeclaration
|
|
124
|
+
* - FunctionExpression/ArrowFunctionExpression in top-level VariableDeclarator (with PascalCase name)
|
|
125
|
+
* - NOT class methods, NOT nested functions, NOT inline callbacks
|
|
126
|
+
*
|
|
127
|
+
* @param functionPath - Path to the function to check
|
|
128
|
+
* @param t - Babel types
|
|
129
|
+
* @returns true if function is a valid component scope
|
|
130
|
+
*/
|
|
131
|
+
function isComponentScope(functionPath: NodePath<BabelTypes.Function>, t: typeof BabelTypes): boolean {
|
|
132
|
+
const node = functionPath.node;
|
|
133
|
+
const parent = functionPath.parent;
|
|
134
|
+
const parentPath = functionPath.parentPath;
|
|
135
|
+
|
|
136
|
+
// Reject class methods (class components not supported for hooks)
|
|
137
|
+
if (t.isClassMethod(parent)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Reject if inside a class body
|
|
142
|
+
if (functionPath.findParent((p) => t.isClassBody(p.node))) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Accept top-level FunctionDeclaration
|
|
147
|
+
if (t.isFunctionDeclaration(node)) {
|
|
148
|
+
// Check if it's at program level or in export
|
|
149
|
+
if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Accept FunctionExpression/ArrowFunctionExpression in VariableDeclarator
|
|
155
|
+
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
|
|
156
|
+
if (t.isVariableDeclarator(parent)) {
|
|
157
|
+
// Check if it's at program level (via VariableDeclaration)
|
|
158
|
+
const varDeclarationPath = parentPath?.parentPath;
|
|
159
|
+
if (
|
|
160
|
+
varDeclarationPath &&
|
|
161
|
+
t.isVariableDeclaration(varDeclarationPath.node) &&
|
|
162
|
+
(t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))
|
|
163
|
+
) {
|
|
164
|
+
// Check for PascalCase naming (component convention)
|
|
165
|
+
if (t.isIdentifier(parent.id)) {
|
|
166
|
+
const name = parent.id.name;
|
|
167
|
+
return /^[A-Z]/.test(name); // Starts with uppercase
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Find the nearest valid component scope for hook injection
|
|
178
|
+
* Climbs the AST from the current path to find a component-level function
|
|
179
|
+
*
|
|
180
|
+
* @param path - Starting path (e.g., JSXAttribute)
|
|
181
|
+
* @param t - Babel types
|
|
182
|
+
* @returns NodePath to component function, or null if not found
|
|
183
|
+
*/
|
|
184
|
+
function findComponentScope(path: NodePath, t: typeof BabelTypes): NodePath<BabelTypes.Function> | null {
|
|
185
|
+
let current = path.getFunctionParent();
|
|
186
|
+
|
|
187
|
+
while (current) {
|
|
188
|
+
if (t.isFunction(current.node) && isComponentScope(current, t)) {
|
|
189
|
+
return current;
|
|
190
|
+
}
|
|
191
|
+
// Climb to next parent function
|
|
192
|
+
current = current.getFunctionParent();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
86
198
|
export default function reactNativeTailwindBabelPlugin(
|
|
87
199
|
{ types: t }: { types: typeof BabelTypes },
|
|
88
200
|
options?: PluginOptions,
|
|
@@ -92,6 +204,12 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
92
204
|
const { exactMatches, patterns } = buildAttributeMatchers(attributes);
|
|
93
205
|
const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
|
|
94
206
|
|
|
207
|
+
// Scheme modifier configuration from plugin options
|
|
208
|
+
const schemeModifierConfig = {
|
|
209
|
+
darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
|
|
210
|
+
lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
|
|
211
|
+
};
|
|
212
|
+
|
|
95
213
|
return {
|
|
96
214
|
name: "react-native-tailwind",
|
|
97
215
|
|
|
@@ -104,14 +222,21 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
104
222
|
state.hasStyleSheetImport = false;
|
|
105
223
|
state.hasPlatformImport = false;
|
|
106
224
|
state.needsPlatformImport = false;
|
|
225
|
+
state.hasColorSchemeImport = false;
|
|
226
|
+
state.needsColorSchemeImport = false;
|
|
227
|
+
state.colorSchemeVariableName = "_twColorScheme";
|
|
107
228
|
state.supportedAttributes = exactMatches;
|
|
108
229
|
state.attributePatterns = patterns;
|
|
109
230
|
state.stylesIdentifier = stylesIdentifier;
|
|
110
231
|
state.twImportNames = new Set();
|
|
111
232
|
state.hasTwImport = false;
|
|
233
|
+
state.functionComponentsNeedingColorScheme = new Set();
|
|
234
|
+
|
|
235
|
+
// Load custom theme from tailwind.config.*
|
|
236
|
+
state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
|
|
112
237
|
|
|
113
|
-
//
|
|
114
|
-
state.
|
|
238
|
+
// Use scheme modifier config from plugin options
|
|
239
|
+
state.schemeModifierConfig = schemeModifierConfig;
|
|
115
240
|
},
|
|
116
241
|
|
|
117
242
|
exit(path, state) {
|
|
@@ -135,6 +260,18 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
135
260
|
addPlatformImport(path, t);
|
|
136
261
|
}
|
|
137
262
|
|
|
263
|
+
// Add useColorScheme import if color scheme modifiers were used and not already present
|
|
264
|
+
if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
|
|
265
|
+
addColorSchemeImport(path, t);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Inject useColorScheme hook in function components that need it
|
|
269
|
+
if (state.needsColorSchemeImport) {
|
|
270
|
+
for (const functionPath of state.functionComponentsNeedingColorScheme) {
|
|
271
|
+
injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
138
275
|
// Generate and inject StyleSheet.create at the beginning of the file (after imports)
|
|
139
276
|
// This ensures _twStyles is defined before any code that references it
|
|
140
277
|
injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
|
|
@@ -163,6 +300,13 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
163
300
|
return false;
|
|
164
301
|
});
|
|
165
302
|
|
|
303
|
+
const hasUseColorScheme = specifiers.some((spec) => {
|
|
304
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
305
|
+
return spec.imported.name === "useColorScheme";
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
});
|
|
309
|
+
|
|
166
310
|
// Only track if imports exist - don't mutate yet
|
|
167
311
|
// Actual import injection happens in Program.exit only if needed
|
|
168
312
|
if (hasStyleSheet) {
|
|
@@ -173,6 +317,10 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
173
317
|
state.hasPlatformImport = true;
|
|
174
318
|
}
|
|
175
319
|
|
|
320
|
+
if (hasUseColorScheme) {
|
|
321
|
+
state.hasColorSchemeImport = true;
|
|
322
|
+
}
|
|
323
|
+
|
|
176
324
|
// Store reference to the react-native import for later modification if needed
|
|
177
325
|
state.reactNativeImportPath = path;
|
|
178
326
|
}
|
|
@@ -323,12 +471,31 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
323
471
|
|
|
324
472
|
state.hasClassNames = true;
|
|
325
473
|
|
|
326
|
-
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
|
|
327
|
-
const { baseClasses, modifierClasses } = splitModifierClasses(className);
|
|
474
|
+
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:, dark:, light:, scheme:)
|
|
475
|
+
const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
|
|
476
|
+
|
|
477
|
+
// Expand scheme: modifiers into dark: and light: modifiers
|
|
478
|
+
const modifierClasses: ParsedModifier[] = [];
|
|
479
|
+
for (const modifier of rawModifierClasses) {
|
|
480
|
+
if (isSchemeModifier(modifier.modifier)) {
|
|
481
|
+
// Expand scheme: into dark: and light:
|
|
482
|
+
const expanded = expandSchemeModifier(
|
|
483
|
+
modifier,
|
|
484
|
+
state.customTheme.colors ?? {},
|
|
485
|
+
state.schemeModifierConfig.darkSuffix,
|
|
486
|
+
state.schemeModifierConfig.lightSuffix,
|
|
487
|
+
);
|
|
488
|
+
modifierClasses.push(...expanded);
|
|
489
|
+
} else {
|
|
490
|
+
// Keep other modifiers as-is
|
|
491
|
+
modifierClasses.push(modifier);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
328
494
|
|
|
329
495
|
// Separate modifiers by type
|
|
330
496
|
const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
|
|
331
497
|
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
498
|
+
const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
|
|
332
499
|
const stateModifiers = modifierClasses.filter(
|
|
333
500
|
(m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
|
|
334
501
|
);
|
|
@@ -341,7 +508,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
341
508
|
|
|
342
509
|
if (componentSupport?.supportedModifiers.includes("placeholder")) {
|
|
343
510
|
const placeholderClasses = placeholderModifiers.map((m) => m.baseClass).join(" ");
|
|
344
|
-
const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.
|
|
511
|
+
const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.customTheme.colors);
|
|
345
512
|
|
|
346
513
|
if (placeholderColor) {
|
|
347
514
|
// Add or merge placeholderTextColor prop
|
|
@@ -359,24 +526,43 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
359
526
|
|
|
360
527
|
// Handle combination of modifiers
|
|
361
528
|
const hasPlatformModifiers = platformModifiers.length > 0;
|
|
529
|
+
const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
|
|
362
530
|
const hasStateModifiers = stateModifiers.length > 0;
|
|
363
531
|
const hasBaseClasses = baseClasses.length > 0;
|
|
364
532
|
|
|
365
|
-
// If we have
|
|
366
|
-
|
|
367
|
-
if (
|
|
533
|
+
// If we have color scheme modifiers, we need to track the parent function component
|
|
534
|
+
let componentScope: NodePath<BabelTypes.Function> | null = null;
|
|
535
|
+
if (hasColorSchemeModifiers) {
|
|
536
|
+
componentScope = findComponentScope(path, t);
|
|
537
|
+
if (componentScope) {
|
|
538
|
+
state.functionComponentsNeedingColorScheme.add(componentScope);
|
|
539
|
+
} else {
|
|
540
|
+
// Warn if color scheme modifiers used in invalid context (class component, nested function)
|
|
541
|
+
if (process.env.NODE_ENV !== "production") {
|
|
542
|
+
console.warn(
|
|
543
|
+
`[react-native-tailwind] dark:/light: modifiers require a function component scope. ` +
|
|
544
|
+
`Found in non-component context at ${state.file.opts.filename ?? "unknown"}. ` +
|
|
545
|
+
`These modifiers are not supported in class components or nested callbacks.`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// If we have multiple modifier types, combine them in an array expression
|
|
552
|
+
// For state modifiers, wrap in arrow function; for color scheme, they're just conditionals
|
|
553
|
+
if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
|
|
368
554
|
// Get the JSX opening element for component support checking
|
|
369
555
|
const jsxOpeningElement = path.parent;
|
|
370
556
|
const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
|
|
371
557
|
|
|
372
558
|
if (componentSupport) {
|
|
373
|
-
// Build style array: [baseStyle, Platform.select(...), stateConditionals]
|
|
559
|
+
// Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, stateConditionals]
|
|
374
560
|
const styleArrayElements: BabelTypes.Expression[] = [];
|
|
375
561
|
|
|
376
562
|
// Add base classes
|
|
377
563
|
if (hasBaseClasses) {
|
|
378
564
|
const baseClassName = baseClasses.join(" ");
|
|
379
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
565
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
380
566
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
381
567
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
382
568
|
styleArrayElements.push(
|
|
@@ -385,14 +571,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
385
571
|
}
|
|
386
572
|
|
|
387
573
|
// Add platform modifiers as Platform.select()
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
574
|
+
if (hasPlatformModifiers) {
|
|
575
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
576
|
+
platformModifiers,
|
|
577
|
+
state,
|
|
578
|
+
parseClassName,
|
|
579
|
+
generateStyleKey,
|
|
580
|
+
t,
|
|
581
|
+
);
|
|
582
|
+
styleArrayElements.push(platformSelectExpression);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Add color scheme modifiers as conditionals (only if component scope exists)
|
|
586
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
587
|
+
const colorSchemeConditionals = processColorSchemeModifiers(
|
|
588
|
+
colorSchemeModifiers,
|
|
589
|
+
state,
|
|
590
|
+
parseClassName,
|
|
591
|
+
generateStyleKey,
|
|
592
|
+
t,
|
|
593
|
+
);
|
|
594
|
+
styleArrayElements.push(...colorSchemeConditionals);
|
|
595
|
+
}
|
|
396
596
|
|
|
397
597
|
// Add state modifiers as conditionals
|
|
398
598
|
// Group by modifier type
|
|
@@ -412,7 +612,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
412
612
|
}
|
|
413
613
|
|
|
414
614
|
const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
415
|
-
const modifierStyleObject = parseClassName(modifierClassNames, state.
|
|
615
|
+
const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
|
|
416
616
|
const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
|
|
417
617
|
state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
|
|
418
618
|
|
|
@@ -446,15 +646,15 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
446
646
|
}
|
|
447
647
|
}
|
|
448
648
|
|
|
449
|
-
// Handle platform
|
|
450
|
-
if (hasPlatformModifiers && !hasStateModifiers) {
|
|
451
|
-
// Build style array/expression: [baseStyle, Platform.select(...)]
|
|
649
|
+
// Handle platform and/or color scheme modifiers (no state modifiers)
|
|
650
|
+
if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
|
|
651
|
+
// Build style array/expression: [baseStyle, Platform.select(...), colorSchemeConditionals]
|
|
452
652
|
const styleExpressions: BabelTypes.Expression[] = [];
|
|
453
653
|
|
|
454
654
|
// Add base classes
|
|
455
655
|
if (hasBaseClasses) {
|
|
456
656
|
const baseClassName = baseClasses.join(" ");
|
|
457
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
657
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
458
658
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
459
659
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
460
660
|
styleExpressions.push(
|
|
@@ -463,14 +663,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
463
663
|
}
|
|
464
664
|
|
|
465
665
|
// Add platform modifiers as Platform.select()
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
666
|
+
if (hasPlatformModifiers) {
|
|
667
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
668
|
+
platformModifiers,
|
|
669
|
+
state,
|
|
670
|
+
parseClassName,
|
|
671
|
+
generateStyleKey,
|
|
672
|
+
t,
|
|
673
|
+
);
|
|
674
|
+
styleExpressions.push(platformSelectExpression);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Add color scheme modifiers as conditionals (only if we have a valid component scope)
|
|
678
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
679
|
+
const colorSchemeConditionals = processColorSchemeModifiers(
|
|
680
|
+
colorSchemeModifiers,
|
|
681
|
+
state,
|
|
682
|
+
parseClassName,
|
|
683
|
+
generateStyleKey,
|
|
684
|
+
t,
|
|
685
|
+
);
|
|
686
|
+
styleExpressions.push(...colorSchemeConditionals);
|
|
687
|
+
}
|
|
474
688
|
|
|
475
689
|
// Generate style attribute
|
|
476
690
|
const styleExpression =
|
|
@@ -602,7 +816,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
602
816
|
return;
|
|
603
817
|
}
|
|
604
818
|
|
|
605
|
-
const styleObject = parseClassName(classNameForStyle, state.
|
|
819
|
+
const styleObject = parseClassName(classNameForStyle, state.customTheme);
|
|
606
820
|
const styleKey = generateStyleKey(classNameForStyle);
|
|
607
821
|
state.styleRegistry.set(styleKey, styleObject);
|
|
608
822
|
|
|
@@ -629,8 +843,25 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
629
843
|
}
|
|
630
844
|
|
|
631
845
|
try {
|
|
632
|
-
//
|
|
633
|
-
const
|
|
846
|
+
// Find component scope for color scheme modifiers
|
|
847
|
+
const componentScope = findComponentScope(path, t);
|
|
848
|
+
|
|
849
|
+
// Process dynamic expression with modifier support
|
|
850
|
+
const result = processDynamicExpression(
|
|
851
|
+
expression,
|
|
852
|
+
state,
|
|
853
|
+
parseClassName,
|
|
854
|
+
generateStyleKey,
|
|
855
|
+
splitModifierClasses,
|
|
856
|
+
processPlatformModifiers,
|
|
857
|
+
processColorSchemeModifiers,
|
|
858
|
+
componentScope,
|
|
859
|
+
isPlatformModifier as (modifier: unknown) => boolean,
|
|
860
|
+
isColorSchemeModifier as (modifier: unknown) => boolean,
|
|
861
|
+
isSchemeModifier as (modifier: unknown) => boolean,
|
|
862
|
+
expandSchemeModifier,
|
|
863
|
+
t,
|
|
864
|
+
);
|
|
634
865
|
|
|
635
866
|
if (result) {
|
|
636
867
|
state.hasClassNames = true;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing color scheme modifiers (dark:, light:)
|
|
3
|
+
*/
|
|
4
|
+
import type * as BabelTypes from "@babel/types";
|
|
5
|
+
import type { CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { StyleObject } from "../../types/core.js";
|
|
7
|
+
/**
|
|
8
|
+
* Plugin state interface (subset needed for color scheme modifier processing)
|
|
9
|
+
*/
|
|
10
|
+
export interface ColorSchemeModifierProcessingState {
|
|
11
|
+
styleRegistry: Map<string, StyleObject>;
|
|
12
|
+
customTheme: CustomTheme;
|
|
13
|
+
stylesIdentifier: string;
|
|
14
|
+
needsColorSchemeImport: boolean;
|
|
15
|
+
colorSchemeVariableName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Process color scheme modifiers and generate conditional style expressions
|
|
19
|
+
*
|
|
20
|
+
* @param colorSchemeModifiers - Array of parsed color scheme modifiers
|
|
21
|
+
* @param state - Plugin state
|
|
22
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
23
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
24
|
+
* @param t - Babel types
|
|
25
|
+
* @returns Array of AST nodes for conditional expressions
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
|
|
29
|
+
* Output: [
|
|
30
|
+
* _twColorScheme === 'dark' && styles._dark_bg_gray_900,
|
|
31
|
+
* _twColorScheme === 'light' && styles._light_bg_white
|
|
32
|
+
* ]
|
|
33
|
+
*/
|
|
34
|
+
export declare function processColorSchemeModifiers(colorSchemeModifiers: ParsedModifier[], state: ColorSchemeModifierProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): BabelTypes.Expression[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing color scheme modifiers (dark:, light:)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { ColorSchemeModifierType, CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { StyleObject } from "../../types/core.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin state interface (subset needed for color scheme modifier processing)
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
|
+
export interface ColorSchemeModifierProcessingState {
|
|
14
|
+
styleRegistry: Map<string, StyleObject>;
|
|
15
|
+
customTheme: CustomTheme;
|
|
16
|
+
stylesIdentifier: string;
|
|
17
|
+
needsColorSchemeImport: boolean;
|
|
18
|
+
colorSchemeVariableName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Process color scheme modifiers and generate conditional style expressions
|
|
23
|
+
*
|
|
24
|
+
* @param colorSchemeModifiers - Array of parsed color scheme modifiers
|
|
25
|
+
* @param state - Plugin state
|
|
26
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
27
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
28
|
+
* @param t - Babel types
|
|
29
|
+
* @returns Array of AST nodes for conditional expressions
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
|
|
33
|
+
* Output: [
|
|
34
|
+
* _twColorScheme === 'dark' && styles._dark_bg_gray_900,
|
|
35
|
+
* _twColorScheme === 'light' && styles._light_bg_white
|
|
36
|
+
* ]
|
|
37
|
+
*/
|
|
38
|
+
export function processColorSchemeModifiers(
|
|
39
|
+
colorSchemeModifiers: ParsedModifier[],
|
|
40
|
+
state: ColorSchemeModifierProcessingState,
|
|
41
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
42
|
+
generateStyleKey: (className: string) => string,
|
|
43
|
+
t: typeof BabelTypes,
|
|
44
|
+
): BabelTypes.Expression[] {
|
|
45
|
+
// Mark that we need useColorScheme import and hook injection
|
|
46
|
+
state.needsColorSchemeImport = true;
|
|
47
|
+
|
|
48
|
+
// Group modifiers by color scheme (dark, light)
|
|
49
|
+
const modifiersByScheme = new Map<ColorSchemeModifierType, ParsedModifier[]>();
|
|
50
|
+
|
|
51
|
+
for (const mod of colorSchemeModifiers) {
|
|
52
|
+
const scheme = mod.modifier as ColorSchemeModifierType;
|
|
53
|
+
if (!modifiersByScheme.has(scheme)) {
|
|
54
|
+
modifiersByScheme.set(scheme, []);
|
|
55
|
+
}
|
|
56
|
+
const schemeGroup = modifiersByScheme.get(scheme);
|
|
57
|
+
if (schemeGroup) {
|
|
58
|
+
schemeGroup.push(mod);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Build conditional expressions for each color scheme
|
|
63
|
+
const conditionalExpressions: BabelTypes.Expression[] = [];
|
|
64
|
+
|
|
65
|
+
for (const [scheme, modifiers] of modifiersByScheme) {
|
|
66
|
+
// Parse all classes for this color scheme together
|
|
67
|
+
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
68
|
+
const styleObject = parseClassName(classNames, state.customTheme);
|
|
69
|
+
const styleKey = generateStyleKey(`${scheme}_${classNames}`);
|
|
70
|
+
|
|
71
|
+
// Register style in the registry
|
|
72
|
+
state.styleRegistry.set(styleKey, styleObject);
|
|
73
|
+
|
|
74
|
+
// Create conditional: _twColorScheme === 'dark' && styles._dark_bg_gray_900
|
|
75
|
+
const colorSchemeCheck = t.binaryExpression(
|
|
76
|
+
"===",
|
|
77
|
+
t.identifier(state.colorSchemeVariableName),
|
|
78
|
+
t.stringLiteral(scheme),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
|
|
82
|
+
|
|
83
|
+
const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
|
|
84
|
+
|
|
85
|
+
conditionalExpressions.push(conditionalExpression);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return conditionalExpressions;
|
|
89
|
+
}
|
|
@@ -1,16 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for processing dynamic className expressions
|
|
3
3
|
*/
|
|
4
|
+
import type { NodePath } from "@babel/core";
|
|
4
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
5
8
|
import type { StyleObject } from "../../types/core.js";
|
|
6
9
|
/**
|
|
7
10
|
* Plugin state interface (subset needed for dynamic processing)
|
|
8
11
|
*/
|
|
9
12
|
export interface DynamicProcessingState {
|
|
10
13
|
styleRegistry: Map<string, StyleObject>;
|
|
11
|
-
|
|
14
|
+
customTheme: CustomTheme;
|
|
15
|
+
schemeModifierConfig: SchemeModifierConfig;
|
|
12
16
|
stylesIdentifier: string;
|
|
17
|
+
needsPlatformImport: boolean;
|
|
18
|
+
needsColorSchemeImport: boolean;
|
|
19
|
+
colorSchemeVariableName: string;
|
|
20
|
+
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
|
|
13
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Type for the splitModifierClasses function
|
|
24
|
+
*/
|
|
25
|
+
export type SplitModifierClassesFn = (className: string) => {
|
|
26
|
+
baseClasses: string[];
|
|
27
|
+
modifierClasses: ParsedModifier[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Type for the processPlatformModifiers function
|
|
31
|
+
*/
|
|
32
|
+
export type ProcessPlatformModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression;
|
|
33
|
+
/**
|
|
34
|
+
* Type for the processColorSchemeModifiers function
|
|
35
|
+
*/
|
|
36
|
+
export type ProcessColorSchemeModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression[];
|
|
37
|
+
/**
|
|
38
|
+
* Type for modifier type guard functions
|
|
39
|
+
*/
|
|
40
|
+
export type ModifierTypeGuardFn = (modifier: unknown) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Type for the expandSchemeModifier function
|
|
43
|
+
*/
|
|
44
|
+
export type ExpandSchemeModifierFn = (modifier: ParsedModifier, customColors: Record<string, string>, darkSuffix: string, lightSuffix: string) => ParsedModifier[];
|
|
14
45
|
/**
|
|
15
46
|
* Result of processing a dynamic expression
|
|
16
47
|
*/
|
|
@@ -22,8 +53,8 @@ export type DynamicExpressionResult = {
|
|
|
22
53
|
* Process a dynamic className expression
|
|
23
54
|
* Extracts static strings and transforms the expression to use pre-compiled styles
|
|
24
55
|
*/
|
|
25
|
-
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string,
|
|
26
|
-
expression: BabelTypes.
|
|
56
|
+
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, splitModifierClasses: SplitModifierClassesFn, processPlatformModifiers: ProcessPlatformModifiersFn, processColorSchemeModifiers: ProcessColorSchemeModifiersFn, componentScope: NodePath<BabelTypes.Function> | null, isPlatformModifier: ModifierTypeGuardFn, isColorSchemeModifier: ModifierTypeGuardFn, isSchemeModifier: ModifierTypeGuardFn, expandSchemeModifier: ExpandSchemeModifierFn, t: typeof BabelTypes): {
|
|
57
|
+
expression: BabelTypes.Expression;
|
|
27
58
|
staticParts: string[] | undefined;
|
|
28
59
|
} | {
|
|
29
60
|
expression: BabelTypes.ConditionalExpression;
|