@cssxio/react-native 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CSSX contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # `@cssxio/react-native`
2
+
3
+ CSSX utilities for bare React Native and Expo. Styles are emitted as native
4
+ style objects; no CSS file or DOM shim is used.
5
+
6
+ ```tsx
7
+ import { create, props, sx } from '@cssxio/react-native';
8
+
9
+ const styles = create({ card: 'flex-1 gap-4 rounded-lg bg-blue-600 p-4' });
10
+
11
+ <View {...props(styles.card)}>
12
+ <Text style={sx('text-lg font-semibold text-white')}>CSSX native</Text>
13
+ </View>;
14
+ ```
15
+
16
+ Add `@cssxio/react-native/babel` to the Babel plugins used by Metro. The plugin
17
+ precompiles object-literal `create` calls. The runtime fallback keeps the same
18
+ API available in tests and environments that do not run Babel.
19
+
20
+ Bare React Native:
21
+
22
+ ```js
23
+ module.exports = {
24
+ presets: ['module:@react-native/babel-preset'],
25
+ plugins: ['@cssxio/react-native/babel'],
26
+ };
27
+ ```
28
+
29
+ Expo:
30
+
31
+ ```js
32
+ module.exports = function (api) {
33
+ api.cache(true);
34
+ return {
35
+ presets: ['babel-preset-expo'],
36
+ plugins: ['@cssxio/react-native/babel'],
37
+ };
38
+ };
39
+ ```
40
+
41
+ Utilities that map to React Native `ViewStyle`, `TextStyle`, or `ImageStyle`
42
+ are supported. Browser-only selectors, media queries, CSS variables, grid,
43
+ tables, filters, masks, and DOM-specific values fail with an explicit build
44
+ error. Pass `{ platform: 'ios' }` or `{ platform: 'android' }` to `create` when
45
+ using the `ios:` and `android:` variants.
package/dist/babel.cjs ADDED
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/babel.ts
21
+ var babel_exports = {};
22
+ __export(babel_exports, {
23
+ default: () => cssxReactNativeBabelPlugin
24
+ });
25
+ module.exports = __toCommonJS(babel_exports);
26
+
27
+ // src/index.ts
28
+ var import_compiler = require("@cssxio/compiler");
29
+ var propertyNames = {
30
+ "align-content": "alignContent",
31
+ "align-items": "alignItems",
32
+ "align-self": "alignSelf",
33
+ "aspect-ratio": "aspectRatio",
34
+ "background-color": "backgroundColor",
35
+ "border-bottom-color": "borderBottomColor",
36
+ "border-bottom-left-radius": "borderBottomLeftRadius",
37
+ "border-bottom-right-radius": "borderBottomRightRadius",
38
+ "border-bottom-width": "borderBottomWidth",
39
+ "border-color": "borderColor",
40
+ "border-left-color": "borderLeftColor",
41
+ "border-left-width": "borderLeftWidth",
42
+ "border-radius": "borderRadius",
43
+ "border-right-color": "borderRightColor",
44
+ "border-right-width": "borderRightWidth",
45
+ "border-style": "borderStyle",
46
+ "border-top-color": "borderTopColor",
47
+ "border-top-left-radius": "borderTopLeftRadius",
48
+ "border-top-right-radius": "borderTopRightRadius",
49
+ "border-top-width": "borderTopWidth",
50
+ "border-width": "borderWidth",
51
+ bottom: "bottom",
52
+ color: "color",
53
+ display: "display",
54
+ flex: "flex",
55
+ "flex-basis": "flexBasis",
56
+ "flex-direction": "flexDirection",
57
+ "flex-grow": "flexGrow",
58
+ "flex-shrink": "flexShrink",
59
+ "flex-wrap": "flexWrap",
60
+ "font-size": "fontSize",
61
+ "font-style": "fontStyle",
62
+ "font-weight": "fontWeight",
63
+ gap: "gap",
64
+ height: "height",
65
+ "justify-content": "justifyContent",
66
+ left: "left",
67
+ "letter-spacing": "letterSpacing",
68
+ "line-height": "lineHeight",
69
+ margin: "margin",
70
+ "margin-bottom": "marginBottom",
71
+ "margin-left": "marginLeft",
72
+ "margin-right": "marginRight",
73
+ "margin-top": "marginTop",
74
+ "max-height": "maxHeight",
75
+ "max-width": "maxWidth",
76
+ "min-height": "minHeight",
77
+ "min-width": "minWidth",
78
+ opacity: "opacity",
79
+ overflow: "overflow",
80
+ padding: "padding",
81
+ "padding-bottom": "paddingBottom",
82
+ "padding-left": "paddingLeft",
83
+ "padding-right": "paddingRight",
84
+ "padding-top": "paddingTop",
85
+ position: "position",
86
+ right: "right",
87
+ "text-align": "textAlign",
88
+ "text-decoration-line": "textDecorationLine",
89
+ "text-transform": "textTransform",
90
+ top: "top",
91
+ width: "width",
92
+ "z-index": "zIndex"
93
+ };
94
+ var logicalProperties = {
95
+ "inset-inline": ["left", "right"],
96
+ "margin-block": ["marginTop", "marginBottom"],
97
+ "margin-inline": ["marginLeft", "marginRight"],
98
+ "padding-block": ["paddingTop", "paddingBottom"],
99
+ "padding-inline": ["paddingLeft", "paddingRight"]
100
+ };
101
+ function create(styles, options = {}) {
102
+ const records = (0, import_compiler.compileStyleRecords)(styles, { theme: options.theme });
103
+ const theme = (0, import_compiler.parseTheme)(options.theme);
104
+ return Object.fromEntries(
105
+ Object.entries(records.candidates).map(([name, candidates]) => [
106
+ name,
107
+ { $$cssx: 3, style: compileCandidates(candidates, theme, options.platform) }
108
+ ])
109
+ );
110
+ }
111
+ function compileCandidates(candidates, theme, platform) {
112
+ const style = {};
113
+ for (const source of candidates) {
114
+ const candidate = platformCandidate(source, platform);
115
+ if (candidate === null) {
116
+ continue;
117
+ }
118
+ const recipe = (0, import_compiler.describeUtilityRecipe)(candidate, theme);
119
+ for (const atom of recipe.atoms) {
120
+ for (const declaration of atom) {
121
+ appendDeclaration(style, candidate, declaration);
122
+ }
123
+ }
124
+ }
125
+ return style;
126
+ }
127
+ function platformCandidate(source, platform) {
128
+ const match = /^(ios|android):(.*)$/.exec(source);
129
+ if (match) {
130
+ return match[1] === platform ? match[2] : null;
131
+ }
132
+ if (!source.startsWith("[") && source.includes(":")) {
133
+ throw new Error(`CSSX React Native cannot represent the variant in "${source}".`);
134
+ }
135
+ return source;
136
+ }
137
+ function appendDeclaration(style, candidate, declaration) {
138
+ if (declaration.selectorSuffix || declaration.atRule) {
139
+ throw new Error(`CSSX React Native cannot represent browser-only utility "${candidate}".`);
140
+ }
141
+ if (appendCustomTransform(style, declaration.property, declaration.value, candidate)) {
142
+ return;
143
+ }
144
+ if (appendTransform(style, declaration.property, declaration.value)) {
145
+ return;
146
+ }
147
+ const logical = logicalProperties[declaration.property];
148
+ if (logical) {
149
+ const value2 = nativeValue(declaration.value, candidate);
150
+ for (const property2 of logical) {
151
+ style[property2] = value2;
152
+ }
153
+ return;
154
+ }
155
+ const property = propertyNames[declaration.property];
156
+ if (!property) {
157
+ throw new Error(
158
+ `CSSX React Native cannot represent "${candidate}" because ${declaration.property} is browser-only.`
159
+ );
160
+ }
161
+ const value = nativeValue(declaration.value, candidate);
162
+ if (property === "display" && value !== "flex" && value !== "none") {
163
+ throw new Error(`CSSX React Native cannot represent display value "${String(value)}" from "${candidate}".`);
164
+ }
165
+ if (property === "overflow" && value !== "visible" && value !== "hidden") {
166
+ throw new Error(`CSSX React Native cannot represent overflow value "${String(value)}" from "${candidate}".`);
167
+ }
168
+ if (property === "flex" && typeof value === "string") {
169
+ style[property] = Number.parseFloat(value);
170
+ return;
171
+ }
172
+ style[property] = value;
173
+ }
174
+ function appendTransform(style, property, value) {
175
+ if ((property === "translate" || property === "rotate" || property === "scale") && value.includes("var(")) {
176
+ return true;
177
+ }
178
+ let transforms;
179
+ if (property === "translate") {
180
+ const [x = "0", y = "0"] = value.split(" ");
181
+ transforms = [{ translateX: nativeValue(x, "translate") }, { translateY: nativeValue(y, "translate") }];
182
+ } else if (property === "rotate") {
183
+ transforms = [{ rotate: value }];
184
+ } else if (property === "scale") {
185
+ const [x = "1", y = x] = value.split(" ");
186
+ transforms = [{ scaleX: Number(x) }, { scaleY: Number(y) }];
187
+ }
188
+ if (!transforms) {
189
+ return false;
190
+ }
191
+ style.transform = [...style.transform ?? [], ...transforms];
192
+ return true;
193
+ }
194
+ function appendCustomTransform(style, property, value, candidate) {
195
+ const transformProperty = {
196
+ "--cssx-rotate": "rotate",
197
+ "--cssx-scale-x": "scaleX",
198
+ "--cssx-scale-y": "scaleY",
199
+ "--cssx-translate-x": "translateX",
200
+ "--cssx-translate-y": "translateY"
201
+ };
202
+ const nativeProperty = transformProperty[property];
203
+ if (!nativeProperty) {
204
+ return false;
205
+ }
206
+ const native = nativeValue(value, candidate);
207
+ style.transform = [
208
+ ...style.transform ?? [],
209
+ { [nativeProperty]: native }
210
+ ];
211
+ return true;
212
+ }
213
+ function nativeValue(value, candidate) {
214
+ const important = value.endsWith(" !important") ? value.slice(0, -11) : value;
215
+ const calculation = /^calc\((-?[\d.]+)rem \* (-?[\d.]+)\)$/.exec(important);
216
+ if (calculation) {
217
+ return Number(calculation[1]) * 16 * Number(calculation[2]);
218
+ }
219
+ const rem = /^(-?[\d.]+)rem$/.exec(important);
220
+ if (rem) {
221
+ return Number(rem[1]) * 16;
222
+ }
223
+ const pixels = /^(-?[\d.]+)px$/.exec(important);
224
+ if (pixels) {
225
+ return Number(pixels[1]);
226
+ }
227
+ if (/^-?[\d.]+$/.test(important)) {
228
+ return Number(important);
229
+ }
230
+ if (/^(?:#|rgb|rgba|hsl|hsla|hwb|transparent|black|white)/.test(important) || important.endsWith("%")) {
231
+ return important;
232
+ }
233
+ if (important.startsWith("var(") || important.startsWith("color-mix(") || important.startsWith("oklch(")) {
234
+ throw new Error(`CSSX React Native requires "${candidate}" to resolve to a native color or value at build time.`);
235
+ }
236
+ return important;
237
+ }
238
+
239
+ // src/babel.ts
240
+ function cssxReactNativeBabelPlugin(api, options = {}) {
241
+ api.assertVersion(7);
242
+ const types = api.types;
243
+ const importSource = options.importSource ?? "@cssxio/react-native";
244
+ return {
245
+ name: "@cssxio/react-native/babel",
246
+ visitor: {
247
+ CallExpression(path) {
248
+ if (!isCreateCall(path, importSource, types)) {
249
+ return;
250
+ }
251
+ const argument = path.node.arguments[0];
252
+ if (path.node.arguments.length !== 1 || !types.isObjectExpression(argument)) {
253
+ throw path.buildCodeFrameError("cssx.create() expects one object literal argument.");
254
+ }
255
+ const input = readStyleMap(argument, types, path);
256
+ path.replaceWith(types.valueToNode(create(input, options)));
257
+ }
258
+ }
259
+ };
260
+ }
261
+ function isCreateCall(path, importSource, types) {
262
+ if (!types.isIdentifier(path.node.callee)) {
263
+ return false;
264
+ }
265
+ const binding = path.scope.getBinding(path.node.callee.name);
266
+ const declaration = binding?.path.parentPath?.node;
267
+ return Boolean(
268
+ binding?.path.isImportSpecifier() && types.isIdentifier(binding.path.node.imported, { name: "create" }) && types.isImportDeclaration(declaration) && declaration.source.value === importSource
269
+ );
270
+ }
271
+ function readStyleMap(object, types, path) {
272
+ const input = {};
273
+ for (const property of object.properties) {
274
+ if (!types.isObjectProperty(property) || !types.isStringLiteral(property.value)) {
275
+ throw path.buildCodeFrameError("CSSX React Native style values must be static string literals.");
276
+ }
277
+ if (property.computed) {
278
+ throw path.buildCodeFrameError("CSSX React Native style keys must be static.");
279
+ }
280
+ const key = types.isIdentifier(property.key) ? property.key.name : types.isStringLiteral(property.key) || types.isNumericLiteral(property.key) ? String(property.key.value) : null;
281
+ if (key === null) {
282
+ throw path.buildCodeFrameError("CSSX React Native style keys must be static.");
283
+ }
284
+ input[key] = property.value.value;
285
+ }
286
+ return input;
287
+ }
@@ -0,0 +1,14 @@
1
+ import { PluginObj, PluginPass } from '@babel/core';
2
+ import * as BabelTypes from '@babel/types';
3
+ import { NativeCompilerOptions } from './index.cjs';
4
+
5
+ interface NativeBabelOptions extends NativeCompilerOptions {
6
+ readonly importSource?: string;
7
+ }
8
+ /** Compiles object-literal create calls imported from the React Native runtime. */
9
+ declare function cssxReactNativeBabelPlugin(api: {
10
+ readonly types: typeof BabelTypes;
11
+ assertVersion(version: number): void;
12
+ }, options?: NativeBabelOptions): PluginObj<PluginPass>;
13
+
14
+ export { type NativeBabelOptions, cssxReactNativeBabelPlugin as default };
@@ -0,0 +1,14 @@
1
+ import { PluginObj, PluginPass } from '@babel/core';
2
+ import * as BabelTypes from '@babel/types';
3
+ import { NativeCompilerOptions } from './index.js';
4
+
5
+ interface NativeBabelOptions extends NativeCompilerOptions {
6
+ readonly importSource?: string;
7
+ }
8
+ /** Compiles object-literal create calls imported from the React Native runtime. */
9
+ declare function cssxReactNativeBabelPlugin(api: {
10
+ readonly types: typeof BabelTypes;
11
+ assertVersion(version: number): void;
12
+ }, options?: NativeBabelOptions): PluginObj<PluginPass>;
13
+
14
+ export { type NativeBabelOptions, cssxReactNativeBabelPlugin as default };
package/dist/babel.js ADDED
@@ -0,0 +1,56 @@
1
+ import {
2
+ create
3
+ } from "./chunk-P3KGLFG3.js";
4
+
5
+ // src/babel.ts
6
+ function cssxReactNativeBabelPlugin(api, options = {}) {
7
+ api.assertVersion(7);
8
+ const types = api.types;
9
+ const importSource = options.importSource ?? "@cssxio/react-native";
10
+ return {
11
+ name: "@cssxio/react-native/babel",
12
+ visitor: {
13
+ CallExpression(path) {
14
+ if (!isCreateCall(path, importSource, types)) {
15
+ return;
16
+ }
17
+ const argument = path.node.arguments[0];
18
+ if (path.node.arguments.length !== 1 || !types.isObjectExpression(argument)) {
19
+ throw path.buildCodeFrameError("cssx.create() expects one object literal argument.");
20
+ }
21
+ const input = readStyleMap(argument, types, path);
22
+ path.replaceWith(types.valueToNode(create(input, options)));
23
+ }
24
+ }
25
+ };
26
+ }
27
+ function isCreateCall(path, importSource, types) {
28
+ if (!types.isIdentifier(path.node.callee)) {
29
+ return false;
30
+ }
31
+ const binding = path.scope.getBinding(path.node.callee.name);
32
+ const declaration = binding?.path.parentPath?.node;
33
+ return Boolean(
34
+ binding?.path.isImportSpecifier() && types.isIdentifier(binding.path.node.imported, { name: "create" }) && types.isImportDeclaration(declaration) && declaration.source.value === importSource
35
+ );
36
+ }
37
+ function readStyleMap(object, types, path) {
38
+ const input = {};
39
+ for (const property of object.properties) {
40
+ if (!types.isObjectProperty(property) || !types.isStringLiteral(property.value)) {
41
+ throw path.buildCodeFrameError("CSSX React Native style values must be static string literals.");
42
+ }
43
+ if (property.computed) {
44
+ throw path.buildCodeFrameError("CSSX React Native style keys must be static.");
45
+ }
46
+ const key = types.isIdentifier(property.key) ? property.key.name : types.isStringLiteral(property.key) || types.isNumericLiteral(property.key) ? String(property.key.value) : null;
47
+ if (key === null) {
48
+ throw path.buildCodeFrameError("CSSX React Native style keys must be static.");
49
+ }
50
+ input[key] = property.value.value;
51
+ }
52
+ return input;
53
+ }
54
+ export {
55
+ cssxReactNativeBabelPlugin as default
56
+ };
@@ -0,0 +1,264 @@
1
+ // src/index.ts
2
+ import { compileStyleRecords, describeUtilityRecipe, parseTheme } from "@cssxio/compiler";
3
+ var propertyNames = {
4
+ "align-content": "alignContent",
5
+ "align-items": "alignItems",
6
+ "align-self": "alignSelf",
7
+ "aspect-ratio": "aspectRatio",
8
+ "background-color": "backgroundColor",
9
+ "border-bottom-color": "borderBottomColor",
10
+ "border-bottom-left-radius": "borderBottomLeftRadius",
11
+ "border-bottom-right-radius": "borderBottomRightRadius",
12
+ "border-bottom-width": "borderBottomWidth",
13
+ "border-color": "borderColor",
14
+ "border-left-color": "borderLeftColor",
15
+ "border-left-width": "borderLeftWidth",
16
+ "border-radius": "borderRadius",
17
+ "border-right-color": "borderRightColor",
18
+ "border-right-width": "borderRightWidth",
19
+ "border-style": "borderStyle",
20
+ "border-top-color": "borderTopColor",
21
+ "border-top-left-radius": "borderTopLeftRadius",
22
+ "border-top-right-radius": "borderTopRightRadius",
23
+ "border-top-width": "borderTopWidth",
24
+ "border-width": "borderWidth",
25
+ bottom: "bottom",
26
+ color: "color",
27
+ display: "display",
28
+ flex: "flex",
29
+ "flex-basis": "flexBasis",
30
+ "flex-direction": "flexDirection",
31
+ "flex-grow": "flexGrow",
32
+ "flex-shrink": "flexShrink",
33
+ "flex-wrap": "flexWrap",
34
+ "font-size": "fontSize",
35
+ "font-style": "fontStyle",
36
+ "font-weight": "fontWeight",
37
+ gap: "gap",
38
+ height: "height",
39
+ "justify-content": "justifyContent",
40
+ left: "left",
41
+ "letter-spacing": "letterSpacing",
42
+ "line-height": "lineHeight",
43
+ margin: "margin",
44
+ "margin-bottom": "marginBottom",
45
+ "margin-left": "marginLeft",
46
+ "margin-right": "marginRight",
47
+ "margin-top": "marginTop",
48
+ "max-height": "maxHeight",
49
+ "max-width": "maxWidth",
50
+ "min-height": "minHeight",
51
+ "min-width": "minWidth",
52
+ opacity: "opacity",
53
+ overflow: "overflow",
54
+ padding: "padding",
55
+ "padding-bottom": "paddingBottom",
56
+ "padding-left": "paddingLeft",
57
+ "padding-right": "paddingRight",
58
+ "padding-top": "paddingTop",
59
+ position: "position",
60
+ right: "right",
61
+ "text-align": "textAlign",
62
+ "text-decoration-line": "textDecorationLine",
63
+ "text-transform": "textTransform",
64
+ top: "top",
65
+ width: "width",
66
+ "z-index": "zIndex"
67
+ };
68
+ var logicalProperties = {
69
+ "inset-inline": ["left", "right"],
70
+ "margin-block": ["marginTop", "marginBottom"],
71
+ "margin-inline": ["marginLeft", "marginRight"],
72
+ "padding-block": ["paddingTop", "paddingBottom"],
73
+ "padding-inline": ["paddingLeft", "paddingRight"]
74
+ };
75
+ function create(styles, options = {}) {
76
+ const records = compileStyleRecords(styles, { theme: options.theme });
77
+ const theme = parseTheme(options.theme);
78
+ return Object.fromEntries(
79
+ Object.entries(records.candidates).map(([name, candidates]) => [
80
+ name,
81
+ { $$cssx: 3, style: compileCandidates(candidates, theme, options.platform) }
82
+ ])
83
+ );
84
+ }
85
+ function props(...inputs) {
86
+ const styles = [];
87
+ visitStyles(inputs, styles);
88
+ return { style: mergeStyles(styles) };
89
+ }
90
+ function sx(...inputs) {
91
+ const styles = [];
92
+ for (const input of inputs) {
93
+ if (typeof input === "string") {
94
+ styles.push(create({ value: input }).value.style);
95
+ } else {
96
+ visitStyles([input], styles);
97
+ }
98
+ }
99
+ return mergeStyles(styles);
100
+ }
101
+ function visitStyles(inputs, output) {
102
+ for (const input of inputs) {
103
+ if (!input) {
104
+ continue;
105
+ }
106
+ if (isNativeStyleArray(input)) {
107
+ visitStyles(input, output);
108
+ continue;
109
+ }
110
+ if (typeof input !== "object" || input.$$cssx !== 3) {
111
+ throw new Error("cssx.props() received an object that was not compiled by CSSX for React Native.");
112
+ }
113
+ output.push(input.style);
114
+ }
115
+ }
116
+ function isNativeStyleArray(input) {
117
+ return Array.isArray(input);
118
+ }
119
+ function mergeStyles(styles) {
120
+ const output = {};
121
+ for (const style of styles) {
122
+ for (const [property, value] of Object.entries(style)) {
123
+ if (property === "transform" && Array.isArray(output.transform) && Array.isArray(value)) {
124
+ output.transform = [...output.transform, ...value];
125
+ } else {
126
+ output[property] = value;
127
+ }
128
+ }
129
+ }
130
+ return output;
131
+ }
132
+ function compileCandidates(candidates, theme, platform) {
133
+ const style = {};
134
+ for (const source of candidates) {
135
+ const candidate = platformCandidate(source, platform);
136
+ if (candidate === null) {
137
+ continue;
138
+ }
139
+ const recipe = describeUtilityRecipe(candidate, theme);
140
+ for (const atom of recipe.atoms) {
141
+ for (const declaration of atom) {
142
+ appendDeclaration(style, candidate, declaration);
143
+ }
144
+ }
145
+ }
146
+ return style;
147
+ }
148
+ function platformCandidate(source, platform) {
149
+ const match = /^(ios|android):(.*)$/.exec(source);
150
+ if (match) {
151
+ return match[1] === platform ? match[2] : null;
152
+ }
153
+ if (!source.startsWith("[") && source.includes(":")) {
154
+ throw new Error(`CSSX React Native cannot represent the variant in "${source}".`);
155
+ }
156
+ return source;
157
+ }
158
+ function appendDeclaration(style, candidate, declaration) {
159
+ if (declaration.selectorSuffix || declaration.atRule) {
160
+ throw new Error(`CSSX React Native cannot represent browser-only utility "${candidate}".`);
161
+ }
162
+ if (appendCustomTransform(style, declaration.property, declaration.value, candidate)) {
163
+ return;
164
+ }
165
+ if (appendTransform(style, declaration.property, declaration.value)) {
166
+ return;
167
+ }
168
+ const logical = logicalProperties[declaration.property];
169
+ if (logical) {
170
+ const value2 = nativeValue(declaration.value, candidate);
171
+ for (const property2 of logical) {
172
+ style[property2] = value2;
173
+ }
174
+ return;
175
+ }
176
+ const property = propertyNames[declaration.property];
177
+ if (!property) {
178
+ throw new Error(
179
+ `CSSX React Native cannot represent "${candidate}" because ${declaration.property} is browser-only.`
180
+ );
181
+ }
182
+ const value = nativeValue(declaration.value, candidate);
183
+ if (property === "display" && value !== "flex" && value !== "none") {
184
+ throw new Error(`CSSX React Native cannot represent display value "${String(value)}" from "${candidate}".`);
185
+ }
186
+ if (property === "overflow" && value !== "visible" && value !== "hidden") {
187
+ throw new Error(`CSSX React Native cannot represent overflow value "${String(value)}" from "${candidate}".`);
188
+ }
189
+ if (property === "flex" && typeof value === "string") {
190
+ style[property] = Number.parseFloat(value);
191
+ return;
192
+ }
193
+ style[property] = value;
194
+ }
195
+ function appendTransform(style, property, value) {
196
+ if ((property === "translate" || property === "rotate" || property === "scale") && value.includes("var(")) {
197
+ return true;
198
+ }
199
+ let transforms;
200
+ if (property === "translate") {
201
+ const [x = "0", y = "0"] = value.split(" ");
202
+ transforms = [{ translateX: nativeValue(x, "translate") }, { translateY: nativeValue(y, "translate") }];
203
+ } else if (property === "rotate") {
204
+ transforms = [{ rotate: value }];
205
+ } else if (property === "scale") {
206
+ const [x = "1", y = x] = value.split(" ");
207
+ transforms = [{ scaleX: Number(x) }, { scaleY: Number(y) }];
208
+ }
209
+ if (!transforms) {
210
+ return false;
211
+ }
212
+ style.transform = [...style.transform ?? [], ...transforms];
213
+ return true;
214
+ }
215
+ function appendCustomTransform(style, property, value, candidate) {
216
+ const transformProperty = {
217
+ "--cssx-rotate": "rotate",
218
+ "--cssx-scale-x": "scaleX",
219
+ "--cssx-scale-y": "scaleY",
220
+ "--cssx-translate-x": "translateX",
221
+ "--cssx-translate-y": "translateY"
222
+ };
223
+ const nativeProperty = transformProperty[property];
224
+ if (!nativeProperty) {
225
+ return false;
226
+ }
227
+ const native = nativeValue(value, candidate);
228
+ style.transform = [
229
+ ...style.transform ?? [],
230
+ { [nativeProperty]: native }
231
+ ];
232
+ return true;
233
+ }
234
+ function nativeValue(value, candidate) {
235
+ const important = value.endsWith(" !important") ? value.slice(0, -11) : value;
236
+ const calculation = /^calc\((-?[\d.]+)rem \* (-?[\d.]+)\)$/.exec(important);
237
+ if (calculation) {
238
+ return Number(calculation[1]) * 16 * Number(calculation[2]);
239
+ }
240
+ const rem = /^(-?[\d.]+)rem$/.exec(important);
241
+ if (rem) {
242
+ return Number(rem[1]) * 16;
243
+ }
244
+ const pixels = /^(-?[\d.]+)px$/.exec(important);
245
+ if (pixels) {
246
+ return Number(pixels[1]);
247
+ }
248
+ if (/^-?[\d.]+$/.test(important)) {
249
+ return Number(important);
250
+ }
251
+ if (/^(?:#|rgb|rgba|hsl|hsla|hwb|transparent|black|white)/.test(important) || important.endsWith("%")) {
252
+ return important;
253
+ }
254
+ if (important.startsWith("var(") || important.startsWith("color-mix(") || important.startsWith("oklch(")) {
255
+ throw new Error(`CSSX React Native requires "${candidate}" to resolve to a native color or value at build time.`);
256
+ }
257
+ return important;
258
+ }
259
+
260
+ export {
261
+ create,
262
+ props,
263
+ sx
264
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ create: () => create,
24
+ props: () => props,
25
+ sx: () => sx
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var import_compiler = require("@cssxio/compiler");
29
+ var propertyNames = {
30
+ "align-content": "alignContent",
31
+ "align-items": "alignItems",
32
+ "align-self": "alignSelf",
33
+ "aspect-ratio": "aspectRatio",
34
+ "background-color": "backgroundColor",
35
+ "border-bottom-color": "borderBottomColor",
36
+ "border-bottom-left-radius": "borderBottomLeftRadius",
37
+ "border-bottom-right-radius": "borderBottomRightRadius",
38
+ "border-bottom-width": "borderBottomWidth",
39
+ "border-color": "borderColor",
40
+ "border-left-color": "borderLeftColor",
41
+ "border-left-width": "borderLeftWidth",
42
+ "border-radius": "borderRadius",
43
+ "border-right-color": "borderRightColor",
44
+ "border-right-width": "borderRightWidth",
45
+ "border-style": "borderStyle",
46
+ "border-top-color": "borderTopColor",
47
+ "border-top-left-radius": "borderTopLeftRadius",
48
+ "border-top-right-radius": "borderTopRightRadius",
49
+ "border-top-width": "borderTopWidth",
50
+ "border-width": "borderWidth",
51
+ bottom: "bottom",
52
+ color: "color",
53
+ display: "display",
54
+ flex: "flex",
55
+ "flex-basis": "flexBasis",
56
+ "flex-direction": "flexDirection",
57
+ "flex-grow": "flexGrow",
58
+ "flex-shrink": "flexShrink",
59
+ "flex-wrap": "flexWrap",
60
+ "font-size": "fontSize",
61
+ "font-style": "fontStyle",
62
+ "font-weight": "fontWeight",
63
+ gap: "gap",
64
+ height: "height",
65
+ "justify-content": "justifyContent",
66
+ left: "left",
67
+ "letter-spacing": "letterSpacing",
68
+ "line-height": "lineHeight",
69
+ margin: "margin",
70
+ "margin-bottom": "marginBottom",
71
+ "margin-left": "marginLeft",
72
+ "margin-right": "marginRight",
73
+ "margin-top": "marginTop",
74
+ "max-height": "maxHeight",
75
+ "max-width": "maxWidth",
76
+ "min-height": "minHeight",
77
+ "min-width": "minWidth",
78
+ opacity: "opacity",
79
+ overflow: "overflow",
80
+ padding: "padding",
81
+ "padding-bottom": "paddingBottom",
82
+ "padding-left": "paddingLeft",
83
+ "padding-right": "paddingRight",
84
+ "padding-top": "paddingTop",
85
+ position: "position",
86
+ right: "right",
87
+ "text-align": "textAlign",
88
+ "text-decoration-line": "textDecorationLine",
89
+ "text-transform": "textTransform",
90
+ top: "top",
91
+ width: "width",
92
+ "z-index": "zIndex"
93
+ };
94
+ var logicalProperties = {
95
+ "inset-inline": ["left", "right"],
96
+ "margin-block": ["marginTop", "marginBottom"],
97
+ "margin-inline": ["marginLeft", "marginRight"],
98
+ "padding-block": ["paddingTop", "paddingBottom"],
99
+ "padding-inline": ["paddingLeft", "paddingRight"]
100
+ };
101
+ function create(styles, options = {}) {
102
+ const records = (0, import_compiler.compileStyleRecords)(styles, { theme: options.theme });
103
+ const theme = (0, import_compiler.parseTheme)(options.theme);
104
+ return Object.fromEntries(
105
+ Object.entries(records.candidates).map(([name, candidates]) => [
106
+ name,
107
+ { $$cssx: 3, style: compileCandidates(candidates, theme, options.platform) }
108
+ ])
109
+ );
110
+ }
111
+ function props(...inputs) {
112
+ const styles = [];
113
+ visitStyles(inputs, styles);
114
+ return { style: mergeStyles(styles) };
115
+ }
116
+ function sx(...inputs) {
117
+ const styles = [];
118
+ for (const input of inputs) {
119
+ if (typeof input === "string") {
120
+ styles.push(create({ value: input }).value.style);
121
+ } else {
122
+ visitStyles([input], styles);
123
+ }
124
+ }
125
+ return mergeStyles(styles);
126
+ }
127
+ function visitStyles(inputs, output) {
128
+ for (const input of inputs) {
129
+ if (!input) {
130
+ continue;
131
+ }
132
+ if (isNativeStyleArray(input)) {
133
+ visitStyles(input, output);
134
+ continue;
135
+ }
136
+ if (typeof input !== "object" || input.$$cssx !== 3) {
137
+ throw new Error("cssx.props() received an object that was not compiled by CSSX for React Native.");
138
+ }
139
+ output.push(input.style);
140
+ }
141
+ }
142
+ function isNativeStyleArray(input) {
143
+ return Array.isArray(input);
144
+ }
145
+ function mergeStyles(styles) {
146
+ const output = {};
147
+ for (const style of styles) {
148
+ for (const [property, value] of Object.entries(style)) {
149
+ if (property === "transform" && Array.isArray(output.transform) && Array.isArray(value)) {
150
+ output.transform = [...output.transform, ...value];
151
+ } else {
152
+ output[property] = value;
153
+ }
154
+ }
155
+ }
156
+ return output;
157
+ }
158
+ function compileCandidates(candidates, theme, platform) {
159
+ const style = {};
160
+ for (const source of candidates) {
161
+ const candidate = platformCandidate(source, platform);
162
+ if (candidate === null) {
163
+ continue;
164
+ }
165
+ const recipe = (0, import_compiler.describeUtilityRecipe)(candidate, theme);
166
+ for (const atom of recipe.atoms) {
167
+ for (const declaration of atom) {
168
+ appendDeclaration(style, candidate, declaration);
169
+ }
170
+ }
171
+ }
172
+ return style;
173
+ }
174
+ function platformCandidate(source, platform) {
175
+ const match = /^(ios|android):(.*)$/.exec(source);
176
+ if (match) {
177
+ return match[1] === platform ? match[2] : null;
178
+ }
179
+ if (!source.startsWith("[") && source.includes(":")) {
180
+ throw new Error(`CSSX React Native cannot represent the variant in "${source}".`);
181
+ }
182
+ return source;
183
+ }
184
+ function appendDeclaration(style, candidate, declaration) {
185
+ if (declaration.selectorSuffix || declaration.atRule) {
186
+ throw new Error(`CSSX React Native cannot represent browser-only utility "${candidate}".`);
187
+ }
188
+ if (appendCustomTransform(style, declaration.property, declaration.value, candidate)) {
189
+ return;
190
+ }
191
+ if (appendTransform(style, declaration.property, declaration.value)) {
192
+ return;
193
+ }
194
+ const logical = logicalProperties[declaration.property];
195
+ if (logical) {
196
+ const value2 = nativeValue(declaration.value, candidate);
197
+ for (const property2 of logical) {
198
+ style[property2] = value2;
199
+ }
200
+ return;
201
+ }
202
+ const property = propertyNames[declaration.property];
203
+ if (!property) {
204
+ throw new Error(
205
+ `CSSX React Native cannot represent "${candidate}" because ${declaration.property} is browser-only.`
206
+ );
207
+ }
208
+ const value = nativeValue(declaration.value, candidate);
209
+ if (property === "display" && value !== "flex" && value !== "none") {
210
+ throw new Error(`CSSX React Native cannot represent display value "${String(value)}" from "${candidate}".`);
211
+ }
212
+ if (property === "overflow" && value !== "visible" && value !== "hidden") {
213
+ throw new Error(`CSSX React Native cannot represent overflow value "${String(value)}" from "${candidate}".`);
214
+ }
215
+ if (property === "flex" && typeof value === "string") {
216
+ style[property] = Number.parseFloat(value);
217
+ return;
218
+ }
219
+ style[property] = value;
220
+ }
221
+ function appendTransform(style, property, value) {
222
+ if ((property === "translate" || property === "rotate" || property === "scale") && value.includes("var(")) {
223
+ return true;
224
+ }
225
+ let transforms;
226
+ if (property === "translate") {
227
+ const [x = "0", y = "0"] = value.split(" ");
228
+ transforms = [{ translateX: nativeValue(x, "translate") }, { translateY: nativeValue(y, "translate") }];
229
+ } else if (property === "rotate") {
230
+ transforms = [{ rotate: value }];
231
+ } else if (property === "scale") {
232
+ const [x = "1", y = x] = value.split(" ");
233
+ transforms = [{ scaleX: Number(x) }, { scaleY: Number(y) }];
234
+ }
235
+ if (!transforms) {
236
+ return false;
237
+ }
238
+ style.transform = [...style.transform ?? [], ...transforms];
239
+ return true;
240
+ }
241
+ function appendCustomTransform(style, property, value, candidate) {
242
+ const transformProperty = {
243
+ "--cssx-rotate": "rotate",
244
+ "--cssx-scale-x": "scaleX",
245
+ "--cssx-scale-y": "scaleY",
246
+ "--cssx-translate-x": "translateX",
247
+ "--cssx-translate-y": "translateY"
248
+ };
249
+ const nativeProperty = transformProperty[property];
250
+ if (!nativeProperty) {
251
+ return false;
252
+ }
253
+ const native = nativeValue(value, candidate);
254
+ style.transform = [
255
+ ...style.transform ?? [],
256
+ { [nativeProperty]: native }
257
+ ];
258
+ return true;
259
+ }
260
+ function nativeValue(value, candidate) {
261
+ const important = value.endsWith(" !important") ? value.slice(0, -11) : value;
262
+ const calculation = /^calc\((-?[\d.]+)rem \* (-?[\d.]+)\)$/.exec(important);
263
+ if (calculation) {
264
+ return Number(calculation[1]) * 16 * Number(calculation[2]);
265
+ }
266
+ const rem = /^(-?[\d.]+)rem$/.exec(important);
267
+ if (rem) {
268
+ return Number(rem[1]) * 16;
269
+ }
270
+ const pixels = /^(-?[\d.]+)px$/.exec(important);
271
+ if (pixels) {
272
+ return Number(pixels[1]);
273
+ }
274
+ if (/^-?[\d.]+$/.test(important)) {
275
+ return Number(important);
276
+ }
277
+ if (/^(?:#|rgb|rgba|hsl|hsla|hwb|transparent|black|white)/.test(important) || important.endsWith("%")) {
278
+ return important;
279
+ }
280
+ if (important.startsWith("var(") || important.startsWith("color-mix(") || important.startsWith("oklch(")) {
281
+ throw new Error(`CSSX React Native requires "${candidate}" to resolve to a native color or value at build time.`);
282
+ }
283
+ return important;
284
+ }
285
+ // Annotate the CommonJS export names for ESM import in node:
286
+ 0 && (module.exports = {
287
+ create,
288
+ props,
289
+ sx
290
+ });
@@ -0,0 +1,27 @@
1
+ type NativePlatform = 'android' | 'ios';
2
+ type NativeTransform = Readonly<Record<string, number | string>>;
3
+ type NativeStyleValue = number | string | readonly NativeTransform[];
4
+ type NativeStyle = Readonly<Record<string, NativeStyleValue>>;
5
+ interface NativeCompilerOptions {
6
+ readonly platform?: NativePlatform;
7
+ readonly theme?: string;
8
+ }
9
+ interface CompiledNativeStyle {
10
+ readonly $$cssx: 3;
11
+ readonly style: NativeStyle;
12
+ }
13
+ type NativeStyleInput = CompiledNativeStyle | false | null | undefined | readonly NativeStyleInput[];
14
+ type NativeSxInput = string | NativeStyleInput;
15
+ type NativeStyleMap<T extends Readonly<Record<string, string>>> = {
16
+ readonly [Key in keyof T]: CompiledNativeStyle;
17
+ };
18
+ /** Compiles static utility maps to React Native-compatible style records. */
19
+ declare function create<T extends Readonly<Record<string, string>>>(styles: T, options?: NativeCompilerOptions): NativeStyleMap<T>;
20
+ /** Merges compiled native styles from left to right. */
21
+ declare function props(...inputs: readonly NativeStyleInput[]): {
22
+ readonly style: NativeStyle;
23
+ };
24
+ /** Compiles inline utility strings and merges compiled native styles. */
25
+ declare function sx(...inputs: readonly NativeSxInput[]): NativeStyle;
26
+
27
+ export { type CompiledNativeStyle, type NativeCompilerOptions, type NativePlatform, type NativeStyle, type NativeStyleInput, type NativeStyleMap, type NativeStyleValue, type NativeSxInput, type NativeTransform, create, props, sx };
@@ -0,0 +1,27 @@
1
+ type NativePlatform = 'android' | 'ios';
2
+ type NativeTransform = Readonly<Record<string, number | string>>;
3
+ type NativeStyleValue = number | string | readonly NativeTransform[];
4
+ type NativeStyle = Readonly<Record<string, NativeStyleValue>>;
5
+ interface NativeCompilerOptions {
6
+ readonly platform?: NativePlatform;
7
+ readonly theme?: string;
8
+ }
9
+ interface CompiledNativeStyle {
10
+ readonly $$cssx: 3;
11
+ readonly style: NativeStyle;
12
+ }
13
+ type NativeStyleInput = CompiledNativeStyle | false | null | undefined | readonly NativeStyleInput[];
14
+ type NativeSxInput = string | NativeStyleInput;
15
+ type NativeStyleMap<T extends Readonly<Record<string, string>>> = {
16
+ readonly [Key in keyof T]: CompiledNativeStyle;
17
+ };
18
+ /** Compiles static utility maps to React Native-compatible style records. */
19
+ declare function create<T extends Readonly<Record<string, string>>>(styles: T, options?: NativeCompilerOptions): NativeStyleMap<T>;
20
+ /** Merges compiled native styles from left to right. */
21
+ declare function props(...inputs: readonly NativeStyleInput[]): {
22
+ readonly style: NativeStyle;
23
+ };
24
+ /** Compiles inline utility strings and merges compiled native styles. */
25
+ declare function sx(...inputs: readonly NativeSxInput[]): NativeStyle;
26
+
27
+ export { type CompiledNativeStyle, type NativeCompilerOptions, type NativePlatform, type NativeStyle, type NativeStyleInput, type NativeStyleMap, type NativeStyleValue, type NativeSxInput, type NativeTransform, create, props, sx };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ create,
3
+ props,
4
+ sx
5
+ } from "./chunk-P3KGLFG3.js";
6
+ export {
7
+ create,
8
+ props,
9
+ sx
10
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@cssxio/react-native",
3
+ "version": "0.2.0",
4
+ "description": "CSSX utility styling runtime and compiler transform for React Native and Expo.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./babel": {
17
+ "types": "./dist/babel.d.ts",
18
+ "import": "./dist/babel.js",
19
+ "require": "./dist/babel.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "sideEffects": false,
26
+ "scripts": {
27
+ "build": "tsup src/index.ts src/babel.ts --format esm,cjs --dts --external @babel/core --clean"
28
+ },
29
+ "dependencies": {
30
+ "@cssxio/compiler": "workspace:*"
31
+ },
32
+ "peerDependencies": {
33
+ "@babel/core": "^7.28.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "@babel/core": {
37
+ "optional": true
38
+ }
39
+ }
40
+ }