@colorye/react-native-css 0.2.1

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.
@@ -0,0 +1,287 @@
1
+ import { Appearance, Dimensions, PixelRatio } from "react-native";
2
+ import CssCalc from "./features/css-calc";
3
+ import CssMedia from "./features/css-media";
4
+ import CssTransform from "./features/css-transform";
5
+ import CssVars from "./features/css-vars";
6
+
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+ const INHERIT_PROPERTIES = [
11
+ "color",
12
+ "fontFamily",
13
+ "fontSize",
14
+ "fontStyle",
15
+ "fontWeight",
16
+ "fontVariant",
17
+ "letterSpacing",
18
+ "lineHeight",
19
+ "textAlign",
20
+ "textTransform",
21
+ ];
22
+
23
+ // ============================================================================
24
+ // Singleton Helper Instances
25
+ // ============================================================================
26
+ const vars = new CssVars();
27
+ const transform = new CssTransform();
28
+ const calc = new CssCalc();
29
+ const media = new CssMedia();
30
+
31
+ // ============================================================================
32
+ // Cached Dimensions and Appearance
33
+ // ============================================================================
34
+ let cachedDimensions = null;
35
+ let cachedColorScheme = null;
36
+ let TRANSFORM_CACHE = {};
37
+ let currentCacheKey = null;
38
+
39
+ function getDimensions() {
40
+ if (!cachedDimensions) {
41
+ cachedDimensions = Dimensions.get("window");
42
+ }
43
+ return cachedDimensions;
44
+ }
45
+
46
+ function getColorScheme() {
47
+ if (cachedColorScheme === null) {
48
+ cachedColorScheme = Appearance.getColorScheme();
49
+ }
50
+ return cachedColorScheme;
51
+ }
52
+
53
+ function getCacheKey() {
54
+ const { width, height } = getDimensions();
55
+ const colorScheme = getColorScheme();
56
+ return `${width}x${height}:${colorScheme}`;
57
+ }
58
+
59
+ function invalidateCache() {
60
+ cachedDimensions = null;
61
+ cachedColorScheme = null;
62
+ TRANSFORM_CACHE = {};
63
+ currentCacheKey = null;
64
+ }
65
+
66
+ // Event listeners for cache invalidation
67
+ Dimensions.addEventListener("change", invalidateCache);
68
+ Appearance.addChangeListener(invalidateCache);
69
+
70
+ // ============================================================================
71
+ // Flatten Style
72
+ // ============================================================================
73
+ function getFlattenStyle(declarations) {
74
+ if (!Array.isArray(declarations)) {
75
+ return declarations;
76
+ }
77
+
78
+ const result = {};
79
+
80
+ function merge(item) {
81
+ if (!item) return;
82
+ if (Array.isArray(item)) {
83
+ for (let i = 0; i < item.length; i++) {
84
+ merge(item[i]);
85
+ }
86
+ } else {
87
+ Object.assign(result, item);
88
+ }
89
+ }
90
+
91
+ for (let i = 0; i < declarations.length; i++) {
92
+ merge(declarations[i]);
93
+ }
94
+
95
+ return Object.keys(result).length > 0 ? result : undefined;
96
+ }
97
+
98
+ // ============================================================================
99
+ // Main Transform Function
100
+ // ============================================================================
101
+ function transformStyles(stylesheet, classNames) {
102
+ if (!stylesheet || !classNames) return undefined;
103
+
104
+ const { width, height } = getDimensions();
105
+ const colorScheme = getColorScheme();
106
+
107
+ // Check cache validity
108
+ const cacheKey = getCacheKey();
109
+ if (cacheKey !== currentCacheKey) {
110
+ TRANSFORM_CACHE = {};
111
+ currentCacheKey = cacheKey;
112
+ }
113
+
114
+ const transformedDeclarations = classNames.split(" ").map((className) => {
115
+ if (!className) return null;
116
+
117
+ // Check cache
118
+ if (TRANSFORM_CACHE[className] !== undefined) {
119
+ return TRANSFORM_CACHE[className];
120
+ }
121
+
122
+ const declaration = stylesheet[className];
123
+ const globalDeclaration = stylesheet[":root"];
124
+
125
+ if (!declaration && !globalDeclaration) {
126
+ TRANSFORM_CACHE[className] = null;
127
+ return null;
128
+ }
129
+
130
+ // Reset vars helper
131
+ vars.global = {};
132
+ vars.data = {};
133
+
134
+ if (globalDeclaration) {
135
+ // Handle global with potential _static/_dynamic format
136
+ const globalRaw = globalDeclaration._static
137
+ ? { ...globalDeclaration._static, ...globalDeclaration._dynamic }
138
+ : globalDeclaration;
139
+ vars.setGlobal(globalRaw, { width, height });
140
+ }
141
+
142
+ if (declaration) {
143
+ // Handle declaration with potential _static/_dynamic format
144
+ const declRaw = declaration._static
145
+ ? { ...declaration._static, ...declaration._dynamic }
146
+ : declaration;
147
+ vars.set(className, declRaw, { width, height });
148
+ }
149
+
150
+ // Get static and dynamic parts
151
+ const staticPart = declaration?._static || {};
152
+ const dynamicPart = declaration?._dynamic || (declaration?._static ? {} : declaration) || {};
153
+
154
+ // Start with pre-computed static styles
155
+ let results = { ...staticPart };
156
+
157
+ // Process dynamic properties
158
+ const transformDynamic = (currentSelector, decl) => {
159
+ for (let property in decl) {
160
+ if (vars.isVar(property)) continue;
161
+
162
+ let value = decl[property];
163
+
164
+ // Handle media queries
165
+ const [isMedia, matchedMedia] = media.match(property, { width, height, colorScheme });
166
+ if (isMedia) {
167
+ if (matchedMedia) {
168
+ vars.set(property, value);
169
+ // Media query value might have _static/_dynamic too
170
+ const mediaStatic = value?._static || {};
171
+ const mediaDynamic = value?._dynamic || (value?._static ? {} : value) || {};
172
+ Object.assign(results, mediaStatic);
173
+ transformDynamic(property, mediaDynamic);
174
+ }
175
+ continue;
176
+ }
177
+
178
+ // Transform the value
179
+ [property, value] = transform.transformUnsafeValue(property, value);
180
+ if (!property) continue;
181
+
182
+ value = vars.injectVar(currentSelector, value);
183
+ value = transform.transformUnsupportedUnit(value);
184
+ value = transform.transformViewportUnit(value, { width, height });
185
+ value = transform.removeUnit(value);
186
+ value = calc.calc(value);
187
+ value = calc.calcColor(value);
188
+ value = transform.transformFontScaling(property, value, {
189
+ width,
190
+ height,
191
+ roundFn: PixelRatio.roundToNearestPixel,
192
+ });
193
+
194
+ if (value === undefined) continue;
195
+
196
+ const transformed = transform.transform(property, value, { width, height });
197
+ if (transformed) {
198
+ Object.assign(results, transformed);
199
+ }
200
+ }
201
+ };
202
+
203
+ transformDynamic(className, dynamicPart);
204
+
205
+ TRANSFORM_CACHE[className] = results;
206
+ return results;
207
+ });
208
+
209
+ return getFlattenStyle(transformedDeclarations);
210
+ }
211
+
212
+ // ============================================================================
213
+ // Inherit Style
214
+ // ============================================================================
215
+ function getInheritStyle(declarations) {
216
+ if (!declarations) return undefined;
217
+
218
+ const inheritDeclarations = {};
219
+ for (const key of INHERIT_PROPERTIES) {
220
+ if (declarations[key] !== undefined) {
221
+ inheritDeclarations[key] = declarations[key];
222
+ }
223
+ }
224
+
225
+ return Object.keys(inheritDeclarations).length > 0 ? inheritDeclarations : undefined;
226
+ }
227
+
228
+ // ============================================================================
229
+ // Main Entry Point
230
+ // ============================================================================
231
+ function getStyle(stylesheet, [inheritStyle, className, style]) {
232
+ return getFlattenStyle([
233
+ getInheritStyle(getFlattenStyle(inheritStyle)),
234
+ transformStyles(stylesheet, className),
235
+ style,
236
+ ]);
237
+ }
238
+
239
+ // ============================================================================
240
+ // Lightweight Merge for Static Styles
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Lightweight merge function for static styles with inheritStyle
245
+ * Much cheaper than full getStyle() - just extracts inherited props and merges
246
+ */
247
+ function mergeStyles(inheritStyle, staticStyles, inlineStyle) {
248
+ // Fast path: no inheritStyle
249
+ if (!inheritStyle && !inlineStyle) {
250
+ return staticStyles;
251
+ }
252
+
253
+ // Extract inherited properties from inheritStyle
254
+ let inherited;
255
+ if (inheritStyle) {
256
+ const flatInherit = getFlattenStyle(inheritStyle);
257
+ if (flatInherit) {
258
+ inherited = {};
259
+ for (const key of INHERIT_PROPERTIES) {
260
+ if (flatInherit[key] !== undefined) {
261
+ inherited[key] = flatInherit[key];
262
+ }
263
+ }
264
+ if (Object.keys(inherited).length === 0) {
265
+ inherited = undefined;
266
+ }
267
+ }
268
+ }
269
+
270
+ // Merge: inheritStyle (lowest) -> staticStyles -> inlineStyle (highest)
271
+ if (!inherited && !inlineStyle) {
272
+ return staticStyles;
273
+ }
274
+
275
+ const result = {};
276
+ if (inherited) Object.assign(result, inherited);
277
+ if (staticStyles) Object.assign(result, staticStyles);
278
+ if (inlineStyle) Object.assign(result, inlineStyle);
279
+
280
+ return Object.keys(result).length > 0 ? result : undefined;
281
+ }
282
+
283
+ export default {
284
+ getStyle,
285
+ getInheritStyle,
286
+ mergeStyles,
287
+ };
@@ -0,0 +1,354 @@
1
+ export function isImportOrRequire(statement) {
2
+ return (
3
+ statement.isImportDeclaration() ||
4
+ statement.node?.declarations?.[0]?.init?.callee?.name === "require"
5
+ );
6
+ }
7
+
8
+ export function isFragmentElement(t, elementName) {
9
+ return (
10
+ (t.isJSXIdentifier(elementName) &&
11
+ (elementName.name === "Fragment" || elementName.name === "React.Fragment")) ||
12
+ (t.isJSXMemberExpression(elementName) &&
13
+ elementName.object.name === "React" &&
14
+ elementName.property.name === "Fragment") ||
15
+ t.isJSXIdentifier(elementName, { name: "" }) // JSX shorthand fragment
16
+ );
17
+ }
18
+
19
+ export function isRootLevelJSXElement(path) {
20
+ let currentPath = path;
21
+
22
+ while (currentPath.parentPath) {
23
+ const parent = currentPath.parentPath;
24
+
25
+ if (parent.isReturnStatement()) return true;
26
+ if (parent.isArrowFunctionExpression() && parent.node.body === currentPath.node) return true;
27
+ if (parent.isJSXElement() && parent !== path) return false;
28
+
29
+ if (
30
+ parent.isConditionalExpression() ||
31
+ parent.isLogicalExpression() ||
32
+ parent.isParenthesizedExpression()
33
+ ) {
34
+ currentPath = parent;
35
+ continue;
36
+ }
37
+
38
+ currentPath = parent;
39
+ }
40
+
41
+ return false;
42
+ }
43
+
44
+ // ============================================================================
45
+ // Static style detection helpers
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Check if className is a static string literal
50
+ */
51
+ export function isStaticClassName(t, classNameAttr) {
52
+ if (!classNameAttr) return false;
53
+ if (t.isStringLiteral(classNameAttr.value)) return true;
54
+ if (
55
+ t.isJSXExpressionContainer(classNameAttr.value) &&
56
+ t.isStringLiteral(classNameAttr.value.expression)
57
+ ) {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+
63
+ /**
64
+ * Get static className string value
65
+ */
66
+ export function getStaticClassNameValue(t, classNameAttr) {
67
+ if (!classNameAttr) return null;
68
+ if (t.isStringLiteral(classNameAttr.value)) return classNameAttr.value.value;
69
+ if (
70
+ t.isJSXExpressionContainer(classNameAttr.value) &&
71
+ t.isStringLiteral(classNameAttr.value.expression)
72
+ ) {
73
+ return classNameAttr.value.expression.value;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Check if a class is fully static (no _dynamic property)
80
+ */
81
+ export function isClassStatic(stylesheet, className) {
82
+ const decl = stylesheet[className];
83
+ if (!decl) return true; // Non-existent class is "static" (no-op)
84
+ if (decl._dynamic && Object.keys(decl._dynamic).length > 0) return false;
85
+ if (decl._static) return true; // Has _static wrapper
86
+ // Check if it's a plain object (fully static, no wrapper)
87
+ return typeof decl === "object" && !decl._dynamic;
88
+ }
89
+
90
+ /**
91
+ * Get static styles from a class declaration
92
+ */
93
+ export function getStaticStylesFromClass(stylesheet, className) {
94
+ const decl = stylesheet[className];
95
+ if (!decl) return {};
96
+
97
+ // If has _static wrapper, use that
98
+ if (decl._static) return { ...decl._static };
99
+
100
+ // If plain object (fully static), use directly
101
+ if (typeof decl === "object" && !decl._dynamic) {
102
+ // Filter out any metadata keys
103
+ const result = {};
104
+ for (const [key, value] of Object.entries(decl)) {
105
+ if (!key.startsWith("_") && !key.startsWith("@media")) {
106
+ result[key] = value;
107
+ }
108
+ }
109
+ return result;
110
+ }
111
+
112
+ return {};
113
+ }
114
+
115
+ /**
116
+ * Check if all classes in className string are static
117
+ */
118
+ export function areAllClassesStatic(stylesheet, classNameValue) {
119
+ if (!classNameValue || !stylesheet) return false;
120
+ const classes = classNameValue.trim().split(/\s+/).filter(Boolean);
121
+ return classes.every((cls) => isClassStatic(stylesheet, cls));
122
+ }
123
+
124
+ /**
125
+ * Compute merged static styles from className string
126
+ */
127
+ export function computeStaticStyles(stylesheet, classNameValue) {
128
+ if (!classNameValue || !stylesheet) return {};
129
+
130
+ const classes = classNameValue.trim().split(/\s+/).filter(Boolean);
131
+ let result = {};
132
+
133
+ for (const cls of classes) {
134
+ const styles = getStaticStylesFromClass(stylesheet, cls);
135
+ result = { ...result, ...styles };
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ /**
142
+ * Convert JavaScript object to Babel AST
143
+ */
144
+ export function objectToAST(t, obj) {
145
+ if (obj === null || obj === undefined) return t.nullLiteral();
146
+
147
+ if (Array.isArray(obj)) {
148
+ return t.arrayExpression(obj.map((item) => objectToAST(t, item)));
149
+ }
150
+
151
+ if (typeof obj === "object") {
152
+ return t.objectExpression(
153
+ Object.entries(obj).map(([key, value]) =>
154
+ t.objectProperty(
155
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? t.identifier(key) : t.stringLiteral(key),
156
+ objectToAST(t, value),
157
+ ),
158
+ ),
159
+ );
160
+ }
161
+
162
+ if (typeof obj === "string") return t.stringLiteral(obj);
163
+ if (typeof obj === "number") return t.numericLiteral(obj);
164
+ if (typeof obj === "boolean") return t.booleanLiteral(obj);
165
+
166
+ return t.nullLiteral();
167
+ }
168
+
169
+ /**
170
+ * Try to get static style info for an element
171
+ * Returns { staticStyles, hasInheritStyle, inlineStyleExpr } or null if not static
172
+ */
173
+ export function tryGetStaticStyleInfo(path, state, t) {
174
+ const openingElement = path.node.openingElement;
175
+
176
+ const classNameAttr = openingElement.attributes.find(
177
+ (attr) => t.isJSXAttribute(attr) && attr.name?.name === "className",
178
+ );
179
+
180
+ // Check if className is static
181
+ if (!isStaticClassName(t, classNameAttr)) return null;
182
+
183
+ // Get stylesheet
184
+ const stylesheet = state.stylesheetData;
185
+ if (!stylesheet) return null;
186
+
187
+ const classNameValue = getStaticClassNameValue(t, classNameAttr);
188
+ if (!classNameValue) return null;
189
+
190
+ // Check if all classes are fully static
191
+ if (!areAllClassesStatic(stylesheet, classNameValue)) return null;
192
+
193
+ // Compute static styles from className
194
+ const staticStyles = computeStaticStyles(stylesheet, classNameValue);
195
+
196
+ // Get inheritStyle attribute (for mergeStyles call)
197
+ const inheritStyleAttr = openingElement.attributes.find(
198
+ (attr) => t.isJSXAttribute(attr) && attr.name?.name === "inheritStyle",
199
+ );
200
+
201
+ // Get style attribute
202
+ const styleAttr = openingElement.attributes.find(
203
+ (attr) => t.isJSXAttribute(attr) && attr.name?.name === "style",
204
+ );
205
+
206
+ return {
207
+ staticStyles,
208
+ inheritStyleAttr,
209
+ styleAttr,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Generate mergeStyles(inheritStyle, staticStyles, inlineStyle) expression
215
+ */
216
+ export function getStaticMergeExpression(path, state, t, staticInfo) {
217
+ const { staticStyles, inheritStyleAttr, styleAttr } = staticInfo;
218
+
219
+ // Get inheritStyle expression
220
+ const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
221
+ const inheritStyleExpr = inheritStyleAttr?.value?.expression;
222
+
223
+ // Build inheritStyle array: [props.inheritStyle, explicitInheritStyle]
224
+ let inheritArg;
225
+ if (propInheritStyle || inheritStyleExpr) {
226
+ inheritArg = t.arrayExpression([
227
+ propInheritStyle || t.nullLiteral(),
228
+ inheritStyleExpr || t.nullLiteral(),
229
+ ]);
230
+ } else {
231
+ inheritArg = t.nullLiteral();
232
+ }
233
+
234
+ // Static styles as inline object
235
+ const staticArg =
236
+ Object.keys(staticStyles).length > 0 ? objectToAST(t, staticStyles) : t.nullLiteral();
237
+
238
+ // Inline style expression
239
+ const inlineArg = styleAttr?.value?.expression || t.nullLiteral();
240
+
241
+ // mergeStyles(inheritStyle, staticStyles, inlineStyle)
242
+ return t.callExpression(state.mergeStylesId, [inheritArg, staticArg, inlineArg]);
243
+ }
244
+
245
+ // ============================================================================
246
+ // InheritStyle helpers
247
+ // ============================================================================
248
+
249
+ const inheritStyleMemo = new WeakMap();
250
+
251
+ function getMemoizedInheritStyleExpression(path, t) {
252
+ let currentPath = path;
253
+ let funcPath = null;
254
+ while (currentPath && !funcPath) {
255
+ if (currentPath.isFunction()) {
256
+ funcPath = currentPath;
257
+ break;
258
+ }
259
+ currentPath = currentPath.parentPath;
260
+ }
261
+ if (!funcPath) return undefined;
262
+
263
+ if (inheritStyleMemo.has(funcPath.node)) {
264
+ return inheritStyleMemo.get(funcPath.node);
265
+ }
266
+
267
+ let result;
268
+ const params = funcPath.node.params;
269
+ let parentPath = funcPath.parentPath;
270
+
271
+ if (
272
+ parentPath &&
273
+ parentPath.isCallExpression() &&
274
+ parentPath.node.callee &&
275
+ parentPath.node.callee.type === "Identifier" &&
276
+ ["useMemo", "useCallback"].includes(parentPath.node.callee.name)
277
+ ) {
278
+ result = undefined;
279
+ } else if (params.length === 0) {
280
+ funcPath.node.params = [t.identifier("props")];
281
+ result = t.memberExpression(t.identifier("props"), t.identifier("inheritStyle"), false, false);
282
+ } else if (params.length > 0) {
283
+ const firstParam = params[0];
284
+ if (t.isIdentifier(firstParam)) {
285
+ result = t.memberExpression(firstParam, t.identifier("inheritStyle"), false, false);
286
+ } else if (t.isObjectPattern(firstParam)) {
287
+ const restElement = firstParam.properties.find((p) => t.isRestElement(p));
288
+ if (restElement) {
289
+ result = t.memberExpression(
290
+ restElement.argument,
291
+ t.identifier("inheritStyle"),
292
+ false,
293
+ false,
294
+ );
295
+ } else {
296
+ const restProp = t.restElement(t.identifier("rest"));
297
+ firstParam.properties.push(restProp);
298
+ result = t.memberExpression(
299
+ t.identifier("rest"),
300
+ t.identifier("inheritStyle"),
301
+ false,
302
+ false,
303
+ );
304
+ }
305
+ }
306
+ }
307
+
308
+ inheritStyleMemo.set(funcPath.node, result);
309
+ return result;
310
+ }
311
+
312
+ // ============================================================================
313
+ // Style expression generators (for dynamic/fallback cases)
314
+ // ============================================================================
315
+
316
+ export function getStyleExpression(path, state, t) {
317
+ const openingElement = path.node.openingElement;
318
+ const elementName = openingElement.name;
319
+
320
+ const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
321
+ const inheritStyle = openingElement.attributes.find((attr) => attr.name?.name === "inheritStyle");
322
+ const className = openingElement.attributes.find((attr) => attr.name?.name === "className");
323
+ const style = openingElement.attributes.find((attr) => attr.name?.name === "style");
324
+
325
+ return t.callExpression(state.getStyleId, [
326
+ state.stylesheetId,
327
+ t.arrayExpression([
328
+ t.arrayExpression([
329
+ propInheritStyle || t.nullLiteral(),
330
+ (inheritStyle && inheritStyle.value.expression) || t.nullLiteral(),
331
+ ]),
332
+ (className &&
333
+ (t.isStringLiteral(className.value)
334
+ ? t.stringLiteral(className.value.value)
335
+ : className.value.expression)) ||
336
+ t.nullLiteral(),
337
+ (style && style.value.expression) || t.nullLiteral(),
338
+ t.isJSXIdentifier(elementName)
339
+ ? t.stringLiteral(elementName.name)
340
+ : t.isJSXMemberExpression(elementName)
341
+ ? t.stringLiteral(`${elementName.object.name}.${elementName.property.name}`)
342
+ : t.stringLiteral("Unknown"),
343
+ ]),
344
+ ]);
345
+ }
346
+
347
+ export function getInheritStyleExpression(path, state, t) {
348
+ return t.callExpression(state.getInheritStyleId, [getStyleExpression(path, state, t)]);
349
+ }
350
+
351
+ export function getRootInheritStyleExpression(path, t) {
352
+ const propInheritStyle = getMemoizedInheritStyleExpression(path, t);
353
+ return propInheritStyle || t.identifier("undefined");
354
+ }