@colorye/react-native-css 0.3.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 +198 -0
- package/crates/transformer/index.js +105 -0
- package/crates/transformer/transformer.darwin-arm64.node +0 -0
- package/crates/transformer/transformer.node +0 -0
- package/dist/babel.js +208 -0
- package/dist/exported-stylesheet.json +1 -0
- package/dist/features/build-transform.js +575 -0
- package/dist/features/css-calc.js +539 -0
- package/dist/features/css-media.js +77 -0
- package/dist/features/css-transform.js +426 -0
- package/dist/features/css-vars.js +153 -0
- package/dist/features/stylesheet.js +45 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +60 -0
- package/dist/interop.js +260 -0
- package/dist/transformer-runtime.js +274 -0
- package/dist/transformer.js +100 -0
- package/dist/utils/babel.js +363 -0
- package/dist/utils/css.js +263 -0
- package/dist/utils/helper.js +11 -0
- package/package.json +51 -0
- package/src/babel.js +271 -0
- package/src/exported-stylesheet.json +1 -0
- package/src/features/build-transform.js +536 -0
- package/src/features/css-calc.js +490 -0
- package/src/features/css-media.js +78 -0
- package/src/features/css-transform.js +446 -0
- package/src/features/css-vars.js +138 -0
- package/src/features/stylesheet.js +29 -0
- package/src/index.d.ts +89 -0
- package/src/index.js +29 -0
- package/src/interop.js +269 -0
- package/src/transformer-runtime.js +241 -0
- package/src/transformer.js +87 -0
- package/src/utils/babel.js +425 -0
- package/src/utils/css.js +221 -0
- package/src/utils/helper.js +3 -0
- package/types.d.ts +47 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import Runtime from "../transformer-runtime.js";
|
|
2
|
+
|
|
3
|
+
export function isImportOrRequire(statement) {
|
|
4
|
+
return (
|
|
5
|
+
statement.isImportDeclaration() ||
|
|
6
|
+
statement.node?.declarations?.[0]?.init?.callee?.name === "require"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isFragmentElement(t, elementName) {
|
|
11
|
+
return (
|
|
12
|
+
(t.isJSXIdentifier(elementName) &&
|
|
13
|
+
(elementName.name === "Fragment" || elementName.name === "React.Fragment")) ||
|
|
14
|
+
(t.isJSXMemberExpression(elementName) &&
|
|
15
|
+
elementName.object.name === "React" &&
|
|
16
|
+
elementName.property.name === "Fragment") ||
|
|
17
|
+
t.isJSXIdentifier(elementName, { name: "" }) // JSX shorthand fragment
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isRootLevelJSXElement(path) {
|
|
22
|
+
let currentPath = path;
|
|
23
|
+
|
|
24
|
+
while (currentPath.parentPath) {
|
|
25
|
+
const parent = currentPath.parentPath;
|
|
26
|
+
|
|
27
|
+
if (parent.isReturnStatement()) return true;
|
|
28
|
+
if (parent.isArrowFunctionExpression() && parent.node.body === currentPath.node) return true;
|
|
29
|
+
if (parent.isJSXElement() && parent !== path) return false;
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
parent.isConditionalExpression() ||
|
|
33
|
+
parent.isLogicalExpression() ||
|
|
34
|
+
parent.isParenthesizedExpression()
|
|
35
|
+
) {
|
|
36
|
+
currentPath = parent;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
currentPath = parent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Static style detection helpers
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if className is a static string literal
|
|
52
|
+
*/
|
|
53
|
+
export function isStaticClassName(t, classNameAttr) {
|
|
54
|
+
if (!classNameAttr) return false;
|
|
55
|
+
if (t.isStringLiteral(classNameAttr.value)) return true;
|
|
56
|
+
if (
|
|
57
|
+
t.isJSXExpressionContainer(classNameAttr.value) &&
|
|
58
|
+
t.isStringLiteral(classNameAttr.value.expression)
|
|
59
|
+
) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get static className string value
|
|
67
|
+
*/
|
|
68
|
+
export function getStaticClassNameValue(t, classNameAttr) {
|
|
69
|
+
if (!classNameAttr) return null;
|
|
70
|
+
if (t.isStringLiteral(classNameAttr.value)) return classNameAttr.value.value;
|
|
71
|
+
if (
|
|
72
|
+
t.isJSXExpressionContainer(classNameAttr.value) &&
|
|
73
|
+
t.isStringLiteral(classNameAttr.value.expression)
|
|
74
|
+
) {
|
|
75
|
+
return classNameAttr.value.expression.value;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a class is fully static (no dynamic breakpoint / dark mode queries)
|
|
82
|
+
*/
|
|
83
|
+
export function isClassStatic(stylesheet, className) {
|
|
84
|
+
if (!className) return true;
|
|
85
|
+
if (
|
|
86
|
+
className === "group" ||
|
|
87
|
+
className.startsWith("group") ||
|
|
88
|
+
className.startsWith("peer") ||
|
|
89
|
+
className.startsWith("active:") ||
|
|
90
|
+
className.startsWith("pressed:") ||
|
|
91
|
+
className.startsWith("disabled:") ||
|
|
92
|
+
className.startsWith("dark:") ||
|
|
93
|
+
className.startsWith("light:") ||
|
|
94
|
+
className.startsWith("sm:") ||
|
|
95
|
+
className.startsWith("md:") ||
|
|
96
|
+
className.startsWith("lg:") ||
|
|
97
|
+
className.startsWith("xl:") ||
|
|
98
|
+
className.startsWith("2xl:") ||
|
|
99
|
+
className.startsWith("portrait:") ||
|
|
100
|
+
className.startsWith("landscape:")
|
|
101
|
+
) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if all classes in className string are static
|
|
109
|
+
*/
|
|
110
|
+
export function areAllClassesStatic(stylesheet, classNameValue) {
|
|
111
|
+
if (!classNameValue || !stylesheet) return false;
|
|
112
|
+
const classes = classNameValue.trim().split(/\s+/).filter(Boolean);
|
|
113
|
+
return classes.every((cls) => isClassStatic(stylesheet, cls));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Compute merged static styles from className string using Runtime compiler
|
|
118
|
+
*/
|
|
119
|
+
export function computeStaticStyles(stylesheet, classNameValue) {
|
|
120
|
+
if (!classNameValue || !stylesheet) return {};
|
|
121
|
+
try {
|
|
122
|
+
const computed = Runtime.getStyle(stylesheet, [undefined, classNameValue, undefined]);
|
|
123
|
+
return computed || {};
|
|
124
|
+
} catch {
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inlines static className and contentContainerClassName attributes directly into styles
|
|
131
|
+
*/
|
|
132
|
+
export function inlineStaticAttributes(path, state, t) {
|
|
133
|
+
if (!state.stylesheetData) return;
|
|
134
|
+
|
|
135
|
+
const openingElement = path.node.openingElement;
|
|
136
|
+
const stylesheet = state.stylesheetData;
|
|
137
|
+
|
|
138
|
+
const classPropMappings = [
|
|
139
|
+
{ classProp: "className", styleProp: "style" },
|
|
140
|
+
{ classProp: "contentContainerClassName", styleProp: "contentContainerStyle" },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const { classProp, styleProp } of classPropMappings) {
|
|
144
|
+
const classAttrIndex = openingElement.attributes.findIndex(
|
|
145
|
+
(attr) => t.isJSXAttribute(attr) && attr.name?.name === classProp,
|
|
146
|
+
);
|
|
147
|
+
if (classAttrIndex === -1) continue;
|
|
148
|
+
|
|
149
|
+
const classAttr = openingElement.attributes[classAttrIndex];
|
|
150
|
+
if (!isStaticClassName(t, classAttr)) continue;
|
|
151
|
+
|
|
152
|
+
const classValue = getStaticClassNameValue(t, classAttr);
|
|
153
|
+
if (!classValue || !areAllClassesStatic(stylesheet, classValue)) continue;
|
|
154
|
+
|
|
155
|
+
const staticStyles = computeStaticStyles(stylesheet, classValue);
|
|
156
|
+
if (!staticStyles || Object.keys(staticStyles).length === 0) continue;
|
|
157
|
+
|
|
158
|
+
const styleAST = objectToAST(t, staticStyles);
|
|
159
|
+
|
|
160
|
+
// Find existing style prop if present
|
|
161
|
+
const existingStyle = openingElement.attributes.find(
|
|
162
|
+
(attr) => t.isJSXAttribute(attr) && attr.name?.name === styleProp,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (existingStyle) {
|
|
166
|
+
const currentVal = t.isJSXExpressionContainer(existingStyle.value)
|
|
167
|
+
? existingStyle.value.expression
|
|
168
|
+
: existingStyle.value;
|
|
169
|
+
existingStyle.value = t.jsxExpressionContainer(
|
|
170
|
+
t.arrayExpression([styleAST, currentVal]),
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
openingElement.attributes.push(
|
|
174
|
+
t.jsxAttribute(t.jsxIdentifier(styleProp), t.jsxExpressionContainer(styleAST)),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Remove the static class attribute to achieve zero runtime parsing
|
|
179
|
+
openingElement.attributes.splice(classAttrIndex, 1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Convert JavaScript object to Babel AST
|
|
185
|
+
*/
|
|
186
|
+
export function objectToAST(t, obj) {
|
|
187
|
+
if (obj === null || obj === undefined) return t.nullLiteral();
|
|
188
|
+
|
|
189
|
+
if (Array.isArray(obj)) {
|
|
190
|
+
return t.arrayExpression(obj.map((item) => objectToAST(t, item)));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof obj === "object") {
|
|
194
|
+
return t.objectExpression(
|
|
195
|
+
Object.entries(obj).map(([key, value]) =>
|
|
196
|
+
t.objectProperty(
|
|
197
|
+
/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? t.identifier(key) : t.stringLiteral(key),
|
|
198
|
+
objectToAST(t, value),
|
|
199
|
+
),
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (typeof obj === "string") return t.stringLiteral(obj);
|
|
205
|
+
if (typeof obj === "number") return t.numericLiteral(obj);
|
|
206
|
+
if (typeof obj === "boolean") return t.booleanLiteral(obj);
|
|
207
|
+
|
|
208
|
+
return t.nullLiteral();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Try to get static style info for an element
|
|
213
|
+
* Returns { staticStyles, hasInheritStyle, inlineStyleExpr } or null if not static
|
|
214
|
+
*/
|
|
215
|
+
export function tryGetStaticStyleInfo(path, state, t) {
|
|
216
|
+
const openingElement = path.node.openingElement;
|
|
217
|
+
|
|
218
|
+
const classNameAttr = openingElement.attributes.find(
|
|
219
|
+
(attr) => t.isJSXAttribute(attr) && attr.name?.name === "className",
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Check if className is static
|
|
223
|
+
if (!isStaticClassName(t, classNameAttr)) return null;
|
|
224
|
+
|
|
225
|
+
// Get stylesheet
|
|
226
|
+
const stylesheet = state.stylesheetData;
|
|
227
|
+
if (!stylesheet) return null;
|
|
228
|
+
|
|
229
|
+
const classNameValue = getStaticClassNameValue(t, classNameAttr);
|
|
230
|
+
if (!classNameValue) return null;
|
|
231
|
+
|
|
232
|
+
// Check if all classes are fully static
|
|
233
|
+
if (!areAllClassesStatic(stylesheet, classNameValue)) return null;
|
|
234
|
+
|
|
235
|
+
// Compute static styles from className
|
|
236
|
+
const staticStyles = computeStaticStyles(stylesheet, classNameValue);
|
|
237
|
+
|
|
238
|
+
// Get inheritStyle attribute (for mergeStyles call)
|
|
239
|
+
const inheritStyleAttr = openingElement.attributes.find(
|
|
240
|
+
(attr) => t.isJSXAttribute(attr) && attr.name?.name === "inheritStyle",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Get style attribute
|
|
244
|
+
const styleAttr = openingElement.attributes.find(
|
|
245
|
+
(attr) => t.isJSXAttribute(attr) && attr.name?.name === "style",
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
staticStyles,
|
|
250
|
+
inheritStyleAttr,
|
|
251
|
+
styleAttr,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Generate mergeStyles(inheritStyle, staticStyles, inlineStyle) expression
|
|
257
|
+
*/
|
|
258
|
+
export function getStaticMergeExpression(path, state, t, staticInfo) {
|
|
259
|
+
const { staticStyles, inheritStyleAttr, styleAttr } = staticInfo;
|
|
260
|
+
|
|
261
|
+
// Get inheritStyle expression
|
|
262
|
+
const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
|
|
263
|
+
const inheritStyleExpr = inheritStyleAttr?.value?.expression;
|
|
264
|
+
|
|
265
|
+
// Build inheritStyle array: [props.inheritStyle, explicitInheritStyle]
|
|
266
|
+
let inheritArg;
|
|
267
|
+
if (propInheritStyle || inheritStyleExpr) {
|
|
268
|
+
inheritArg = t.arrayExpression([
|
|
269
|
+
propInheritStyle || t.nullLiteral(),
|
|
270
|
+
inheritStyleExpr || t.nullLiteral(),
|
|
271
|
+
]);
|
|
272
|
+
} else {
|
|
273
|
+
inheritArg = t.nullLiteral();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Static styles as inline object
|
|
277
|
+
const staticArg =
|
|
278
|
+
Object.keys(staticStyles).length > 0 ? objectToAST(t, staticStyles) : t.nullLiteral();
|
|
279
|
+
|
|
280
|
+
// Inline style expression
|
|
281
|
+
const inlineArg = styleAttr?.value?.expression || t.nullLiteral();
|
|
282
|
+
|
|
283
|
+
// mergeStyles(inheritStyle, staticStyles, inlineStyle)
|
|
284
|
+
return t.callExpression(state.mergeStylesId, [inheritArg, staticArg, inlineArg]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// InheritStyle helpers
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
const inheritStyleMemo = new WeakMap();
|
|
292
|
+
|
|
293
|
+
function getMemoizedInheritStyleExpression(path, t) {
|
|
294
|
+
let currentPath = path;
|
|
295
|
+
let funcPath = null;
|
|
296
|
+
while (currentPath && !funcPath) {
|
|
297
|
+
if (currentPath.isFunction()) {
|
|
298
|
+
funcPath = currentPath;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
currentPath = currentPath.parentPath;
|
|
302
|
+
}
|
|
303
|
+
if (!funcPath) return undefined;
|
|
304
|
+
|
|
305
|
+
if (inheritStyleMemo.has(funcPath.node)) {
|
|
306
|
+
return inheritStyleMemo.get(funcPath.node);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let result;
|
|
310
|
+
const params = funcPath.node.params;
|
|
311
|
+
let parentPath = funcPath.parentPath;
|
|
312
|
+
|
|
313
|
+
if (
|
|
314
|
+
parentPath &&
|
|
315
|
+
parentPath.isCallExpression() &&
|
|
316
|
+
parentPath.node.callee &&
|
|
317
|
+
parentPath.node.callee.type === "Identifier" &&
|
|
318
|
+
["useMemo", "useCallback"].includes(parentPath.node.callee.name)
|
|
319
|
+
) {
|
|
320
|
+
result = undefined;
|
|
321
|
+
} else if (params.length === 0) {
|
|
322
|
+
funcPath.node.params = [t.identifier("props")];
|
|
323
|
+
result = t.memberExpression(t.identifier("props"), t.identifier("inheritStyle"), false, false);
|
|
324
|
+
} else if (params.length > 0) {
|
|
325
|
+
const firstParam = params[0];
|
|
326
|
+
if (t.isIdentifier(firstParam)) {
|
|
327
|
+
result = t.memberExpression(firstParam, t.identifier("inheritStyle"), false, false);
|
|
328
|
+
} else if (t.isObjectPattern(firstParam)) {
|
|
329
|
+
const restElement = firstParam.properties.find((p) => t.isRestElement(p));
|
|
330
|
+
if (restElement) {
|
|
331
|
+
result = t.memberExpression(
|
|
332
|
+
restElement.argument,
|
|
333
|
+
t.identifier("inheritStyle"),
|
|
334
|
+
false,
|
|
335
|
+
false,
|
|
336
|
+
);
|
|
337
|
+
} else {
|
|
338
|
+
const restProp = t.restElement(t.identifier("rest"));
|
|
339
|
+
firstParam.properties.push(restProp);
|
|
340
|
+
result = t.memberExpression(
|
|
341
|
+
t.identifier("rest"),
|
|
342
|
+
t.identifier("inheritStyle"),
|
|
343
|
+
false,
|
|
344
|
+
false,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
inheritStyleMemo.set(funcPath.node, result);
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ============================================================================
|
|
355
|
+
// Style expression generators (for dynamic/fallback cases)
|
|
356
|
+
// ============================================================================
|
|
357
|
+
|
|
358
|
+
export function getStyleExpression(path, state, t) {
|
|
359
|
+
const openingElement = path.node.openingElement;
|
|
360
|
+
const elementName = openingElement.name;
|
|
361
|
+
|
|
362
|
+
const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
|
|
363
|
+
const inheritStyle = openingElement.attributes.find((attr) => attr.name?.name === "inheritStyle");
|
|
364
|
+
const className = openingElement.attributes.find((attr) => attr.name?.name === "className");
|
|
365
|
+
const style = openingElement.attributes.find((attr) => attr.name?.name === "style");
|
|
366
|
+
|
|
367
|
+
return t.callExpression(state.getStyleId, [
|
|
368
|
+
state.stylesheetId,
|
|
369
|
+
t.arrayExpression([
|
|
370
|
+
t.arrayExpression([
|
|
371
|
+
propInheritStyle || t.nullLiteral(),
|
|
372
|
+
(inheritStyle && inheritStyle.value.expression) || t.nullLiteral(),
|
|
373
|
+
]),
|
|
374
|
+
(className &&
|
|
375
|
+
(t.isStringLiteral(className.value)
|
|
376
|
+
? t.stringLiteral(className.value.value)
|
|
377
|
+
: className.value.expression)) ||
|
|
378
|
+
t.nullLiteral(),
|
|
379
|
+
(style && style.value.expression) || t.nullLiteral(),
|
|
380
|
+
t.isJSXIdentifier(elementName)
|
|
381
|
+
? t.stringLiteral(elementName.name)
|
|
382
|
+
: t.isJSXMemberExpression(elementName)
|
|
383
|
+
? t.stringLiteral(`${elementName.object.name}.${elementName.property.name}`)
|
|
384
|
+
: t.stringLiteral("Unknown"),
|
|
385
|
+
]),
|
|
386
|
+
]);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function getInheritStyleExpression(path, state, t) {
|
|
390
|
+
const openingElement = path.node.openingElement;
|
|
391
|
+
const inheritStyle = openingElement.attributes.find((attr) => attr.name?.name === "inheritStyle");
|
|
392
|
+
|
|
393
|
+
const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
|
|
394
|
+
const inheritStyleExpr = inheritStyle?.value?.expression || propInheritStyle;
|
|
395
|
+
|
|
396
|
+
const classNameAttr = openingElement.attributes.find((attr) => attr.name?.name === "className");
|
|
397
|
+
const styleAttr = openingElement.attributes.find((attr) => attr.name?.name === "style");
|
|
398
|
+
|
|
399
|
+
return t.callExpression(state.getInheritStyleId, [
|
|
400
|
+
t.callExpression(state.getStyleId, [
|
|
401
|
+
state.stylesheetId,
|
|
402
|
+
t.arrayExpression([
|
|
403
|
+
inheritStyleExpr || t.nullLiteral(),
|
|
404
|
+
(classNameAttr?.value &&
|
|
405
|
+
(t.isStringLiteral(classNameAttr.value)
|
|
406
|
+
? t.stringLiteral(classNameAttr.value.value)
|
|
407
|
+
: classNameAttr.value.expression)) ||
|
|
408
|
+
t.nullLiteral(),
|
|
409
|
+
styleAttr?.value?.expression || t.nullLiteral(),
|
|
410
|
+
t.isJSXIdentifier(openingElement.name)
|
|
411
|
+
? t.stringLiteral(openingElement.name.name)
|
|
412
|
+
: t.isJSXMemberExpression(openingElement.name)
|
|
413
|
+
? t.stringLiteral(
|
|
414
|
+
`${openingElement.name.object.name}.${openingElement.name.property.name}`,
|
|
415
|
+
)
|
|
416
|
+
: t.stringLiteral("Unknown"),
|
|
417
|
+
]),
|
|
418
|
+
]),
|
|
419
|
+
]);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function getRootInheritStyleExpression(path, t) {
|
|
423
|
+
const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
|
|
424
|
+
return propInheritStyle || t.identifier("undefined");
|
|
425
|
+
}
|
package/src/utils/css.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { transform } from "lightningcss";
|
|
2
|
+
import { camelize } from "./helper";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unwrap @layer and @supports blocks to expose standard CSS rules
|
|
6
|
+
*/
|
|
7
|
+
function flattenBlocks(cssStr) {
|
|
8
|
+
let result = "";
|
|
9
|
+
let i = 0;
|
|
10
|
+
const len = cssStr.length;
|
|
11
|
+
|
|
12
|
+
while (i < len) {
|
|
13
|
+
// Skip comments
|
|
14
|
+
if (cssStr[i] === "/" && cssStr[i + 1] === "*") {
|
|
15
|
+
const commentEnd = cssStr.indexOf("*/", i + 2);
|
|
16
|
+
if (commentEnd === -1) {
|
|
17
|
+
result += cssStr.substring(i);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
result += cssStr.substring(i, commentEnd + 2);
|
|
21
|
+
i = commentEnd + 2;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Skip strings
|
|
26
|
+
if (cssStr[i] === '"' || cssStr[i] === "'") {
|
|
27
|
+
const char = cssStr[i];
|
|
28
|
+
let strEnd = i + 1;
|
|
29
|
+
while (strEnd < len) {
|
|
30
|
+
if (cssStr[strEnd] === "\\") {
|
|
31
|
+
strEnd += 2;
|
|
32
|
+
} else if (cssStr[strEnd] === char) {
|
|
33
|
+
strEnd++;
|
|
34
|
+
break;
|
|
35
|
+
} else {
|
|
36
|
+
strEnd++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
result += cssStr.substring(i, strEnd);
|
|
40
|
+
i = strEnd;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check for at-rules
|
|
45
|
+
if (cssStr[i] === "@") {
|
|
46
|
+
const remaining = cssStr.substring(i);
|
|
47
|
+
const layerMatch = remaining.match(/^@(layer|supports|property)[^{]*\{/i);
|
|
48
|
+
if (layerMatch) {
|
|
49
|
+
const layerType = layerMatch[1].toLowerCase();
|
|
50
|
+
// Skip @layer base entirely because React Native views should not inherit browser base resets (* { border: 0 solid; margin: 0 })
|
|
51
|
+
const isLayerBase = /^@layer\s+base\s*\{/i.test(layerMatch[0]);
|
|
52
|
+
if (layerType === "property" || isLayerBase) {
|
|
53
|
+
let braceCount = 1;
|
|
54
|
+
let j = i + layerMatch[0].length;
|
|
55
|
+
while (j < len && braceCount > 0) {
|
|
56
|
+
if (cssStr[j] === "{") braceCount++;
|
|
57
|
+
else if (cssStr[j] === "}") braceCount--;
|
|
58
|
+
j++;
|
|
59
|
+
}
|
|
60
|
+
i = j;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let braceCount = 1;
|
|
65
|
+
let j = i + layerMatch[0].length;
|
|
66
|
+
let innerContent = "";
|
|
67
|
+
while (j < len && braceCount > 0) {
|
|
68
|
+
if (cssStr[j] === "{") braceCount++;
|
|
69
|
+
else if (cssStr[j] === "}") braceCount--;
|
|
70
|
+
if (braceCount > 0) innerContent += cssStr[j];
|
|
71
|
+
j++;
|
|
72
|
+
}
|
|
73
|
+
result += flattenBlocks(innerContent);
|
|
74
|
+
i = j;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
result += cssStr[i];
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse and lower modern CSS using LightningCSS (Rust-based engine)
|
|
88
|
+
* Handles OKLCH, color-mix, CSS nesting, @layer, and @media range queries
|
|
89
|
+
*/
|
|
90
|
+
export function parseStylesheetWithLightning(rawCss) {
|
|
91
|
+
const transformed = transform({
|
|
92
|
+
filename: "input.css",
|
|
93
|
+
code: Buffer.from(rawCss),
|
|
94
|
+
targets: {
|
|
95
|
+
safari: 14 << 16,
|
|
96
|
+
},
|
|
97
|
+
minify: false,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const cssText = flattenBlocks(transformed.code.toString());
|
|
101
|
+
const rawStylesheet = {};
|
|
102
|
+
|
|
103
|
+
const cleanSelector = (sel) => {
|
|
104
|
+
if (sel.includes(":root")) return [":root"];
|
|
105
|
+
|
|
106
|
+
let s = sel.trim();
|
|
107
|
+
if (!s.startsWith(".")) return [];
|
|
108
|
+
|
|
109
|
+
// Strip leading dot
|
|
110
|
+
s = s.slice(1);
|
|
111
|
+
|
|
112
|
+
// Extract class name (handling escaped chars like active\:scale-95, w-\[48\%\], etc.)
|
|
113
|
+
let classPart = "";
|
|
114
|
+
let i = 0;
|
|
115
|
+
while (i < s.length) {
|
|
116
|
+
if (s[i] === "\\") {
|
|
117
|
+
if (i + 1 < s.length) {
|
|
118
|
+
classPart += s[i + 1];
|
|
119
|
+
i += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (s[i] === ":" || s[i] === " " || s[i] === ">" || s[i] === "~" || s[i] === "+") {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
classPart += s[i];
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!classPart) return [];
|
|
131
|
+
|
|
132
|
+
const names = [classPart];
|
|
133
|
+
|
|
134
|
+
// For variants like disabled:bg-navy-300 or active:opacity-80, also register the base class name
|
|
135
|
+
if (
|
|
136
|
+
classPart.startsWith("disabled:") ||
|
|
137
|
+
classPart.startsWith("active:") ||
|
|
138
|
+
classPart.startsWith("pressed:")
|
|
139
|
+
) {
|
|
140
|
+
const base = classPart.replace(/^(disabled|active|pressed):/, "");
|
|
141
|
+
if (base && !names.includes(base)) {
|
|
142
|
+
names.push(base);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return names;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const parseDeclarations = (bodyText) => {
|
|
150
|
+
const decls = {};
|
|
151
|
+
const parts = bodyText.split(";");
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
const trimmed = part.trim();
|
|
154
|
+
if (!trimmed) continue;
|
|
155
|
+
const colonIdx = trimmed.indexOf(":");
|
|
156
|
+
if (colonIdx === -1) continue;
|
|
157
|
+
const prop = trimmed.slice(0, colonIdx).trim();
|
|
158
|
+
const val = trimmed.slice(colonIdx + 1).trim();
|
|
159
|
+
if (!prop || !val) continue;
|
|
160
|
+
|
|
161
|
+
if (prop.startsWith("--")) {
|
|
162
|
+
decls[prop] = val;
|
|
163
|
+
} else {
|
|
164
|
+
decls[camelize(prop)] = val;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return decls;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// 1. Separate media query blocks
|
|
171
|
+
const mediaBlocks = [];
|
|
172
|
+
const noMediaCss = cssText.replace(/@media\s*([^{]+)\{([\s\S]+?\}\s*)\}/g, (_, query, inner) => {
|
|
173
|
+
mediaBlocks.push({ query: query.trim(), inner });
|
|
174
|
+
return "";
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 2. Parse regular rules
|
|
178
|
+
const ruleRe = /([^{}]+)\{([^{}]+)\}/g;
|
|
179
|
+
let match;
|
|
180
|
+
while ((match = ruleRe.exec(noMediaCss)) !== null) {
|
|
181
|
+
const rawSelectors = match[1].trim();
|
|
182
|
+
const body = match[2].trim();
|
|
183
|
+
if (rawSelectors.startsWith("@")) continue;
|
|
184
|
+
|
|
185
|
+
const selectors = rawSelectors.split(",").map((s) => s.trim());
|
|
186
|
+
const decls = parseDeclarations(body);
|
|
187
|
+
|
|
188
|
+
for (const sel of selectors) {
|
|
189
|
+
if (!sel.startsWith(".") && !sel.includes(":root")) continue;
|
|
190
|
+
const names = cleanSelector(sel);
|
|
191
|
+
for (const name of names) {
|
|
192
|
+
rawStylesheet[name] = { ...rawStylesheet[name], ...decls };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 3. Parse media query rules
|
|
198
|
+
for (const mb of mediaBlocks) {
|
|
199
|
+
let mMatch;
|
|
200
|
+
const mRuleRe = /([^{}]+)\{([^{}]+)\}/g;
|
|
201
|
+
while ((mMatch = mRuleRe.exec(mb.inner)) !== null) {
|
|
202
|
+
const rawSelectors = mMatch[1].trim();
|
|
203
|
+
const body = mMatch[2].trim();
|
|
204
|
+
const selectors = rawSelectors.split(",").map((s) => s.trim());
|
|
205
|
+
const decls = parseDeclarations(body);
|
|
206
|
+
|
|
207
|
+
for (const sel of selectors) {
|
|
208
|
+
if (!sel.startsWith(".") && !sel.includes(":root")) continue;
|
|
209
|
+
const names = cleanSelector(sel);
|
|
210
|
+
for (const name of names) {
|
|
211
|
+
rawStylesheet[name] = {
|
|
212
|
+
...rawStylesheet[name],
|
|
213
|
+
[`@media ${mb.query}`]: decls,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return rawStylesheet;
|
|
221
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "react-native";
|
|
2
|
+
|
|
3
|
+
declare module "react-native" {
|
|
4
|
+
interface ViewProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
interface TextProps {
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
interface ImageProps {
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ScrollViewProps {
|
|
14
|
+
className?: string;
|
|
15
|
+
contentContainerClassName?: string;
|
|
16
|
+
}
|
|
17
|
+
interface FlatListProps<ItemT> {
|
|
18
|
+
className?: string;
|
|
19
|
+
contentContainerClassName?: string;
|
|
20
|
+
columnWrapperClassName?: string;
|
|
21
|
+
}
|
|
22
|
+
interface SectionListProps<ItemT, SectionT> {
|
|
23
|
+
className?: string;
|
|
24
|
+
contentContainerClassName?: string;
|
|
25
|
+
}
|
|
26
|
+
interface PressableProps {
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
interface TextInputProps {
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
interface TouchableOpacityProps {
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
interface TouchableHighlightProps {
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
interface TouchableWithoutFeedbackProps {
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
interface ActivityIndicatorProps {
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
interface SwitchProps {
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
}
|