@mgcrea/react-native-tailwind 0.8.1 → 0.9.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 +152 -0
- package/dist/babel/config-loader.ts +2 -0
- package/dist/babel/index.cjs +177 -4
- package/dist/babel/plugin.d.ts +2 -0
- package/dist/babel/plugin.test.ts +241 -0
- package/dist/babel/plugin.ts +187 -10
- package/dist/babel/utils/platformModifierProcessing.d.ts +30 -0
- package/dist/babel/utils/platformModifierProcessing.ts +80 -0
- package/dist/babel/utils/styleInjection.d.ts +4 -0
- package/dist/babel/utils/styleInjection.ts +28 -0
- package/dist/babel/utils/styleTransforms.ts +1 -0
- package/dist/parser/index.d.ts +2 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/modifiers.d.ts +20 -2
- package/dist/parser/modifiers.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/dist/stubs/tw.test.js +1 -0
- package/package.json +6 -5
- package/src/babel/config-loader.ts +2 -0
- package/src/babel/plugin.test.ts +241 -0
- package/src/babel/plugin.ts +187 -10
- package/src/babel/utils/platformModifierProcessing.ts +80 -0
- package/src/babel/utils/styleInjection.ts +28 -0
- package/src/babel/utils/styleTransforms.ts +1 -0
- package/src/parser/aspectRatio.ts +1 -0
- package/src/parser/borders.ts +2 -0
- package/src/parser/colors.ts +2 -0
- package/src/parser/index.ts +9 -2
- package/src/parser/layout.ts +2 -0
- package/src/parser/modifiers.ts +38 -4
- package/src/parser/placeholder.ts +1 -0
- package/src/parser/sizing.ts +1 -0
- package/src/parser/spacing.ts +1 -0
- package/src/parser/transforms.ts +5 -0
- package/src/parser/typography.ts +2 -0
- package/src/stubs/tw.test.ts +27 -0
package/dist/babel/plugin.ts
CHANGED
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import type { NodePath, PluginObj, PluginPass } from "@babel/core";
|
|
7
7
|
import * as BabelTypes from "@babel/types";
|
|
8
|
-
import {
|
|
8
|
+
import type { ParsedModifier, StateModifierType } from "../parser/index.js";
|
|
9
|
+
import {
|
|
10
|
+
isPlatformModifier,
|
|
11
|
+
isStateModifier,
|
|
12
|
+
parseClassName,
|
|
13
|
+
parsePlaceholderClasses,
|
|
14
|
+
splitModifierClasses,
|
|
15
|
+
} from "../parser/index.js";
|
|
9
16
|
import type { StyleObject } from "../types/core.js";
|
|
10
17
|
import { generateStyleKey } from "../utils/styleKey.js";
|
|
11
18
|
import { extractCustomColors } from "./config-loader.js";
|
|
@@ -17,10 +24,11 @@ import {
|
|
|
17
24
|
getTargetStyleProp,
|
|
18
25
|
isAttributeSupported,
|
|
19
26
|
} from "./utils/attributeMatchers.js";
|
|
20
|
-
import { getComponentModifierSupport } from "./utils/componentSupport.js";
|
|
27
|
+
import { getComponentModifierSupport, getStatePropertyForModifier } from "./utils/componentSupport.js";
|
|
21
28
|
import { processDynamicExpression } from "./utils/dynamicProcessing.js";
|
|
22
29
|
import { createStyleFunction, processStaticClassNameWithModifiers } from "./utils/modifierProcessing.js";
|
|
23
|
-
import {
|
|
30
|
+
import { processPlatformModifiers } from "./utils/platformModifierProcessing.js";
|
|
31
|
+
import { addPlatformImport, addStyleSheetImport, injectStylesAtTop } from "./utils/styleInjection.js";
|
|
24
32
|
import {
|
|
25
33
|
addOrMergePlaceholderTextColorProp,
|
|
26
34
|
findStyleAttribute,
|
|
@@ -59,6 +67,8 @@ type PluginState = PluginPass & {
|
|
|
59
67
|
styleRegistry: Map<string, StyleObject>;
|
|
60
68
|
hasClassNames: boolean;
|
|
61
69
|
hasStyleSheetImport: boolean;
|
|
70
|
+
hasPlatformImport: boolean;
|
|
71
|
+
needsPlatformImport: boolean;
|
|
62
72
|
customColors: Record<string, string>;
|
|
63
73
|
supportedAttributes: Set<string>;
|
|
64
74
|
attributePatterns: RegExp[];
|
|
@@ -90,6 +100,8 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
90
100
|
state.styleRegistry = new Map();
|
|
91
101
|
state.hasClassNames = false;
|
|
92
102
|
state.hasStyleSheetImport = false;
|
|
103
|
+
state.hasPlatformImport = false;
|
|
104
|
+
state.needsPlatformImport = false;
|
|
93
105
|
state.supportedAttributes = exactMatches;
|
|
94
106
|
state.attributePatterns = patterns;
|
|
95
107
|
state.stylesIdentifier = stylesIdentifier;
|
|
@@ -116,19 +128,25 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
116
128
|
addStyleSheetImport(path, t);
|
|
117
129
|
}
|
|
118
130
|
|
|
131
|
+
// Add Platform import if platform modifiers were used and not already present
|
|
132
|
+
if (state.needsPlatformImport && !state.hasPlatformImport) {
|
|
133
|
+
addPlatformImport(path, t);
|
|
134
|
+
}
|
|
135
|
+
|
|
119
136
|
// Generate and inject StyleSheet.create at the beginning of the file (after imports)
|
|
120
137
|
// This ensures _twStyles is defined before any code that references it
|
|
121
138
|
injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
|
|
122
139
|
},
|
|
123
140
|
},
|
|
124
141
|
|
|
125
|
-
// Check if StyleSheet
|
|
142
|
+
// Check if StyleSheet/Platform are already imported and track tw/twStyle imports
|
|
126
143
|
ImportDeclaration(path, state) {
|
|
127
144
|
const node = path.node;
|
|
128
145
|
|
|
129
|
-
// Track react-native StyleSheet
|
|
146
|
+
// Track react-native StyleSheet and Platform imports
|
|
130
147
|
if (node.source.value === "react-native") {
|
|
131
148
|
const specifiers = node.specifiers;
|
|
149
|
+
|
|
132
150
|
const hasStyleSheet = specifiers.some((spec) => {
|
|
133
151
|
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
134
152
|
return spec.imported.name === "StyleSheet";
|
|
@@ -136,6 +154,13 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
136
154
|
return false;
|
|
137
155
|
});
|
|
138
156
|
|
|
157
|
+
const hasPlatform = specifiers.some((spec) => {
|
|
158
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
159
|
+
return spec.imported.name === "Platform";
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
});
|
|
163
|
+
|
|
139
164
|
if (hasStyleSheet) {
|
|
140
165
|
state.hasStyleSheetImport = true;
|
|
141
166
|
} else {
|
|
@@ -143,6 +168,10 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
143
168
|
node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
|
|
144
169
|
state.hasStyleSheetImport = true;
|
|
145
170
|
}
|
|
171
|
+
|
|
172
|
+
if (hasPlatform) {
|
|
173
|
+
state.hasPlatformImport = true;
|
|
174
|
+
}
|
|
146
175
|
}
|
|
147
176
|
|
|
148
177
|
// Track tw/twStyle imports from main package (for compile-time transformation)
|
|
@@ -291,12 +320,15 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
291
320
|
|
|
292
321
|
state.hasClassNames = true;
|
|
293
322
|
|
|
294
|
-
// Check if className contains modifiers (active:, hover:, focus:, placeholder:)
|
|
323
|
+
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
|
|
295
324
|
const { baseClasses, modifierClasses } = splitModifierClasses(className);
|
|
296
325
|
|
|
297
|
-
// Separate
|
|
326
|
+
// Separate modifiers by type
|
|
298
327
|
const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
|
|
299
|
-
const
|
|
328
|
+
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
329
|
+
const stateModifiers = modifierClasses.filter(
|
|
330
|
+
(m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
|
|
331
|
+
);
|
|
300
332
|
|
|
301
333
|
// Handle placeholder modifiers first (they generate placeholderTextColor prop, not style)
|
|
302
334
|
if (placeholderModifiers.length > 0) {
|
|
@@ -322,8 +354,153 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
322
354
|
}
|
|
323
355
|
}
|
|
324
356
|
|
|
325
|
-
//
|
|
326
|
-
|
|
357
|
+
// Handle combination of modifiers
|
|
358
|
+
const hasPlatformModifiers = platformModifiers.length > 0;
|
|
359
|
+
const hasStateModifiers = stateModifiers.length > 0;
|
|
360
|
+
const hasBaseClasses = baseClasses.length > 0;
|
|
361
|
+
|
|
362
|
+
// If we have both state and platform modifiers, or platform modifiers with complex state,
|
|
363
|
+
// we need to combine them in an array expression wrapped in an arrow function
|
|
364
|
+
if (hasStateModifiers && hasPlatformModifiers) {
|
|
365
|
+
// Get the JSX opening element for component support checking
|
|
366
|
+
const jsxOpeningElement = path.parent;
|
|
367
|
+
const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
|
|
368
|
+
|
|
369
|
+
if (componentSupport) {
|
|
370
|
+
// Build style array: [baseStyle, Platform.select(...), stateConditionals]
|
|
371
|
+
const styleArrayElements: BabelTypes.Expression[] = [];
|
|
372
|
+
|
|
373
|
+
// Add base classes
|
|
374
|
+
if (hasBaseClasses) {
|
|
375
|
+
const baseClassName = baseClasses.join(" ");
|
|
376
|
+
const baseStyleObject = parseClassName(baseClassName, state.customColors);
|
|
377
|
+
const baseStyleKey = generateStyleKey(baseClassName);
|
|
378
|
+
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
379
|
+
styleArrayElements.push(
|
|
380
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Add platform modifiers as Platform.select()
|
|
385
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
386
|
+
platformModifiers,
|
|
387
|
+
state,
|
|
388
|
+
parseClassName,
|
|
389
|
+
generateStyleKey,
|
|
390
|
+
t,
|
|
391
|
+
);
|
|
392
|
+
styleArrayElements.push(platformSelectExpression);
|
|
393
|
+
|
|
394
|
+
// Add state modifiers as conditionals
|
|
395
|
+
// Group by modifier type
|
|
396
|
+
const modifiersByType = new Map<StateModifierType, ParsedModifier[]>();
|
|
397
|
+
for (const mod of stateModifiers) {
|
|
398
|
+
const modType = mod.modifier as StateModifierType;
|
|
399
|
+
if (!modifiersByType.has(modType)) {
|
|
400
|
+
modifiersByType.set(modType, []);
|
|
401
|
+
}
|
|
402
|
+
modifiersByType.get(modType)?.push(mod);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Build conditionals for each state modifier type
|
|
406
|
+
for (const [modifierType, modifiers] of modifiersByType) {
|
|
407
|
+
if (!componentSupport.supportedModifiers.includes(modifierType)) {
|
|
408
|
+
continue; // Skip unsupported modifiers
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
412
|
+
const modifierStyleObject = parseClassName(modifierClassNames, state.customColors);
|
|
413
|
+
const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
|
|
414
|
+
state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
|
|
415
|
+
|
|
416
|
+
const stateProperty = getStatePropertyForModifier(modifierType);
|
|
417
|
+
const conditionalExpression = t.logicalExpression(
|
|
418
|
+
"&&",
|
|
419
|
+
t.identifier(stateProperty),
|
|
420
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey)),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
styleArrayElements.push(conditionalExpression);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Wrap in arrow function for state support
|
|
427
|
+
const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter((mod) =>
|
|
428
|
+
componentSupport.supportedModifiers.includes(mod),
|
|
429
|
+
);
|
|
430
|
+
const styleArrayExpression = t.arrayExpression(styleArrayElements);
|
|
431
|
+
const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
|
|
432
|
+
|
|
433
|
+
const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
|
|
434
|
+
if (styleAttribute) {
|
|
435
|
+
mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
|
|
436
|
+
} else {
|
|
437
|
+
replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
} else {
|
|
441
|
+
// Component doesn't support state modifiers, but we can still use platform modifiers
|
|
442
|
+
// Fall through to platform-only handling
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Handle platform-only modifiers (no state modifiers)
|
|
447
|
+
if (hasPlatformModifiers && !hasStateModifiers) {
|
|
448
|
+
// Build style array/expression: [baseStyle, Platform.select(...)]
|
|
449
|
+
const styleExpressions: BabelTypes.Expression[] = [];
|
|
450
|
+
|
|
451
|
+
// Add base classes
|
|
452
|
+
if (hasBaseClasses) {
|
|
453
|
+
const baseClassName = baseClasses.join(" ");
|
|
454
|
+
const baseStyleObject = parseClassName(baseClassName, state.customColors);
|
|
455
|
+
const baseStyleKey = generateStyleKey(baseClassName);
|
|
456
|
+
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
457
|
+
styleExpressions.push(
|
|
458
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Add platform modifiers as Platform.select()
|
|
463
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
464
|
+
platformModifiers,
|
|
465
|
+
state,
|
|
466
|
+
parseClassName,
|
|
467
|
+
generateStyleKey,
|
|
468
|
+
t,
|
|
469
|
+
);
|
|
470
|
+
styleExpressions.push(platformSelectExpression);
|
|
471
|
+
|
|
472
|
+
// Generate style attribute
|
|
473
|
+
const styleExpression =
|
|
474
|
+
styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
|
|
475
|
+
|
|
476
|
+
const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
|
|
477
|
+
if (styleAttribute) {
|
|
478
|
+
// Merge with existing style attribute
|
|
479
|
+
const existingStyle = styleAttribute.value;
|
|
480
|
+
if (
|
|
481
|
+
t.isJSXExpressionContainer(existingStyle) &&
|
|
482
|
+
!t.isJSXEmptyExpression(existingStyle.expression)
|
|
483
|
+
) {
|
|
484
|
+
const existing = existingStyle.expression;
|
|
485
|
+
// Merge as array: [ourStyles, existingStyles]
|
|
486
|
+
const mergedArray = t.isArrayExpression(existing)
|
|
487
|
+
? t.arrayExpression([styleExpression, ...existing.elements])
|
|
488
|
+
: t.arrayExpression([styleExpression, existing]);
|
|
489
|
+
styleAttribute.value = t.jsxExpressionContainer(mergedArray);
|
|
490
|
+
} else {
|
|
491
|
+
styleAttribute.value = t.jsxExpressionContainer(styleExpression);
|
|
492
|
+
}
|
|
493
|
+
path.remove();
|
|
494
|
+
} else {
|
|
495
|
+
// Replace className with style prop containing our expression
|
|
496
|
+
path.node.name = t.jsxIdentifier(targetStyleProp);
|
|
497
|
+
path.node.value = t.jsxExpressionContainer(styleExpression);
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// If there are state modifiers (and no platform modifiers), check if this component supports them
|
|
503
|
+
if (hasStateModifiers) {
|
|
327
504
|
// Get the JSX opening element (the direct parent of the attribute)
|
|
328
505
|
const jsxOpeningElement = path.parent;
|
|
329
506
|
const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing platform modifiers (ios:, android:, web:)
|
|
3
|
+
*/
|
|
4
|
+
import type * as BabelTypes from "@babel/types";
|
|
5
|
+
import type { ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { StyleObject } from "../../types/core.js";
|
|
7
|
+
/**
|
|
8
|
+
* Plugin state interface (subset needed for platform modifier processing)
|
|
9
|
+
*/
|
|
10
|
+
export interface PlatformModifierProcessingState {
|
|
11
|
+
styleRegistry: Map<string, StyleObject>;
|
|
12
|
+
customColors: Record<string, string>;
|
|
13
|
+
stylesIdentifier: string;
|
|
14
|
+
needsPlatformImport: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Process platform modifiers and generate Platform.select() expression
|
|
18
|
+
*
|
|
19
|
+
* @param platformModifiers - Array of parsed platform modifiers
|
|
20
|
+
* @param state - Plugin state
|
|
21
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
22
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
23
|
+
* @param t - Babel types
|
|
24
|
+
* @returns AST node for Platform.select() call
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* Input: [{ modifier: "ios", baseClass: "shadow-lg" }, { modifier: "android", baseClass: "elevation-4" }]
|
|
28
|
+
* Output: Platform.select({ ios: styles._ios_shadow_lg, android: styles._android_elevation_4 })
|
|
29
|
+
*/
|
|
30
|
+
export declare function processPlatformModifiers(platformModifiers: ParsedModifier[], state: PlatformModifierProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): BabelTypes.Expression;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing platform modifiers (ios:, android:, web:)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { ParsedModifier, PlatformModifierType } from "../../parser/index.js";
|
|
7
|
+
import type { StyleObject } from "../../types/core.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin state interface (subset needed for platform modifier processing)
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
|
+
export interface PlatformModifierProcessingState {
|
|
14
|
+
styleRegistry: Map<string, StyleObject>;
|
|
15
|
+
customColors: Record<string, string>;
|
|
16
|
+
stylesIdentifier: string;
|
|
17
|
+
needsPlatformImport: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Process platform modifiers and generate Platform.select() expression
|
|
22
|
+
*
|
|
23
|
+
* @param platformModifiers - Array of parsed platform modifiers
|
|
24
|
+
* @param state - Plugin state
|
|
25
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
26
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
27
|
+
* @param t - Babel types
|
|
28
|
+
* @returns AST node for Platform.select() call
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* Input: [{ modifier: "ios", baseClass: "shadow-lg" }, { modifier: "android", baseClass: "elevation-4" }]
|
|
32
|
+
* Output: Platform.select({ ios: styles._ios_shadow_lg, android: styles._android_elevation_4 })
|
|
33
|
+
*/
|
|
34
|
+
export function processPlatformModifiers(
|
|
35
|
+
platformModifiers: ParsedModifier[],
|
|
36
|
+
state: PlatformModifierProcessingState,
|
|
37
|
+
parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
|
|
38
|
+
generateStyleKey: (className: string) => string,
|
|
39
|
+
t: typeof BabelTypes,
|
|
40
|
+
): BabelTypes.Expression {
|
|
41
|
+
// Mark that we need Platform import
|
|
42
|
+
state.needsPlatformImport = true;
|
|
43
|
+
|
|
44
|
+
// Group modifiers by platform
|
|
45
|
+
const modifiersByPlatform = new Map<PlatformModifierType, ParsedModifier[]>();
|
|
46
|
+
|
|
47
|
+
for (const mod of platformModifiers) {
|
|
48
|
+
const platform = mod.modifier as PlatformModifierType;
|
|
49
|
+
if (!modifiersByPlatform.has(platform)) {
|
|
50
|
+
modifiersByPlatform.set(platform, []);
|
|
51
|
+
}
|
|
52
|
+
const platformGroup = modifiersByPlatform.get(platform);
|
|
53
|
+
if (platformGroup) {
|
|
54
|
+
platformGroup.push(mod);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build Platform.select() object properties
|
|
59
|
+
const selectProperties: BabelTypes.ObjectProperty[] = [];
|
|
60
|
+
|
|
61
|
+
for (const [platform, modifiers] of modifiersByPlatform) {
|
|
62
|
+
// Parse all classes for this platform together
|
|
63
|
+
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
64
|
+
const styleObject = parseClassName(classNames, state.customColors);
|
|
65
|
+
const styleKey = generateStyleKey(`${platform}_${classNames}`);
|
|
66
|
+
|
|
67
|
+
// Register style in the registry
|
|
68
|
+
state.styleRegistry.set(styleKey, styleObject);
|
|
69
|
+
|
|
70
|
+
// Create property: ios: styles._ios_shadow_lg
|
|
71
|
+
const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
|
|
72
|
+
|
|
73
|
+
selectProperties.push(t.objectProperty(t.identifier(platform), styleReference));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Create Platform.select({ ios: ..., android: ... })
|
|
77
|
+
return t.callExpression(t.memberExpression(t.identifier("Platform"), t.identifier("select")), [
|
|
78
|
+
t.objectExpression(selectProperties),
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
@@ -8,6 +8,10 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
8
8
|
* Add StyleSheet import to the file
|
|
9
9
|
*/
|
|
10
10
|
export declare function addStyleSheetImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void;
|
|
11
|
+
/**
|
|
12
|
+
* Add Platform import to the file or merge with existing react-native import
|
|
13
|
+
*/
|
|
14
|
+
export declare function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void;
|
|
11
15
|
/**
|
|
12
16
|
* Inject StyleSheet.create with all collected styles at the top of the file
|
|
13
17
|
* This ensures the styles object is defined before any code that references it
|
|
@@ -19,6 +19,34 @@ export function addStyleSheetImport(path: NodePath<BabelTypes.Program>, t: typeo
|
|
|
19
19
|
path.unshiftContainer("body", importDeclaration);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Add Platform import to the file or merge with existing react-native import
|
|
24
|
+
*/
|
|
25
|
+
export function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
|
|
26
|
+
// Check if there's already a react-native import
|
|
27
|
+
const body = path.node.body;
|
|
28
|
+
let reactNativeImport: BabelTypes.ImportDeclaration | null = null;
|
|
29
|
+
|
|
30
|
+
for (const statement of body) {
|
|
31
|
+
if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
|
|
32
|
+
reactNativeImport = statement;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (reactNativeImport) {
|
|
38
|
+
// Add Platform to existing react-native import
|
|
39
|
+
reactNativeImport.specifiers.push(t.importSpecifier(t.identifier("Platform"), t.identifier("Platform")));
|
|
40
|
+
} else {
|
|
41
|
+
// Create new react-native import with Platform
|
|
42
|
+
const importDeclaration = t.importDeclaration(
|
|
43
|
+
[t.importSpecifier(t.identifier("Platform"), t.identifier("Platform"))],
|
|
44
|
+
t.stringLiteral("react-native"),
|
|
45
|
+
);
|
|
46
|
+
path.unshiftContainer("body", importDeclaration);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
22
50
|
/**
|
|
23
51
|
* Inject StyleSheet.create with all collected styles at the top of the file
|
|
24
52
|
* This ensures the styles object is defined before any code that references it
|
|
@@ -242,6 +242,7 @@ export function addOrMergePlaceholderTextColorProp(
|
|
|
242
242
|
if (existingProp) {
|
|
243
243
|
// If explicit prop exists, don't override it (explicit props take precedence)
|
|
244
244
|
// This matches the behavior of style prop precedence
|
|
245
|
+
/* v8 ignore next 5 */
|
|
245
246
|
if (process.env.NODE_ENV !== "production") {
|
|
246
247
|
console.warn(
|
|
247
248
|
`[react-native-tailwind] placeholderTextColor prop will be overridden by className placeholder: modifier. ` +
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -27,5 +27,5 @@ export { parseSizing } from "./sizing";
|
|
|
27
27
|
export { parseSpacing } from "./spacing";
|
|
28
28
|
export { parseTransform } from "./transforms";
|
|
29
29
|
export { parseTypography } from "./typography";
|
|
30
|
-
export { hasModifier, parseModifier, splitModifierClasses } from "./modifiers";
|
|
31
|
-
export type { ModifierType, ParsedModifier } from "./modifiers";
|
|
30
|
+
export { hasModifier, isPlatformModifier, isStateModifier, parseModifier, splitModifierClasses, } from "./modifiers";
|
|
31
|
+
export type { ModifierType, ParsedModifier, PlatformModifierType, StateModifierType } from "./modifiers";
|
package/dist/parser/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"hasModifier",{enumerable:true,get:function get(){return _modifiers.hasModifier;}});Object.defineProperty(exports,"parseAspectRatio",{enumerable:true,get:function get(){return _aspectRatio.parseAspectRatio;}});Object.defineProperty(exports,"parseBorder",{enumerable:true,get:function get(){return _borders.parseBorder;}});exports.parseClass=parseClass;exports.parseClassName=parseClassName;Object.defineProperty(exports,"parseColor",{enumerable:true,get:function get(){return _colors.parseColor;}});Object.defineProperty(exports,"parseLayout",{enumerable:true,get:function get(){return _layout.parseLayout;}});Object.defineProperty(exports,"parseModifier",{enumerable:true,get:function get(){return _modifiers.parseModifier;}});Object.defineProperty(exports,"parsePlaceholderClass",{enumerable:true,get:function get(){return _placeholder.parsePlaceholderClass;}});Object.defineProperty(exports,"parsePlaceholderClasses",{enumerable:true,get:function get(){return _placeholder.parsePlaceholderClasses;}});Object.defineProperty(exports,"parseShadow",{enumerable:true,get:function get(){return _shadows.parseShadow;}});Object.defineProperty(exports,"parseSizing",{enumerable:true,get:function get(){return _sizing.parseSizing;}});Object.defineProperty(exports,"parseSpacing",{enumerable:true,get:function get(){return _spacing.parseSpacing;}});Object.defineProperty(exports,"parseTransform",{enumerable:true,get:function get(){return _transforms.parseTransform;}});Object.defineProperty(exports,"parseTypography",{enumerable:true,get:function get(){return _typography.parseTypography;}});Object.defineProperty(exports,"splitModifierClasses",{enumerable:true,get:function get(){return _modifiers.splitModifierClasses;}});var _aspectRatio=require("./aspectRatio");var _borders=require("./borders");var _colors=require("./colors");var _layout=require("./layout");var _shadows=require("./shadows");var _sizing=require("./sizing");var _spacing=require("./spacing");var _transforms=require("./transforms");var _typography=require("./typography");var _placeholder=require("./placeholder");var _modifiers=require("./modifiers");function parseClassName(className,customColors){var classes=className.split(/\s+/).filter(Boolean);var style={};for(var cls of classes){var parsedStyle=parseClass(cls,customColors);Object.assign(style,parsedStyle);}return style;}function parseClass(cls,customColors){var parsers=[_spacing.parseSpacing,_borders.parseBorder,function(cls){return(0,_colors.parseColor)(cls,customColors);},_layout.parseLayout,_typography.parseTypography,_sizing.parseSizing,_shadows.parseShadow,_aspectRatio.parseAspectRatio,_transforms.parseTransform];for(var parser of parsers){var result=parser(cls);if(result!==null){return result;}}if(process.env.NODE_ENV!=="production"){console.warn(`[react-native-tailwind] Unknown class: "${cls}"`);}return{};}
|
|
1
|
+
Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"hasModifier",{enumerable:true,get:function get(){return _modifiers.hasModifier;}});Object.defineProperty(exports,"isPlatformModifier",{enumerable:true,get:function get(){return _modifiers.isPlatformModifier;}});Object.defineProperty(exports,"isStateModifier",{enumerable:true,get:function get(){return _modifiers.isStateModifier;}});Object.defineProperty(exports,"parseAspectRatio",{enumerable:true,get:function get(){return _aspectRatio.parseAspectRatio;}});Object.defineProperty(exports,"parseBorder",{enumerable:true,get:function get(){return _borders.parseBorder;}});exports.parseClass=parseClass;exports.parseClassName=parseClassName;Object.defineProperty(exports,"parseColor",{enumerable:true,get:function get(){return _colors.parseColor;}});Object.defineProperty(exports,"parseLayout",{enumerable:true,get:function get(){return _layout.parseLayout;}});Object.defineProperty(exports,"parseModifier",{enumerable:true,get:function get(){return _modifiers.parseModifier;}});Object.defineProperty(exports,"parsePlaceholderClass",{enumerable:true,get:function get(){return _placeholder.parsePlaceholderClass;}});Object.defineProperty(exports,"parsePlaceholderClasses",{enumerable:true,get:function get(){return _placeholder.parsePlaceholderClasses;}});Object.defineProperty(exports,"parseShadow",{enumerable:true,get:function get(){return _shadows.parseShadow;}});Object.defineProperty(exports,"parseSizing",{enumerable:true,get:function get(){return _sizing.parseSizing;}});Object.defineProperty(exports,"parseSpacing",{enumerable:true,get:function get(){return _spacing.parseSpacing;}});Object.defineProperty(exports,"parseTransform",{enumerable:true,get:function get(){return _transforms.parseTransform;}});Object.defineProperty(exports,"parseTypography",{enumerable:true,get:function get(){return _typography.parseTypography;}});Object.defineProperty(exports,"splitModifierClasses",{enumerable:true,get:function get(){return _modifiers.splitModifierClasses;}});var _aspectRatio=require("./aspectRatio");var _borders=require("./borders");var _colors=require("./colors");var _layout=require("./layout");var _shadows=require("./shadows");var _sizing=require("./sizing");var _spacing=require("./spacing");var _transforms=require("./transforms");var _typography=require("./typography");var _placeholder=require("./placeholder");var _modifiers=require("./modifiers");function parseClassName(className,customColors){var classes=className.split(/\s+/).filter(Boolean);var style={};for(var cls of classes){var parsedStyle=parseClass(cls,customColors);Object.assign(style,parsedStyle);}return style;}function parseClass(cls,customColors){var parsers=[_spacing.parseSpacing,_borders.parseBorder,function(cls){return(0,_colors.parseColor)(cls,customColors);},_layout.parseLayout,_typography.parseTypography,_sizing.parseSizing,_shadows.parseShadow,_aspectRatio.parseAspectRatio,_transforms.parseTransform];for(var parser of parsers){var result=parser(cls);if(result!==null){return result;}}if(process.env.NODE_ENV!=="production"){console.warn(`[react-native-tailwind] Unknown class: "${cls}"`);}return{};}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Modifier parsing utilities for state-based class names
|
|
2
|
+
* Modifier parsing utilities for state-based and platform-specific class names
|
|
3
|
+
* - State modifiers: active:, hover:, focus:, disabled:, placeholder:
|
|
4
|
+
* - Platform modifiers: ios:, android:, web:
|
|
3
5
|
*/
|
|
4
|
-
export type
|
|
6
|
+
export type StateModifierType = "active" | "hover" | "focus" | "disabled" | "placeholder";
|
|
7
|
+
export type PlatformModifierType = "ios" | "android" | "web";
|
|
8
|
+
export type ModifierType = StateModifierType | PlatformModifierType;
|
|
5
9
|
export type ParsedModifier = {
|
|
6
10
|
modifier: ModifierType;
|
|
7
11
|
baseClass: string;
|
|
@@ -25,6 +29,20 @@ export declare function parseModifier(cls: string): ParsedModifier | null;
|
|
|
25
29
|
* @returns true if class has a supported modifier prefix
|
|
26
30
|
*/
|
|
27
31
|
export declare function hasModifier(cls: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a modifier is a state modifier (active, hover, focus, disabled, placeholder)
|
|
34
|
+
*
|
|
35
|
+
* @param modifier - Modifier type to check
|
|
36
|
+
* @returns true if modifier is a state modifier
|
|
37
|
+
*/
|
|
38
|
+
export declare function isStateModifier(modifier: ModifierType): modifier is StateModifierType;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a modifier is a platform modifier (ios, android, web)
|
|
41
|
+
*
|
|
42
|
+
* @param modifier - Modifier type to check
|
|
43
|
+
* @returns true if modifier is a platform modifier
|
|
44
|
+
*/
|
|
45
|
+
export declare function isPlatformModifier(modifier: ModifierType): modifier is PlatformModifierType;
|
|
28
46
|
/**
|
|
29
47
|
* Split a space-separated className string into base and modifier classes
|
|
30
48
|
*
|
package/dist/parser/modifiers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,"__esModule",{value:true});exports.hasModifier=hasModifier;exports.parseModifier=parseModifier;exports.splitModifierClasses=splitModifierClasses;var
|
|
1
|
+
Object.defineProperty(exports,"__esModule",{value:true});exports.hasModifier=hasModifier;exports.isPlatformModifier=isPlatformModifier;exports.isStateModifier=isStateModifier;exports.parseModifier=parseModifier;exports.splitModifierClasses=splitModifierClasses;var STATE_MODIFIERS=["active","hover","focus","disabled","placeholder"];var PLATFORM_MODIFIERS=["ios","android","web"];var SUPPORTED_MODIFIERS=[].concat(STATE_MODIFIERS,PLATFORM_MODIFIERS);function parseModifier(cls){var colonIndex=cls.indexOf(":");if(colonIndex===-1){return null;}var potentialModifier=cls.slice(0,colonIndex);var baseClass=cls.slice(colonIndex+1);if(!SUPPORTED_MODIFIERS.includes(potentialModifier)){return null;}if(baseClass.includes(":")){return null;}if(!baseClass){return null;}return{modifier:potentialModifier,baseClass:baseClass};}function hasModifier(cls){return parseModifier(cls)!==null;}function isStateModifier(modifier){return STATE_MODIFIERS.includes(modifier);}function isPlatformModifier(modifier){return PLATFORM_MODIFIERS.includes(modifier);}function splitModifierClasses(className){var classes=className.trim().split(/\s+/).filter(Boolean);var baseClasses=[];var modifierClasses=[];for(var cls of classes){var parsed=parseModifier(cls);if(parsed){modifierClasses.push(parsed);}else{baseClasses.push(cls);}}return{baseClasses:baseClasses,modifierClasses:modifierClasses};}
|