@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/src/index.js ADDED
@@ -0,0 +1,29 @@
1
+ export {
2
+ GroupContext,
3
+ InheritContext,
4
+ cssInterop,
5
+ getGlobalStylesheet,
6
+ remapProps,
7
+ setGlobalStylesheet,
8
+ } from "./interop.js";
9
+ export { default as Runtime } from "./transformer-runtime.js";
10
+
11
+ import {
12
+ GroupContext,
13
+ InheritContext,
14
+ cssInterop,
15
+ getGlobalStylesheet,
16
+ remapProps,
17
+ setGlobalStylesheet,
18
+ } from "./interop.js";
19
+ import Runtime from "./transformer-runtime.js";
20
+
21
+ export default {
22
+ cssInterop,
23
+ remapProps,
24
+ setGlobalStylesheet,
25
+ getGlobalStylesheet,
26
+ GroupContext,
27
+ InheritContext,
28
+ Runtime,
29
+ };
package/src/interop.js ADDED
@@ -0,0 +1,269 @@
1
+ import React, { createContext, forwardRef, useContext } from "react";
2
+ import Runtime from "./transformer-runtime.js";
3
+
4
+ export const InheritContext = createContext(undefined);
5
+ export const GroupContext = createContext({ pressed: false, hovered: false, focus: false });
6
+
7
+ let globalStylesheet = {};
8
+
9
+ try {
10
+ const raw = require("./exported-stylesheet.json");
11
+ let current = raw;
12
+ while (current?.default && typeof current.default === "object" && !current[":root"]) {
13
+ current = current.default;
14
+ }
15
+ globalStylesheet = current || {};
16
+ } catch {
17
+ // Fallback to empty if not yet compiled
18
+ }
19
+
20
+ export function setGlobalStylesheet(sheet) {
21
+ if (sheet && typeof sheet === "object") {
22
+ let current = sheet;
23
+ while (current?.default && typeof current.default === "object" && !current[":root"]) {
24
+ current = current.default;
25
+ }
26
+ globalStylesheet = current;
27
+ Runtime.clearCache?.();
28
+ }
29
+ }
30
+
31
+ export function getGlobalStylesheet() {
32
+ return globalStylesheet;
33
+ }
34
+
35
+ /**
36
+ * cssInterop(Component, mapping)
37
+ *
38
+ * Wraps any React Native or third-party component to support className mapping.
39
+ *
40
+ * @example
41
+ * cssInterop(FlashList, {
42
+ * className: "style",
43
+ * contentContainerClassName: "contentContainerStyle",
44
+ * });
45
+ *
46
+ * @param {React.ComponentType} Component
47
+ * @param {Record<string, string>} mapping Mapping of class props to style props
48
+ * @returns {React.ForwardRefExoticComponent}
49
+ */
50
+ export function cssInterop(Component, mapping = { className: "style" }) {
51
+ const InteropComponent = forwardRef((props, ref) => {
52
+ const parentInherit = useContext(InheritContext);
53
+ const parentGroup = useContext(GroupContext);
54
+ const inheritStyle = props.inheritStyle || parentInherit;
55
+ const [groupPressed, setGroupPressed] = React.useState(false);
56
+
57
+ const nextProps = { ...props };
58
+ if (ref) {
59
+ nextProps.ref = ref;
60
+ }
61
+
62
+ const sheet = getGlobalStylesheet();
63
+ let currentInherit = parentInherit;
64
+ let isGroupProvider = false;
65
+
66
+ // Check if this component acts as a group container
67
+ for (const classProp of Object.keys(mapping)) {
68
+ const val = props[classProp];
69
+ if (typeof val === "string" && (val === "group" || val.split(/\s+/).includes("group"))) {
70
+ isGroupProvider = true;
71
+ break;
72
+ }
73
+ }
74
+
75
+ if (isGroupProvider) {
76
+ const originalOnPressIn = props.onPressIn;
77
+ const originalOnPressOut = props.onPressOut;
78
+ nextProps.onPressIn = (e) => {
79
+ setGroupPressed(true);
80
+ originalOnPressIn?.(e);
81
+ };
82
+ nextProps.onPressOut = (e) => {
83
+ setGroupPressed(false);
84
+ originalOnPressOut?.(e);
85
+ };
86
+ }
87
+
88
+ for (const [classProp, styleProp] of Object.entries(mapping)) {
89
+ const classValue = props[classProp];
90
+ const hasClass = typeof classValue === "string" && classValue.length > 0;
91
+ const isPrimaryStyle = styleProp === "style";
92
+ const hasInherit = isPrimaryStyle && Boolean(inheritStyle);
93
+
94
+ if (hasClass) {
95
+ const hasActiveVariants = /(^|\s)(active|pressed):/.test(classValue);
96
+ const hasDisabledVariants = /(^|\s)disabled:/.test(classValue);
97
+ const hasGroupActiveVariants = /(^|\s)group-(active|pressed):/.test(classValue);
98
+
99
+ const isDisabled = Boolean(props.disabled || props.accessibilityState?.disabled);
100
+
101
+ if ((hasActiveVariants || hasDisabledVariants || hasGroupActiveVariants) && isPrimaryStyle) {
102
+ const classes = classValue.trim().split(/\s+/);
103
+ const normalClasses = [];
104
+ const activeClasses = [];
105
+ const disabledClasses = [];
106
+ const groupActiveClasses = [];
107
+
108
+ for (const cls of classes) {
109
+ if (cls.startsWith("active:") || cls.startsWith("pressed:")) {
110
+ const baseCls = cls.replace(/^(active|pressed):/, "");
111
+ activeClasses.push(baseCls);
112
+ } else if (cls.startsWith("disabled:")) {
113
+ const baseCls = cls.replace(/^disabled:/, "");
114
+ disabledClasses.push(baseCls);
115
+ } else if (cls.startsWith("group-active:") || cls.startsWith("group-pressed:")) {
116
+ const baseCls = cls.replace(/^group-(active|pressed):/, "");
117
+ groupActiveClasses.push(baseCls);
118
+ } else {
119
+ normalClasses.push(cls);
120
+ }
121
+ }
122
+
123
+ const normalStyle = Runtime.getStyle(sheet, [
124
+ hasInherit ? inheritStyle : undefined,
125
+ normalClasses.join(" "),
126
+ typeof props[styleProp] === "object" ? props[styleProp] : undefined,
127
+ ]);
128
+
129
+ const activeStyle = activeClasses.length > 0
130
+ ? Runtime.getStyle(sheet, [undefined, activeClasses.join(" "), undefined])
131
+ : undefined;
132
+
133
+ const disabledStyle = disabledClasses.length > 0
134
+ ? Runtime.getStyle(sheet, [undefined, disabledClasses.join(" "), undefined])
135
+ : undefined;
136
+
137
+ const groupActiveStyle = (hasGroupActiveVariants && parentGroup.pressed && groupActiveClasses.length > 0)
138
+ ? Runtime.getStyle(sheet, [undefined, groupActiveClasses.join(" "), undefined])
139
+ : undefined;
140
+
141
+ if (hasActiveVariants || typeof props[styleProp] === "function") {
142
+ nextProps[styleProp] = (state) => {
143
+ const isPressed = (state && state.pressed) || false;
144
+ const userStyle = typeof props[styleProp] === "function" ? props[styleProp](state) : null;
145
+ const currentStyles = [normalStyle, groupActiveStyle];
146
+
147
+ if (isPressed && !isDisabled && activeStyle) {
148
+ currentStyles.push(activeStyle);
149
+ }
150
+ if (isDisabled && disabledStyle) {
151
+ currentStyles.push(disabledStyle);
152
+ }
153
+ if (userStyle) {
154
+ currentStyles.push(userStyle);
155
+ }
156
+
157
+ return currentStyles.filter(Boolean);
158
+ };
159
+ } else {
160
+ const currentStyles = [
161
+ normalStyle,
162
+ groupActiveStyle,
163
+ isDisabled ? disabledStyle : undefined,
164
+ typeof props[styleProp] === "object" ? props[styleProp] : undefined,
165
+ ].filter(Boolean);
166
+ nextProps[styleProp] =
167
+ currentStyles.length === 1
168
+ ? currentStyles[0]
169
+ : currentStyles.length > 1
170
+ ? currentStyles
171
+ : undefined;
172
+ }
173
+
174
+ const inheritable = Runtime.getInheritStyle(normalStyle);
175
+ if (inheritable) {
176
+ currentInherit = parentInherit
177
+ ? { ...parentInherit, ...inheritable }
178
+ : inheritable;
179
+ }
180
+ } else {
181
+ // Dynamic className present - runtime resolution
182
+ const computedStyle = Runtime.getStyle(sheet, [
183
+ hasInherit ? inheritStyle : undefined,
184
+ classValue,
185
+ props[styleProp],
186
+ ]);
187
+
188
+ if (computedStyle !== undefined) {
189
+ nextProps[styleProp] = computedStyle;
190
+
191
+ if (isPrimaryStyle) {
192
+ const inheritable = Runtime.getInheritStyle(computedStyle);
193
+ if (inheritable) {
194
+ currentInherit = parentInherit
195
+ ? { ...parentInherit, ...inheritable }
196
+ : inheritable;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ delete nextProps[classProp];
202
+ } else if (isPrimaryStyle) {
203
+ // Static inlined style or plain style prop with inheritance
204
+ const ownStyle = props[styleProp];
205
+
206
+ if (typeof ownStyle === "function") {
207
+ if (hasInherit) {
208
+ nextProps[styleProp] = (state) => {
209
+ const res = ownStyle(state);
210
+ return [inheritStyle, res];
211
+ };
212
+ } else {
213
+ nextProps[styleProp] = ownStyle;
214
+ }
215
+ } else {
216
+ const flatOwnStyle = ownStyle ? Runtime.getFlattenStyle(ownStyle) : undefined;
217
+
218
+ if (hasInherit) {
219
+ const merged = Runtime.mergeStyles(inheritStyle, flatOwnStyle, undefined);
220
+ if (merged !== undefined) {
221
+ nextProps[styleProp] = merged;
222
+ }
223
+ }
224
+
225
+ // Inheritable styles come from either the component's own style or inherited from parent
226
+ const combinedForInherit = hasInherit
227
+ ? Runtime.mergeStyles(inheritStyle, flatOwnStyle, undefined)
228
+ : flatOwnStyle;
229
+ const ownInheritable = Runtime.getInheritStyle(combinedForInherit);
230
+
231
+ if (ownInheritable) {
232
+ currentInherit = parentInherit
233
+ ? { ...parentInherit, ...ownInheritable }
234
+ : ownInheritable;
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ delete nextProps.inheritStyle;
241
+
242
+ let element = React.createElement(Component, nextProps);
243
+
244
+ if (isGroupProvider) {
245
+ element = React.createElement(
246
+ GroupContext.Provider,
247
+ { value: { ...parentGroup, pressed: groupPressed } },
248
+ element,
249
+ );
250
+ }
251
+
252
+ if (currentInherit && currentInherit !== parentInherit) {
253
+ return React.createElement(InheritContext.Provider, { value: currentInherit }, element);
254
+ }
255
+
256
+ return element;
257
+ });
258
+
259
+ const name = Component.displayName || Component.name || "Component";
260
+ InteropComponent.displayName = `CssInterop(${name})`;
261
+
262
+ return InteropComponent;
263
+ }
264
+
265
+ export function remapProps(Component, mapping) {
266
+ return cssInterop(Component, mapping);
267
+ }
268
+
269
+ export default cssInterop;
@@ -0,0 +1,241 @@
1
+ let RN = {};
2
+ try {
3
+ RN = require("react-native");
4
+ } catch {
5
+ // Safe in Node/Babel build environment
6
+ }
7
+
8
+ const Appearance = RN.Appearance || { getColorScheme: () => "light" };
9
+ const Dimensions = RN.Dimensions || { get: () => ({ width: 375, height: 812 }) };
10
+
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+ const INHERIT_PROPERTIES = [
15
+ "color",
16
+ "fontFamily",
17
+ "fontSize",
18
+ "fontStyle",
19
+ "fontWeight",
20
+ "fontVariant",
21
+ "letterSpacing",
22
+ "lineHeight",
23
+ "textAlign",
24
+ "textTransform",
25
+ ];
26
+
27
+ // ============================================================================
28
+ // Cached Dimensions and Appearance
29
+ // ============================================================================
30
+ let cachedDimensions = null;
31
+ let cachedColorScheme = null;
32
+ let TRANSFORM_CACHE = {};
33
+ let currentCacheKey = null;
34
+
35
+ function getDimensions() {
36
+ if (!cachedDimensions) {
37
+ try {
38
+ cachedDimensions = Dimensions?.get?.("window") || { width: 375, height: 812 };
39
+ } catch {
40
+ cachedDimensions = { width: 375, height: 812 };
41
+ }
42
+ }
43
+ return cachedDimensions;
44
+ }
45
+
46
+ function getColorScheme() {
47
+ if (cachedColorScheme === null) {
48
+ try {
49
+ cachedColorScheme = Appearance?.getColorScheme?.() || "light";
50
+ } catch {
51
+ cachedColorScheme = "light";
52
+ }
53
+ }
54
+ return cachedColorScheme;
55
+ }
56
+
57
+ function getCacheKey() {
58
+ const { width, height } = getDimensions();
59
+ const colorScheme = getColorScheme();
60
+ return `${width}x${height}:${colorScheme}`;
61
+ }
62
+
63
+ function invalidateCache() {
64
+ cachedDimensions = null;
65
+ cachedColorScheme = null;
66
+ TRANSFORM_CACHE = {};
67
+ currentCacheKey = null;
68
+ }
69
+
70
+ function clearCache() {
71
+ TRANSFORM_CACHE = {};
72
+ currentCacheKey = null;
73
+ }
74
+
75
+ try {
76
+ Dimensions?.addEventListener?.("change", invalidateCache);
77
+ Appearance?.addChangeListener?.(invalidateCache);
78
+ } catch {}
79
+
80
+ // ============================================================================
81
+ // Flatten Style
82
+ // ============================================================================
83
+ function getFlattenStyle(declarations) {
84
+ if (!Array.isArray(declarations)) {
85
+ return declarations;
86
+ }
87
+
88
+ const result = {};
89
+
90
+ function merge(item) {
91
+ if (!item) return;
92
+ if (Array.isArray(item)) {
93
+ for (let i = 0; i < item.length; i++) {
94
+ merge(item[i]);
95
+ }
96
+ } else {
97
+ Object.assign(result, item);
98
+ }
99
+ }
100
+
101
+ for (let i = 0; i < declarations.length; i++) {
102
+ merge(declarations[i]);
103
+ }
104
+
105
+ const borderStyles = ["borderBottomStyle", "borderTopStyle", "borderLeftStyle", "borderRightStyle"];
106
+ for (const bs of borderStyles) {
107
+ if (result[bs] !== undefined) {
108
+ if (result.borderStyle === undefined) {
109
+ result.borderStyle = ["solid", "dotted", "dashed"].includes(result[bs]) ? result[bs] : "solid";
110
+ }
111
+ delete result[bs];
112
+ }
113
+ }
114
+ if (result.borderStyle !== undefined) {
115
+ if (typeof result.borderStyle !== "string" || !["solid", "dotted", "dashed"].includes(result.borderStyle)) {
116
+ delete result.borderStyle;
117
+ }
118
+ }
119
+
120
+ return Object.keys(result).length > 0 ? result : undefined;
121
+ }
122
+
123
+ // ============================================================================
124
+ // Main Native Rust Stylesheet Transform
125
+ // ============================================================================
126
+ function transformStyles(stylesheet, classNames) {
127
+ if (!stylesheet || !classNames) return undefined;
128
+ if (stylesheet.default && typeof stylesheet.default === "object" && !stylesheet[":root"]) {
129
+ stylesheet = stylesheet.default;
130
+ }
131
+
132
+ const { width, height } = getDimensions();
133
+ const colorScheme = getColorScheme();
134
+
135
+ const cacheKey = getCacheKey();
136
+ if (cacheKey !== currentCacheKey) {
137
+ TRANSFORM_CACHE = {};
138
+ currentCacheKey = cacheKey;
139
+ }
140
+
141
+ if (TRANSFORM_CACHE[classNames] !== undefined) {
142
+ return TRANSFORM_CACHE[classNames];
143
+ }
144
+
145
+ const classes = classNames.trim().split(/\s+/);
146
+ const resolved = {};
147
+
148
+ for (const cls of classes) {
149
+ if (!cls) continue;
150
+ const entry = stylesheet[cls];
151
+ if (!entry) continue;
152
+
153
+ if (entry._static) {
154
+ Object.assign(resolved, entry._static);
155
+ } else if (entry._dynamic) {
156
+ Object.assign(resolved, entry._dynamic);
157
+ } else if (typeof entry === "object") {
158
+ Object.assign(resolved, entry);
159
+ }
160
+ }
161
+
162
+ const result = Object.keys(resolved).length > 0 ? resolved : undefined;
163
+ TRANSFORM_CACHE[classNames] = result;
164
+ return result;
165
+ }
166
+
167
+ // ============================================================================
168
+ // Inherit Style
169
+ // ============================================================================
170
+ function getInheritStyle(declarations) {
171
+ if (!declarations) return undefined;
172
+
173
+ const inheritDeclarations = {};
174
+ for (const key of INHERIT_PROPERTIES) {
175
+ if (declarations[key] !== undefined) {
176
+ inheritDeclarations[key] = declarations[key];
177
+ }
178
+ }
179
+
180
+ return Object.keys(inheritDeclarations).length > 0 ? inheritDeclarations : undefined;
181
+ }
182
+
183
+ // ============================================================================
184
+ // Main Entry Point
185
+ // ============================================================================
186
+ function getStyle(stylesheet, [inheritStyle, className, style]) {
187
+ const inherited = getInheritStyle(getFlattenStyle(inheritStyle));
188
+ const transformed = transformStyles(stylesheet, className);
189
+ const result = getFlattenStyle([inherited, transformed, style]);
190
+ return result;
191
+ }
192
+
193
+ // ============================================================================
194
+ // Lightweight Merge for Static Styles
195
+ // ============================================================================
196
+ function mergeStyles(inheritStyle, staticStyles, inlineStyle) {
197
+ if (!inheritStyle && !inlineStyle) {
198
+ return staticStyles;
199
+ }
200
+
201
+ let inherited;
202
+ if (inheritStyle) {
203
+ const flatInherit = getFlattenStyle(inheritStyle);
204
+ if (flatInherit) {
205
+ inherited = {};
206
+ for (const key of INHERIT_PROPERTIES) {
207
+ if (flatInherit[key] !== undefined) {
208
+ inherited[key] = flatInherit[key];
209
+ }
210
+ }
211
+ if (Object.keys(inherited).length === 0) {
212
+ inherited = undefined;
213
+ }
214
+ }
215
+ }
216
+
217
+ if (!inherited && !inlineStyle) {
218
+ return staticStyles;
219
+ }
220
+
221
+ const result = {};
222
+ if (inherited) Object.assign(result, inherited);
223
+ if (staticStyles) {
224
+ for (const key in staticStyles) {
225
+ if (!key.startsWith("--")) {
226
+ result[key] = staticStyles[key];
227
+ }
228
+ }
229
+ }
230
+ if (inlineStyle) Object.assign(result, inlineStyle);
231
+
232
+ return Object.keys(result).length > 0 ? result : undefined;
233
+ }
234
+
235
+ export default {
236
+ getFlattenStyle,
237
+ getStyle,
238
+ getInheritStyle,
239
+ mergeStyles,
240
+ clearCache,
241
+ };
@@ -0,0 +1,87 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import Stylesheet from "./features/stylesheet";
4
+ import { parseStylesheetWithLightning } from "./utils/css";
5
+
6
+ export function getStylesheet(css, filename) {
7
+ const rawStylesheet = parseStylesheetWithLightning(css);
8
+ const stylesheet = new Stylesheet();
9
+ stylesheet.setRawStylesheet(rawStylesheet);
10
+
11
+ const jsonContent = stylesheet.toJSON();
12
+ writeStylesheetJSON(jsonContent, filename);
13
+
14
+ return jsonContent;
15
+ }
16
+
17
+ export function writeStylesheetJSON(content, filename) {
18
+ try {
19
+ const distPath = path.join(__dirname, "exported-stylesheet.json");
20
+ fs.writeFileSync(distPath, content, { mode: 0o755 });
21
+
22
+ const srcPath = path.resolve(__dirname, "../src/exported-stylesheet.json");
23
+ if (fs.existsSync(path.dirname(srcPath))) {
24
+ fs.writeFileSync(srcPath, content, { mode: 0o755 });
25
+ }
26
+
27
+ if (filename) {
28
+ fs.writeFileSync(`${filename}.json`, content, { mode: 0o755 });
29
+ }
30
+ } catch {
31
+ // Silently fail - Babel will fall back to runtime
32
+ }
33
+ }
34
+
35
+ export function transform({ src, filename, options }) {
36
+ const projectRoot = options && options.projectRoot ? options.projectRoot : process.cwd();
37
+
38
+ const resolveTransformer = (() => {
39
+ try {
40
+ return require("@expo/metro-config/babel-transformer");
41
+ } catch {
42
+ try {
43
+ return require("@react-native/metro-babel-transformer");
44
+ } catch {
45
+ try {
46
+ return require("metro-react-native-babel-transformer");
47
+ } catch {
48
+ const resolveOptions = { paths: [projectRoot] };
49
+ try {
50
+ const resolved = require.resolve("@expo/metro-config/babel-transformer", resolveOptions);
51
+ return eval("require")(resolved);
52
+ } catch {
53
+ try {
54
+ const resolved = require.resolve("@react-native/metro-babel-transformer", resolveOptions);
55
+ return eval("require")(resolved);
56
+ } catch {
57
+ try {
58
+ const resolved = require.resolve("metro-react-native-babel-transformer", resolveOptions);
59
+ return eval("require")(resolved);
60
+ } catch {
61
+ throw new Error(
62
+ "Failed to load any upstream babel-transformer. Please ensure either '@expo/metro-config', '@react-native/metro-babel-transformer', or 'metro-react-native-babel-transformer' is installed.",
63
+ );
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ })();
71
+
72
+ if (filename.endsWith(".css")) {
73
+ const jsonContent = getStylesheet(src, filename);
74
+ return resolveTransformer.transform({
75
+ src: `const sheet = ${jsonContent};\nmodule.exports = sheet;\nmodule.exports.default = sheet;\nmodule.exports.__esModule = true;`,
76
+ filename,
77
+ options,
78
+ });
79
+ }
80
+ return resolveTransformer.transform({ src, filename, options });
81
+ }
82
+
83
+ export default {
84
+ transform,
85
+ getStylesheet,
86
+ writeStylesheetJSON,
87
+ };