react-native-css 0.0.0-nightly.d6f42ab → 0.0.0-nightly.da5ce0f

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.
Files changed (74) hide show
  1. package/README.md +5 -5
  2. package/dist/commonjs/compiler/declarations.js +51 -0
  3. package/dist/commonjs/compiler/declarations.js.map +1 -1
  4. package/dist/commonjs/compiler/lightningcss-loader.js +12 -2
  5. package/dist/commonjs/compiler/lightningcss-loader.js.map +1 -1
  6. package/dist/commonjs/compiler/supports.js +3 -1
  7. package/dist/commonjs/compiler/supports.js.map +1 -1
  8. package/dist/commonjs/components/SafeAreaProvider.js +13 -0
  9. package/dist/commonjs/components/SafeAreaProvider.js.map +1 -0
  10. package/dist/commonjs/native/react/useNativeCss.js +11 -5
  11. package/dist/commonjs/native/react/useNativeCss.js.map +1 -1
  12. package/dist/commonjs/native/styles/functions/color-mix.js +50 -0
  13. package/dist/commonjs/native/styles/functions/color-mix.js.map +1 -0
  14. package/dist/commonjs/native/styles/functions/index.js +11 -0
  15. package/dist/commonjs/native/styles/functions/index.js.map +1 -1
  16. package/dist/commonjs/native/styles/index.js +247 -19
  17. package/dist/commonjs/native/styles/index.js.map +1 -1
  18. package/dist/commonjs/web/api.js +1 -1
  19. package/dist/commonjs/web/api.js.map +1 -1
  20. package/dist/module/compiler/declarations.js +50 -0
  21. package/dist/module/compiler/declarations.js.map +1 -1
  22. package/dist/module/compiler/lightningcss-loader.js +12 -2
  23. package/dist/module/compiler/lightningcss-loader.js.map +1 -1
  24. package/dist/module/compiler/supports.js +3 -1
  25. package/dist/module/compiler/supports.js.map +1 -1
  26. package/dist/module/components/SafeAreaProvider.js +4 -0
  27. package/dist/module/components/SafeAreaProvider.js.map +1 -0
  28. package/dist/module/native/react/useNativeCss.js +11 -5
  29. package/dist/module/native/react/useNativeCss.js.map +1 -1
  30. package/dist/module/native/styles/functions/color-mix.js +45 -0
  31. package/dist/module/native/styles/functions/color-mix.js.map +1 -0
  32. package/dist/module/native/styles/functions/index.js +1 -0
  33. package/dist/module/native/styles/functions/index.js.map +1 -1
  34. package/dist/module/native/styles/index.js +248 -19
  35. package/dist/module/native/styles/index.js.map +1 -1
  36. package/dist/module/web/api.js +1 -1
  37. package/dist/module/web/api.js.map +1 -1
  38. package/dist/typescript/commonjs/.config/eslint.config.d.mts +1 -1
  39. package/dist/typescript/commonjs/example/src/App.d.ts.map +1 -1
  40. package/dist/typescript/commonjs/src/compiler/declarations.d.ts +1 -0
  41. package/dist/typescript/commonjs/src/compiler/declarations.d.ts.map +1 -1
  42. package/dist/typescript/commonjs/src/compiler/lightningcss-loader.d.ts.map +1 -1
  43. package/dist/typescript/commonjs/src/components/SafeAreaProvider.d.ts +2 -0
  44. package/dist/typescript/commonjs/src/components/SafeAreaProvider.d.ts.map +1 -0
  45. package/dist/typescript/commonjs/src/native/react/useNativeCss.d.ts.map +1 -1
  46. package/dist/typescript/commonjs/src/native/styles/functions/color-mix.d.ts +3 -0
  47. package/dist/typescript/commonjs/src/native/styles/functions/color-mix.d.ts.map +1 -0
  48. package/dist/typescript/commonjs/src/native/styles/functions/index.d.ts +1 -0
  49. package/dist/typescript/commonjs/src/native/styles/functions/index.d.ts.map +1 -1
  50. package/dist/typescript/commonjs/src/native/styles/index.d.ts.map +1 -1
  51. package/dist/typescript/module/.config/eslint.config.d.mts +1 -1
  52. package/dist/typescript/module/example/src/App.d.ts.map +1 -1
  53. package/dist/typescript/module/src/compiler/declarations.d.ts +1 -0
  54. package/dist/typescript/module/src/compiler/declarations.d.ts.map +1 -1
  55. package/dist/typescript/module/src/compiler/lightningcss-loader.d.ts.map +1 -1
  56. package/dist/typescript/module/src/components/SafeAreaProvider.d.ts +2 -0
  57. package/dist/typescript/module/src/components/SafeAreaProvider.d.ts.map +1 -0
  58. package/dist/typescript/module/src/native/react/useNativeCss.d.ts.map +1 -1
  59. package/dist/typescript/module/src/native/styles/functions/color-mix.d.ts +3 -0
  60. package/dist/typescript/module/src/native/styles/functions/color-mix.d.ts.map +1 -0
  61. package/dist/typescript/module/src/native/styles/functions/index.d.ts +1 -0
  62. package/dist/typescript/module/src/native/styles/functions/index.d.ts.map +1 -1
  63. package/dist/typescript/module/src/native/styles/index.d.ts.map +1 -1
  64. package/package.json +14 -1
  65. package/src/compiler/declarations.ts +96 -0
  66. package/src/compiler/lightningcss-loader.ts +20 -1
  67. package/src/compiler/supports.ts +2 -0
  68. package/src/components/SafeAreaProvider.tsx +1 -0
  69. package/src/native/react/useNativeCss.ts +13 -6
  70. package/src/native/styles/functions/color-mix.ts +68 -0
  71. package/src/native/styles/functions/index.ts +1 -0
  72. package/src/native/styles/index.ts +302 -26
  73. package/src/web/api.tsx +1 -1
  74. package/types.d.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-css",
3
- "version": "0.0.0-nightly.d6f42ab",
3
+ "version": "0.0.0-nightly.da5ce0f",
4
4
  "description": "A CSS polyfill for React Native",
5
5
  "exports": {
6
6
  ".": {
@@ -47,6 +47,18 @@
47
47
  "types": "./dist/typescript/commonjs/src/components/index.d.ts"
48
48
  }
49
49
  },
50
+ "./components/react-native-safe-area-context": {
51
+ "source": "./src/components/react-native-safe-area-context.native.tsx",
52
+ "react-native": "./src/components/react-native-safe-area-context.native.tsx",
53
+ "import": {
54
+ "types": "./dist/typescript/module/src/components/react-native-safe-area-context.d.ts",
55
+ "default": "./dist/module/components/react-native-safe-area-context.js"
56
+ },
57
+ "require": {
58
+ "types": "./dist/typescript/commonjs/src/components/react-native-safe-area-context.d.ts",
59
+ "default": "./dist/commonjs/components/react-native-safe-area-context.js"
60
+ }
61
+ },
50
62
  "./components/*": {
51
63
  "source": "./src/components/*.tsx",
52
64
  "react-native": "./src/components/*.tsx",
@@ -192,6 +204,7 @@
192
204
  },
193
205
  "peerDependencies": {
194
206
  "@expo/metro-config": ">=54",
207
+ "lightningcss": ">=1.27.0",
195
208
  "react": ">=19",
196
209
  "react-native": ">=0.81"
197
210
  },
@@ -1228,6 +1228,8 @@ export function parseUnparsed(
1228
1228
  builder,
1229
1229
  property,
1230
1230
  );
1231
+ case "color-mix":
1232
+ return parseColorMix(tokenOrValue.value.arguments, builder, property);
1231
1233
  default: {
1232
1234
  builder.addWarning("value", `${tokenOrValue.value.name}()`);
1233
1235
  return;
@@ -2633,6 +2635,100 @@ export function parseCalcFn(
2633
2635
  return;
2634
2636
  }
2635
2637
 
2638
+ export function parseColorMix(
2639
+ tokens: TokenOrValue[],
2640
+ builder: StylesheetBuilder,
2641
+ property: string,
2642
+ ): StyleDescriptor {
2643
+ const [inToken, whitespace, colorSpace, comma, ...rest] = tokens;
2644
+ if (
2645
+ typeof inToken !== "object" ||
2646
+ inToken.type !== "token" ||
2647
+ inToken.value.type !== "ident" ||
2648
+ inToken.value.value !== "in"
2649
+ ) {
2650
+ return;
2651
+ }
2652
+
2653
+ if (
2654
+ typeof whitespace !== "object" ||
2655
+ whitespace.type !== "token" ||
2656
+ whitespace.value.type !== "white-space"
2657
+ ) {
2658
+ return;
2659
+ }
2660
+
2661
+ if (
2662
+ typeof comma !== "object" ||
2663
+ comma.type !== "token" ||
2664
+ comma.value.type !== "comma"
2665
+ ) {
2666
+ return;
2667
+ }
2668
+
2669
+ const colorSpaceArg = parseUnparsed(colorSpace, builder, property);
2670
+ if (typeof colorSpaceArg !== "string") {
2671
+ return;
2672
+ }
2673
+
2674
+ let nextToken = rest.shift();
2675
+
2676
+ const leftColorArg = parseUnparsed(nextToken, builder, property);
2677
+
2678
+ if (!leftColorArg) {
2679
+ return;
2680
+ }
2681
+
2682
+ nextToken = rest.shift();
2683
+
2684
+ let leftColorPercentage: StyleDescriptor | undefined;
2685
+ if (nextToken?.type !== "token" || nextToken.value.type !== "comma") {
2686
+ leftColorPercentage = parseUnparsed(nextToken, builder, property);
2687
+ nextToken = rest.shift();
2688
+ }
2689
+
2690
+ if (
2691
+ typeof nextToken !== "object" ||
2692
+ nextToken.type !== "token" ||
2693
+ nextToken.value.type !== "comma"
2694
+ ) {
2695
+ return;
2696
+ }
2697
+
2698
+ nextToken = rest.shift();
2699
+
2700
+ const rightColorArg = parseUnparsed(nextToken, builder, property);
2701
+
2702
+ if (rightColorArg === "transparent") {
2703
+ // Ignore the rest, treat as single color with alpha
2704
+ return [{}, "colorMix", [colorSpaceArg, leftColorArg, leftColorPercentage]];
2705
+ }
2706
+
2707
+ nextToken = rest.shift();
2708
+ let rightColorPercentage: StyleDescriptor | undefined;
2709
+ if (nextToken?.type !== "token" || nextToken.value.type !== "comma") {
2710
+ rightColorPercentage = parseUnparsed(nextToken, builder, property);
2711
+ nextToken = rest.shift();
2712
+ }
2713
+
2714
+ // We should have expired all tokens now
2715
+ if (nextToken) {
2716
+ return;
2717
+ }
2718
+
2719
+ return [
2720
+ {},
2721
+ "colorMix",
2722
+ [
2723
+ colorSpaceArg,
2724
+ leftColorArg,
2725
+ leftColorPercentage,
2726
+ rightColorArg,
2727
+ rightColorPercentage,
2728
+ ],
2729
+ ];
2730
+ }
2731
+
2636
2732
  export function parseCalcArguments(
2637
2733
  [...args]: TokenOrValue[],
2638
2734
  builder: StylesheetBuilder,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
1
2
  export function lightningcssLoader() {
2
3
  let lightningcssPath: string | undefined;
3
4
 
@@ -28,11 +29,29 @@ export function lightningcssLoader() {
28
29
  );
29
30
  }
30
31
 
31
- // eslint-disable-next-line @typescript-eslint/no-require-imports
32
32
  const { transform: lightningcss, Features } = require(
33
33
  lightningcssPath,
34
34
  ) as typeof import("lightningcss");
35
35
 
36
+ try {
37
+ const lightningcssPackageJSONPath = require.resolve("../../package.json", {
38
+ paths: [lightningcssPath],
39
+ });
40
+
41
+ const packageJSON = require(lightningcssPackageJSONPath) as Record<
42
+ string,
43
+ unknown
44
+ >;
45
+
46
+ if (packageJSON.version === "1.30.2") {
47
+ throw new Error(
48
+ "[react-native-css] lightningcss version 1.30.2 has a critical bug that breaks compilation. Please pin the version of lightningcss to 1.30.1; or try upgrading.",
49
+ );
50
+ }
51
+ } catch {
52
+ // Intentionally left empty
53
+ }
54
+
36
55
  return {
37
56
  lightningcss,
38
57
  Features,
@@ -23,4 +23,6 @@ export function supportsConditionValid(condition: SupportsCondition): boolean {
23
23
  const declarations: Record<string, string[]> = {
24
24
  // We don't actually support this, but its needed for Tailwind CSS
25
25
  "-moz-orient": ["inline"],
26
+ // Special text used by TailwindCSS. We should probably change this to all color-mix
27
+ "color": ["color-mix(in lab, red, red)"],
26
28
  };
@@ -0,0 +1 @@
1
+ export { SafeAreaProvider } from "react-native-css/components/react-native-safe-area-context";
@@ -172,29 +172,36 @@ export function useNativeCss(
172
172
  export function mappingToConfig(mapping: StyledConfiguration<any>) {
173
173
  return Object.entries(mapping).flatMap(([key, value]): Config => {
174
174
  if (value === true) {
175
- return { source: key, target: key };
175
+ return {
176
+ source: key,
177
+ target: key,
178
+ };
176
179
  } else if (value === false) {
177
180
  return { source: key, target: false };
178
181
  } else if (typeof value === "string") {
179
182
  return { source: key, target: value.split(".") };
180
183
  } else if (typeof value === "object") {
184
+ const nativeStyleMapping = value.nativeStyleMapping as
185
+ | Record<string, string>
186
+ | undefined;
187
+
181
188
  if (Array.isArray(value)) {
182
- return { source: key, target: value };
189
+ return { source: key, target: value, nativeStyleMapping };
183
190
  }
184
191
 
185
192
  if ("target" in value) {
186
193
  if (value.target === false) {
187
- return { source: key, target: false };
194
+ return { source: key, target: false, nativeStyleMapping };
188
195
  } else if (typeof value.target === "string") {
189
196
  const target = value.target.split(".");
190
197
 
191
198
  if (target.length === 1) {
192
- return { source: key, target: target[0]! };
199
+ return { source: key, target: target[0]!, nativeStyleMapping };
193
200
  } else {
194
- return { source: key, target };
201
+ return { source: key, target, nativeStyleMapping };
195
202
  }
196
203
  } else if (Array.isArray(value.target)) {
197
- return { source: key, target: value.target };
204
+ return { source: key, target: value.target, nativeStyleMapping };
198
205
  }
199
206
  }
200
207
  }
@@ -0,0 +1,68 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ import type { PlainColorObject } from "colorjs.io";
3
+ import {
4
+ ColorSpace,
5
+ to as convert,
6
+ mix,
7
+ OKLab,
8
+ P3,
9
+ parse,
10
+ sRGB,
11
+ type ColorConstructor,
12
+ } from "colorjs.io/fn";
13
+
14
+ import type { StyleFunctionResolver } from "../resolve";
15
+
16
+ ColorSpace.register(sRGB);
17
+ ColorSpace.register(P3);
18
+ ColorSpace.register(OKLab);
19
+
20
+ export const colorMix: StyleFunctionResolver = (resolveValue, value) => {
21
+ const args = resolveValue(value[2]);
22
+
23
+ if (!Array.isArray(args) || args.length < 3) {
24
+ return;
25
+ }
26
+
27
+ try {
28
+ const space = args.shift();
29
+
30
+ let left: ColorConstructor | PlainColorObject = parse(
31
+ args.shift() as string,
32
+ );
33
+
34
+ let next = args.shift();
35
+
36
+ if (typeof next === "string" && next.endsWith("%")) {
37
+ left.alpha = parseFloat(next) / 100;
38
+ next = args.shift();
39
+ }
40
+
41
+ if (next === undefined) {
42
+ if (left.spaceId !== "srgb") {
43
+ left = convert(left, "srgb");
44
+ }
45
+
46
+ return `rgba(${(left.coords[0] ?? 0) * 255}, ${(left.coords[1] ?? 0) * 255}, ${(left.coords[2] ?? 0) * 255}, ${left.alpha})`;
47
+ }
48
+
49
+ if (typeof next !== "string") {
50
+ return;
51
+ }
52
+ const right = parse(next);
53
+
54
+ next = args.shift();
55
+ if (next && typeof next === "string" && next.endsWith("%")) {
56
+ right.alpha = parseFloat(next) / 100;
57
+ }
58
+
59
+ const result = mix(left, right, {
60
+ space,
61
+ outputSpace: "srgb",
62
+ });
63
+
64
+ return `rgba(${(result.coords[0] ?? 0) * 255}, ${(result.coords[1] ?? 0) * 255}, ${(result.coords[2] ?? 0) * 255}, ${result.alpha})`;
65
+ } catch {
66
+ return;
67
+ }
68
+ };
@@ -5,3 +5,4 @@ export * from "./numeric-functions";
5
5
  export * from "./platform-functions";
6
6
  export * from "./string-functions";
7
7
  export * from "./transform-functions";
8
+ export * from "./color-mix";
@@ -17,6 +17,149 @@ import {
17
17
  } from "../reactivity";
18
18
  import { calculateProps } from "./calculate-props";
19
19
 
20
+ /**
21
+ * Checks if the left style object has any properties that are not present in the right style object.
22
+ * This is used to determine if styles need to be preserved in an array or if right can completely override left.
23
+ *
24
+ * Note: This intentionally only checks one direction (left → right). We don't need to check if right has
25
+ * properties that left doesn't have, because right will always be applied/merged. The question we're
26
+ * answering is: "Does left have any properties that would be lost if we just used right?" If yes, we
27
+ * create a style array [left, right] to preserve both. If no, right can safely replace left entirely.
28
+ *
29
+ * @param left - The left style object to check
30
+ * @param right - The right style object to compare against
31
+ * @returns true if left has at least one property key that doesn't exist in right, false otherwise
32
+ *
33
+ * @example
34
+ * hasNonOverlappingProperties({color: 'red', fontSize: 12}, {color: 'blue'}) // true - fontSize is not in right
35
+ * hasNonOverlappingProperties({color: 'red'}, {color: 'blue', fontSize: 12}) // false - all left keys exist in right
36
+ */
37
+ function hasNonOverlappingProperties(
38
+ left: Record<string, any>,
39
+ right: Record<string, any>,
40
+ ): boolean {
41
+ // Null safety check
42
+ if (!left || !right) {
43
+ return false;
44
+ }
45
+
46
+ // Only check own properties to avoid prototype pollution
47
+ for (const key in left) {
48
+ if (Object.prototype.hasOwnProperty.call(left, key)) {
49
+ if (!Object.prototype.hasOwnProperty.call(right, key)) {
50
+ return true;
51
+ }
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+
57
+ /**
58
+ * Flattens a style array into a single object when possible, with rightmost values taking precedence.
59
+ * Returns the original array if it contains any non-plain objects, arrays, or CSS variable objects.
60
+ *
61
+ * Note: This function assumes the input array has already been filtered (e.g., by filterCssVariables),
62
+ * so empty arrays should not reach this function. If they do, they will be treated as non-flattenable.
63
+ *
64
+ * @param styleArray - The style array to potentially flatten
65
+ * @returns A single merged object if all items are plain objects, otherwise the original array
66
+ */
67
+ function flattenStyleArray(styleArray: any[]): any {
68
+ // Check if we can flatten to a single object (all items are plain objects)
69
+ for (const item of styleArray) {
70
+ // Use explicit null check instead of !item to allow falsy values like 0 or false
71
+ if (
72
+ item == null ||
73
+ typeof item !== "object" ||
74
+ Array.isArray(item) ||
75
+ Object.prototype.hasOwnProperty.call(item, VAR_SYMBOL)
76
+ ) {
77
+ return styleArray;
78
+ }
79
+ }
80
+
81
+ // Use reduce to avoid spread operator performance issues with large arrays
82
+ return styleArray.reduce((acc, item) => Object.assign(acc, item), {});
83
+ }
84
+
85
+ /**
86
+ * Recursively filters out CSS variable objects (with VAR_SYMBOL) from style values.
87
+ * This prevents CSS variable runtime objects from leaking into React Native component props.
88
+ *
89
+ * @param value - The value to filter (can be any type: object, array, primitive, etc.)
90
+ * @param depth - Internal recursion depth counter to prevent stack overflow (max 100)
91
+ * @returns The filtered value with CSS variables removed, or `undefined` if the entire value
92
+ * should be filtered out (e.g., empty arrays, objects with only VAR_SYMBOL properties)
93
+ *
94
+ * Filtering behavior:
95
+ * - Objects with VAR_SYMBOL property: returns `undefined` (completely filtered)
96
+ * - Arrays: filters out VAR_SYMBOL objects, returns `undefined` if empty after filtering
97
+ * - Objects: recursively filters properties, returns `undefined` if no properties remain
98
+ * - Primitives (null, undefined, numbers, strings, booleans): returned as-is
99
+ * - Symbol properties: intentionally filtered out for React Native compatibility
100
+ *
101
+ * @example
102
+ * filterCssVariables({fontSize: 16, color: {[VAR_SYMBOL]: true}}) // {fontSize: 16}
103
+ * filterCssVariables([{margin: 10}, {[VAR_SYMBOL]: true}]) // [{margin: 10}]
104
+ * filterCssVariables({color: {[VAR_SYMBOL]: true}}) // undefined (all props filtered)
105
+ */
106
+ function filterCssVariables(value: any, depth = 0): any | undefined {
107
+ // Prevent stack overflow on deeply nested structures
108
+ if (depth > 100) {
109
+ return value;
110
+ }
111
+
112
+ if (value === null || value === undefined) {
113
+ return value;
114
+ }
115
+
116
+ if (Array.isArray(value)) {
117
+ // Single-pass filter with map operation
118
+ const filtered: any[] = [];
119
+
120
+ for (const item of value) {
121
+ const filteredItem = filterCssVariables(item, depth + 1);
122
+ if (
123
+ filteredItem !== undefined &&
124
+ !(
125
+ typeof filteredItem === "object" &&
126
+ filteredItem !== null &&
127
+ Object.prototype.hasOwnProperty.call(filteredItem, VAR_SYMBOL)
128
+ )
129
+ ) {
130
+ filtered.push(filteredItem);
131
+ }
132
+ }
133
+
134
+ return filtered.length > 0 ? filtered : undefined;
135
+ }
136
+
137
+ if (typeof value === "object") {
138
+ // If the object itself has VAR_SYMBOL, filter it out (check own property only)
139
+ if (Object.prototype.hasOwnProperty.call(value, VAR_SYMBOL)) {
140
+ return undefined;
141
+ }
142
+
143
+ // Otherwise, filter VAR_SYMBOL properties from nested objects
144
+ const filtered: Record<string, any> = {};
145
+ let hasProperties = false;
146
+
147
+ // Use Object.keys to only iterate own string properties (not inherited, not Symbols)
148
+ // This intentionally filters out Symbol properties for React Native compatibility
149
+ for (const key of Object.keys(value)) {
150
+ const filteredValue = filterCssVariables(value[key], depth + 1);
151
+ if (filteredValue !== undefined) {
152
+ filtered[key] = filteredValue;
153
+ hasProperties = true;
154
+ }
155
+ }
156
+
157
+ return hasProperties ? filtered : undefined;
158
+ }
159
+
160
+ return value;
161
+ }
162
+
20
163
  export const stylesFamily = family(
21
164
  (
22
165
  hash: string,
@@ -49,10 +192,19 @@ export function getStyledProps(
49
192
  const styledProps = state.stylesObs?.get(state.styleEffect);
50
193
 
51
194
  for (const config of state.configs) {
52
- result = deepMergeConfig(config, styledProps?.normal, inline, true);
195
+ result = deepMergeConfig(
196
+ config,
197
+ nativeStyleMapping(config, styledProps?.normal),
198
+ inline,
199
+ true,
200
+ );
53
201
 
54
202
  if (styledProps?.important) {
55
- result = deepMergeConfig(config, result, styledProps.important);
203
+ result = deepMergeConfig(
204
+ config,
205
+ result,
206
+ nativeStyleMapping(config, styledProps.important),
207
+ );
56
208
  }
57
209
 
58
210
  // Apply the handlers
@@ -122,11 +274,96 @@ function deepMergeConfig(
122
274
  right: Record<string, any> | undefined | null,
123
275
  rightIsInline = false,
124
276
  ) {
125
- if (!config.target || !right) {
277
+ if (!right) {
126
278
  return { ...left };
127
279
  }
128
280
 
129
- let result = Object.assign({}, left, right);
281
+ // Handle style merging to support both className and inline style props
282
+ let result: Record<string, any>;
283
+ if (config.target) {
284
+ if (
285
+ Array.isArray(config.target) &&
286
+ config.target.length === 1 &&
287
+ config.target[0] === "style"
288
+ ) {
289
+ // Special handling for style target when we have inline styles
290
+ result = { ...left, ...right };
291
+
292
+ // Handle null/undefined inline styles
293
+ if (right?.style === null || right?.style === undefined) {
294
+ if (left?.style) {
295
+ result.style = left.style;
296
+ }
297
+ } else if (rightIsInline && right?.style) {
298
+ // Filter inline styles if rightIsInline is true
299
+ const filteredRightStyle = filterCssVariables(right.style);
300
+
301
+ if (left?.style) {
302
+ if (!filteredRightStyle) {
303
+ // All inline styles were CSS variables, only use left
304
+ result.style = left.style;
305
+ } else {
306
+ const leftStyle = left.style;
307
+
308
+ // For arrays or objects, check if we need to create a style array
309
+ const leftIsObject =
310
+ typeof leftStyle === "object" && !Array.isArray(leftStyle);
311
+ const rightIsObject =
312
+ typeof filteredRightStyle === "object" &&
313
+ !Array.isArray(filteredRightStyle);
314
+
315
+ if (leftIsObject && rightIsObject) {
316
+ if (hasNonOverlappingProperties(leftStyle, filteredRightStyle)) {
317
+ result.style = [leftStyle, filteredRightStyle];
318
+ } else {
319
+ // All left properties are in right, right overrides
320
+ result.style = filteredRightStyle;
321
+ }
322
+ } else {
323
+ // One or both are arrays, merge them
324
+ result.style = [leftStyle, filteredRightStyle];
325
+ }
326
+ }
327
+ } else {
328
+ // No left style, just use filtered right
329
+ if (filteredRightStyle) {
330
+ result.style = filteredRightStyle;
331
+ } else {
332
+ // All filtered out, remove style prop
333
+ delete result.style;
334
+ }
335
+ }
336
+ } else if (!rightIsInline && right?.style) {
337
+ // Merging non-inline styles (e.g., important styles)
338
+ if (left?.style) {
339
+ // If left.style is an array, append right.style
340
+ if (Array.isArray(left.style)) {
341
+ const combined = [...left.style, right.style];
342
+ result.style = flattenStyleArray(combined);
343
+ } else if (
344
+ typeof left.style === "object" &&
345
+ typeof right.style === "object"
346
+ ) {
347
+ // Both are objects, check for overlaps
348
+ if (hasNonOverlappingProperties(left.style, right.style)) {
349
+ result.style = flattenStyleArray([left.style, right.style]);
350
+ } else {
351
+ // All left properties are overridden by right
352
+ result.style = right.style;
353
+ }
354
+ } else {
355
+ // One or both are arrays/mixed types
356
+ const combined = [left.style, right.style];
357
+ result.style = flattenStyleArray(combined);
358
+ }
359
+ }
360
+ }
361
+ } else {
362
+ result = Object.assign({}, left, right);
363
+ }
364
+ } else {
365
+ result = Object.assign({}, left, right);
366
+ }
130
367
 
131
368
  if (
132
369
  right &&
@@ -140,7 +377,7 @@ function deepMergeConfig(
140
377
  /**
141
378
  * If target is a path, deep merge until we get to the last key
142
379
  */
143
- if (Array.isArray(config.target) && config.target.length > 1) {
380
+ if (Array.isArray(config.target)) {
144
381
  for (let i = 0; i < config.target.length - 1; i++) {
145
382
  const key = config.target[i];
146
383
 
@@ -159,34 +396,17 @@ function deepMergeConfig(
159
396
  return result;
160
397
  }
161
398
 
162
- const target = Array.isArray(config.target)
163
- ? config.target[0]
164
- : config.target;
399
+ const target = config.target;
165
400
 
166
- if (target === undefined) {
401
+ if (target === undefined || target === false) {
167
402
  return result;
168
403
  }
169
404
 
170
405
  let rightValue = right?.[target];
171
406
 
172
407
  // Strip any inline variables from the target
173
- if (rightIsInline && rightValue) {
174
- if (Array.isArray(rightValue)) {
175
- rightValue = rightValue.filter((v) => {
176
- return typeof v !== "object" || !(v && VAR_SYMBOL in v);
177
- });
178
-
179
- if (rightValue.length === 0) {
180
- rightValue = undefined;
181
- }
182
- } else if (
183
- typeof rightValue === "object" &&
184
- rightValue &&
185
- VAR_SYMBOL in rightValue
186
- ) {
187
- rightValue = undefined;
188
- delete result[target][VAR_SYMBOL];
189
- }
408
+ if (rightIsInline && rightValue !== undefined) {
409
+ rightValue = filterCssVariables(rightValue);
190
410
  }
191
411
 
192
412
  if (rightValue !== undefined) {
@@ -196,3 +416,59 @@ function deepMergeConfig(
196
416
 
197
417
  return result;
198
418
  }
419
+
420
+ function nativeStyleMapping(
421
+ config: Config,
422
+ props: Record<string, any> | undefined,
423
+ ) {
424
+ if (!config.nativeStyleMapping || !props) {
425
+ return props;
426
+ }
427
+
428
+ let source: Record<string, any> | undefined;
429
+
430
+ if (typeof config.target === "string") {
431
+ source = props[config.target];
432
+ } else if (config.target === false) {
433
+ source = props["style"];
434
+ } else {
435
+ const tokens = [...config.target];
436
+ const lastToken = tokens.pop()!;
437
+
438
+ source = props;
439
+ for (const token of tokens) {
440
+ source = source[token];
441
+ if (!source) {
442
+ return props;
443
+ }
444
+ }
445
+
446
+ source = source[lastToken];
447
+ }
448
+
449
+ if (!source) {
450
+ return props;
451
+ }
452
+
453
+ for (const [key, path] of Object.entries(config.nativeStyleMapping)) {
454
+ const styleValue = source[key];
455
+
456
+ delete source[key];
457
+
458
+ if (styleValue === undefined) {
459
+ continue;
460
+ }
461
+
462
+ let target = props;
463
+ const tokens = path.split(".");
464
+ const lastToken = tokens.pop();
465
+ for (const token of tokens) {
466
+ target[token] ??= {};
467
+ target = target[token];
468
+ }
469
+
470
+ target[lastToken!] = styleValue;
471
+ }
472
+
473
+ return props;
474
+ }
package/src/web/api.tsx CHANGED
@@ -31,7 +31,7 @@ export const styled = <
31
31
  _options?: StyledOptions,
32
32
  ) => {
33
33
  return (props: StyledProps<ComponentPropsWithRef<C>, M>) => {
34
- return useCssElement(baseComponent, mapping, props);
34
+ return useCssElement(baseComponent, props, mapping);
35
35
  };
36
36
  };
37
37