@niibase/uniwind 1.5.1 โ†’ 1.6.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/common/components/web/generateDataSet.js +2 -1
  3. package/dist/common/core/config/config.common.js +6 -3
  4. package/dist/common/core/web/getWebStyles.js +12 -1
  5. package/dist/common/hoc/withUniwind.js +7 -4
  6. package/dist/common/hoc/withUniwind.native.js +4 -4
  7. package/dist/common/hooks/useResolveClassNames.js +1 -1
  8. package/dist/metro/metro-transformer.cjs +42 -7
  9. package/dist/metro/metro-transformer.mjs +42 -7
  10. package/dist/module/components/web/generateDataSet.d.ts +1 -1
  11. package/dist/module/components/web/generateDataSet.js +2 -1
  12. package/dist/module/core/config/config.common.d.ts +2 -1
  13. package/dist/module/core/config/config.common.js +6 -3
  14. package/dist/module/core/web/getWebStyles.d.ts +1 -1
  15. package/dist/module/core/web/getWebStyles.js +12 -1
  16. package/dist/module/hoc/withUniwind.js +7 -4
  17. package/dist/module/hoc/withUniwind.native.js +4 -4
  18. package/dist/module/hooks/useResolveClassNames.js +2 -2
  19. package/dist/module/hooks/useUniwind.d.ts +2 -1
  20. package/dist/module/index.d.ts +1 -0
  21. package/package.json +3 -2
  22. package/src/components/web/generateDataSet.ts +4 -2
  23. package/src/components/web/rnw.ts +1 -1
  24. package/src/core/config/config.common.ts +7 -3
  25. package/src/core/web/getWebStyles.ts +20 -1
  26. package/src/hoc/withUniwind.native.tsx +4 -4
  27. package/src/hoc/withUniwind.tsx +5 -2
  28. package/src/hooks/useResolveClassNames.ts +2 -2
  29. package/src/hooks/useUniwind.ts +2 -2
  30. package/src/index.ts +1 -0
  31. package/src/metro/processor/color.ts +2 -1
  32. package/src/metro/processor/css.ts +35 -0
  33. package/src/metro/utils/serialize.ts +13 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,48 @@
1
+ ## What's Changed in 1.6.1
2
+
3
+
4
+
5
+ ### ๐Ÿ› Bug Fixes
6
+
7
+ * pass data attributes to withUniwind (#477) by @Brentlok
8
+ * p3 color parsing (#476) by @Brentlok
9
+
10
+ ### ๐Ÿงช Testing
11
+
12
+ * advanced type check for ThemeName (#465) by @Brentlok
13
+
14
+ ### ๐Ÿ“ฆ Other
15
+
16
+ * Merge branch 'prepare-release' by @divineniiquaye
17
+
18
+
19
+ **Full Changelog**: https://github.com/divineniiquaye/uniwind/compare/v1.6.0...v1.6.1
20
+
21
+
22
+ ## What's Changed in 1.6.0
23
+
24
+
25
+
26
+ ### ๐Ÿš€ Features
27
+
28
+ * export ThemeName and make themes public in Uniwind class (#460) by @Brentlok
29
+
30
+ ### ๐Ÿ› Bug Fixes
31
+
32
+ * parsing multiple transforms tokens in single class (#451) by @Brentlok
33
+
34
+ ### ๐Ÿ  Chores
35
+
36
+ * validate each entry in serialization instead of whole object (#456) by @Brentlok
37
+
38
+ ### ๐Ÿ“ฆ Other
39
+
40
+ * Merge branch 'orig' by @divineniiquaye
41
+
42
+
43
+ **Full Changelog**: https://github.com/divineniiquaye/uniwind/compare/v1.5.1...v1.6.0
44
+
45
+
1
46
  ## What's Changed in 1.5.1
2
47
 
3
48
 
@@ -4,13 +4,14 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.generateDataSet = void 0;
7
+ const toCamelCase = str => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
7
8
  const generateDataSet = props => {
8
9
  const dataSet = props.dataSet !== void 0 ? {
9
10
  ...props.dataSet
10
11
  } : {};
11
12
  Object.entries(props).forEach(([key, value]) => {
12
13
  if (key.startsWith("data-")) {
13
- dataSet[key.slice(5)] = value;
14
+ dataSet[toCamelCase(key.slice(5))] = value;
14
15
  }
15
16
  });
16
17
  return dataSet;
@@ -13,7 +13,7 @@ const SYSTEM_THEME = "system";
13
13
  const RN_VERSION = _reactNative.Platform.constants?.reactNativeVersion?.minor ?? 0;
14
14
  const UNSPECIFIED_THEME = RN_VERSION >= 82 ? "unspecified" : void 0;
15
15
  class UniwindConfigBuilder {
16
- themes = ["light", "dark"];
16
+ _themes = ["light", "dark"];
17
17
  #hasAdaptiveThemes = true;
18
18
  #currentTheme = this.colorScheme;
19
19
  constructor() {
@@ -27,6 +27,9 @@ class UniwindConfigBuilder {
27
27
  }
28
28
  });
29
29
  }
30
+ get themes() {
31
+ return this._themes;
32
+ }
30
33
  get hasAdaptiveThemes() {
31
34
  return this.#hasAdaptiveThemes;
32
35
  }
@@ -61,7 +64,7 @@ class UniwindConfigBuilder {
61
64
  }
62
65
  return;
63
66
  }
64
- if (!this.themes.includes(theme)) {
67
+ if (!this._themes.includes(theme)) {
65
68
  throw new Error(`Uniwind: You're trying to setTheme to '${theme}', but it was not registered.`);
66
69
  }
67
70
  this.#hasAdaptiveThemes = false;
@@ -89,7 +92,7 @@ class UniwindConfigBuilder {
89
92
  insets;
90
93
  }
91
94
  __reinit(_, themes) {
92
- this.themes = themes;
95
+ this._themes = themes;
93
96
  }
94
97
  onThemeChange() {}
95
98
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getWebVariable = exports.getWebStyles = void 0;
7
+ var _generateDataSet = require("../../components/web/generateDataSet");
7
8
  var _cssListener = require("./cssListener");
8
9
  var _parseCSSValue = require("./parseCSSValue");
9
10
  const dummyParent = typeof document !== "undefined" ? Object.assign(document.createElement("div"), {
@@ -38,7 +39,7 @@ const getActiveStylesForClass = className => {
38
39
  });
39
40
  return extractedStyles;
40
41
  };
41
- const getWebStyles = (className, uniwindContext) => {
42
+ const getWebStyles = (className, componentProps, uniwindContext) => {
42
43
  if (className === void 0) {
43
44
  return {};
44
45
  }
@@ -51,7 +52,17 @@ const getWebStyles = (className, uniwindContext) => {
51
52
  dummyParent?.removeAttribute("class");
52
53
  }
53
54
  dummy.className = className;
55
+ const dataSet = (0, _generateDataSet.generateDataSet)(componentProps ?? {});
56
+ Object.entries(dataSet).forEach(([key, value]) => {
57
+ if (value === false || value === void 0) {
58
+ return;
59
+ }
60
+ dummy.dataset[key] = String(value);
61
+ });
54
62
  const computedStyles = getActiveStylesForClass(className);
63
+ Object.keys(dataSet).forEach(key => {
64
+ delete dummy.dataset[key];
65
+ });
55
66
  return Object.fromEntries(Object.entries(computedStyles).map(([key, value]) => {
56
67
  const parsedKey = key[0] === "-" ? key : key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
57
68
  return [parsedKey, (0, _parseCSSValue.parseCSSValue)(value)];
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.withUniwind = void 0;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _react = require("react");
9
+ var _generateDataSet = require("../components/web/generateDataSet");
9
10
  var _context = require("../core/context");
10
11
  var _web = require("../core/web");
11
12
  var _withUniwindUtils = require("./withUniwindUtils");
@@ -23,7 +24,7 @@ const withAutoUniwind = Component2 => props => {
23
24
  return acc;
24
25
  }
25
26
  const className = propValue;
26
- const color = (0, _web.getWebStyles)(className, uniwindContext).accentColor;
27
+ const color = (0, _web.getWebStyles)(className, props, uniwindContext).accentColor;
27
28
  acc.generatedProps[colorProp] = color !== void 0 ? (0, _web.formatColor)(color) : void 0;
28
29
  acc.classNames += `${className} `;
29
30
  return acc;
@@ -54,7 +55,8 @@ const withAutoUniwind = Component2 => props => {
54
55
  }, [classNames]);
55
56
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(Component2, {
56
57
  ...props,
57
- ...generatedProps
58
+ ...generatedProps,
59
+ dataSet: (0, _generateDataSet.generateDataSet)(props)
58
60
  });
59
61
  };
60
62
  const withManualUniwind = (Component2, options) => props => {
@@ -71,7 +73,7 @@ const withManualUniwind = (Component2, options) => props => {
71
73
  if (props[propName] !== void 0) {
72
74
  return acc;
73
75
  }
74
- const value = (0, _web.getWebStyles)(className, uniwindContext)[option.styleProperty];
76
+ const value = (0, _web.getWebStyles)(className, props, uniwindContext)[option.styleProperty];
75
77
  const transformedValue = value !== void 0 && option.styleProperty.toLowerCase().includes("color") ? (0, _web.formatColor)(value) : value;
76
78
  acc.classNames += `${className} `;
77
79
  acc.generatedProps[propName] = transformedValue;
@@ -93,6 +95,7 @@ const withManualUniwind = (Component2, options) => props => {
93
95
  }, [classNames]);
94
96
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(Component2, {
95
97
  ...props,
96
- ...generatedProps
98
+ ...generatedProps,
99
+ dataSet: (0, _generateDataSet.generateDataSet)(props)
97
100
  });
98
101
  };
@@ -26,7 +26,7 @@ const withAutoUniwind = Component2 => props => {
26
26
  const {
27
27
  styles,
28
28
  dependencies: dependencies2
29
- } = _native.UniwindStore.getStyles(propValue, void 0, void 0, uniwindContext);
29
+ } = _native.UniwindStore.getStyles(propValue, props, void 0, uniwindContext);
30
30
  acc.dependencies.push(...dependencies2);
31
31
  acc.generatedProps[colorProp] = styles.accentColor;
32
32
  return acc;
@@ -36,7 +36,7 @@ const withAutoUniwind = Component2 => props => {
36
36
  const {
37
37
  styles,
38
38
  dependencies: dependencies2
39
- } = _native.UniwindStore.getStyles(propValue, void 0, void 0, uniwindContext);
39
+ } = _native.UniwindStore.getStyles(propValue, props, void 0, uniwindContext);
40
40
  acc.dependencies.push(...dependencies2);
41
41
  acc.generatedProps[styleProp] ??= [];
42
42
  acc.generatedProps[styleProp][0] = styles;
@@ -83,7 +83,7 @@ const withManualUniwind = (Component2, options) => props => {
83
83
  const {
84
84
  styles: styles2,
85
85
  dependencies: dependencies3
86
- } = _native.UniwindStore.getStyles(className, void 0, void 0, uniwindContext);
86
+ } = _native.UniwindStore.getStyles(className, props, void 0, uniwindContext);
87
87
  acc.generatedProps[propName] = styles2[option.styleProperty];
88
88
  acc.dependencies.push(...dependencies3);
89
89
  return acc;
@@ -91,7 +91,7 @@ const withManualUniwind = (Component2, options) => props => {
91
91
  const {
92
92
  styles,
93
93
  dependencies: dependencies2
94
- } = _native.UniwindStore.getStyles(className, void 0, void 0, uniwindContext);
94
+ } = _native.UniwindStore.getStyles(className, props, void 0, uniwindContext);
95
95
  acc.dependencies.push(...dependencies2);
96
96
  if (!(0, _withUniwindUtils.isStyleProperty)(propName)) {
97
97
  acc.generatedProps[propName] = styles;
@@ -10,7 +10,7 @@ var _web = require("../core/web");
10
10
  const emptyState = {};
11
11
  const useResolveClassNames = className => {
12
12
  const uniwindContext = (0, _context.useUniwindContext)();
13
- const [styles, recreate] = (0, _react.useReducer)(() => className !== "" ? (0, _web.getWebStyles)(className, uniwindContext) : emptyState, void 0, () => className !== "" ? (0, _web.getWebStyles)(className, uniwindContext) : emptyState);
13
+ const [styles, recreate] = (0, _react.useReducer)(() => className !== "" ? (0, _web.getWebStyles)(className, void 0, uniwindContext) : emptyState, void 0, () => className !== "" ? (0, _web.getWebStyles)(className, void 0, uniwindContext) : emptyState);
14
14
  (0, _react.useLayoutEffect)(() => {
15
15
  if (className === "") {
16
16
  return;
@@ -94,6 +94,15 @@ const serializeJSObject = (obj, serializer) => {
94
94
  const serializedObject = common.pipe(obj)(
95
95
  Object.entries,
96
96
  (entries) => entries.map(([key, value]) => serializer(key, serialize(value))),
97
+ (serializedValues) => serializedValues.filter((serializedValue) => {
98
+ try {
99
+ new Function(`function validateJS() { const obj = ({ ${serializedValue} }) }`);
100
+ return true;
101
+ } catch {
102
+ stringifyThemes.Logger.error(`Failed to serialize ${serializedValue} as a valid JS object entry. Skipping.`);
103
+ return false;
104
+ }
105
+ }),
97
106
  (entries) => entries.join(","),
98
107
  (result) => {
99
108
  if (result === "") {
@@ -102,12 +111,6 @@ const serializeJSObject = (obj, serializer) => {
102
111
  return `${result},`;
103
112
  }
104
113
  );
105
- try {
106
- new Function(`function validateJS() { const obj = ({ ${serializedObject} }) }`);
107
- } catch {
108
- stringifyThemes.Logger.error("Failed to serialize javascript object");
109
- return "";
110
- }
111
114
  return serializedObject;
112
115
  };
113
116
 
@@ -337,8 +340,9 @@ class Color {
337
340
  mode: "rgb"
338
341
  });
339
342
  }
343
+ const colorType = color.type === "display-p3" ? "p3" : color.type;
340
344
  const result = this.toRgb({
341
- mode: color.type,
345
+ mode: colorType,
342
346
  ...color
343
347
  });
344
348
  return this.format(result);
@@ -842,6 +846,12 @@ class CSS {
842
846
  if (declarationValue.length === 1) {
843
847
  return this.processValue(declarationValue[0]);
844
848
  }
849
+ if (this.isTransformArray(declarationValue)) {
850
+ return declarationValue.flatMap((value) => {
851
+ const result = this.processValue(value);
852
+ return Array.isArray(result) ? result : [result];
853
+ });
854
+ }
845
855
  return this.addComaBetweenTokens(declarationValue).reduce((acc, value, index, array) => {
846
856
  if (typeof value === "object") {
847
857
  const nextValue = array.at(index + 1);
@@ -949,6 +959,31 @@ class CSS {
949
959
  this.logUnsupported(`Unsupported value - ${JSON.stringify(declarationValue)}`);
950
960
  return void 0;
951
961
  }
962
+ // eslint-disable-next-line @typescript-eslint/member-ordering
963
+ static TRANSFORM_TYPES = /* @__PURE__ */ new Set([
964
+ "translate",
965
+ "translateX",
966
+ "translateY",
967
+ "translateZ",
968
+ "rotate",
969
+ "rotateX",
970
+ "rotateY",
971
+ "rotateZ",
972
+ "scale",
973
+ "scaleX",
974
+ "scaleY",
975
+ "scaleZ",
976
+ "skew",
977
+ "skewX",
978
+ "skewY",
979
+ "matrix",
980
+ "perspective"
981
+ ]);
982
+ isTransformArray(values) {
983
+ return values.every(
984
+ (value) => typeof value === "object" && value !== null && "type" in value && CSS.TRANSFORM_TYPES.has(value.type)
985
+ );
986
+ }
952
987
  isOverflow(value) {
953
988
  return typeof value === "object" && "x" in value && ["hidden", "visible"].includes(value.x);
954
989
  }
@@ -87,6 +87,15 @@ const serializeJSObject = (obj, serializer) => {
87
87
  const serializedObject = pipe(obj)(
88
88
  Object.entries,
89
89
  (entries) => entries.map(([key, value]) => serializer(key, serialize(value))),
90
+ (serializedValues) => serializedValues.filter((serializedValue) => {
91
+ try {
92
+ new Function(`function validateJS() { const obj = ({ ${serializedValue} }) }`);
93
+ return true;
94
+ } catch {
95
+ Logger.error(`Failed to serialize ${serializedValue} as a valid JS object entry. Skipping.`);
96
+ return false;
97
+ }
98
+ }),
90
99
  (entries) => entries.join(","),
91
100
  (result) => {
92
101
  if (result === "") {
@@ -95,12 +104,6 @@ const serializeJSObject = (obj, serializer) => {
95
104
  return `${result},`;
96
105
  }
97
106
  );
98
- try {
99
- new Function(`function validateJS() { const obj = ({ ${serializedObject} }) }`);
100
- } catch {
101
- Logger.error("Failed to serialize javascript object");
102
- return "";
103
- }
104
107
  return serializedObject;
105
108
  };
106
109
 
@@ -330,8 +333,9 @@ class Color {
330
333
  mode: "rgb"
331
334
  });
332
335
  }
336
+ const colorType = color.type === "display-p3" ? "p3" : color.type;
333
337
  const result = this.toRgb({
334
- mode: color.type,
338
+ mode: colorType,
335
339
  ...color
336
340
  });
337
341
  return this.format(result);
@@ -835,6 +839,12 @@ class CSS {
835
839
  if (declarationValue.length === 1) {
836
840
  return this.processValue(declarationValue[0]);
837
841
  }
842
+ if (this.isTransformArray(declarationValue)) {
843
+ return declarationValue.flatMap((value) => {
844
+ const result = this.processValue(value);
845
+ return Array.isArray(result) ? result : [result];
846
+ });
847
+ }
838
848
  return this.addComaBetweenTokens(declarationValue).reduce((acc, value, index, array) => {
839
849
  if (typeof value === "object") {
840
850
  const nextValue = array.at(index + 1);
@@ -942,6 +952,31 @@ class CSS {
942
952
  this.logUnsupported(`Unsupported value - ${JSON.stringify(declarationValue)}`);
943
953
  return void 0;
944
954
  }
955
+ // eslint-disable-next-line @typescript-eslint/member-ordering
956
+ static TRANSFORM_TYPES = /* @__PURE__ */ new Set([
957
+ "translate",
958
+ "translateX",
959
+ "translateY",
960
+ "translateZ",
961
+ "rotate",
962
+ "rotateX",
963
+ "rotateY",
964
+ "rotateZ",
965
+ "scale",
966
+ "scaleX",
967
+ "scaleY",
968
+ "scaleZ",
969
+ "skew",
970
+ "skewX",
971
+ "skewY",
972
+ "matrix",
973
+ "perspective"
974
+ ]);
975
+ isTransformArray(values) {
976
+ return values.every(
977
+ (value) => typeof value === "object" && value !== null && "type" in value && CSS.TRANSFORM_TYPES.has(value.type)
978
+ );
979
+ }
945
980
  isOverflow(value) {
946
981
  return typeof value === "object" && "x" in value && ["hidden", "visible"].includes(value.x);
947
982
  }
@@ -1,5 +1,5 @@
1
1
  export declare const generateDataSet: (props: Record<PropertyKey, any>) => DataSet;
2
- type DataSet = Record<string, string | boolean>;
2
+ type DataSet = Record<string, string | boolean | undefined>;
3
3
  declare module 'react-native' {
4
4
  interface SwitchProps {
5
5
  dataSet?: DataSet;
@@ -1,8 +1,9 @@
1
+ const toCamelCase = (str) => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1
2
  export const generateDataSet = (props) => {
2
3
  const dataSet = props.dataSet !== void 0 ? { ...props.dataSet } : {};
3
4
  Object.entries(props).forEach(([key, value]) => {
4
5
  if (key.startsWith("data-")) {
5
- dataSet[key.slice(5)] = value;
6
+ dataSet[toCamelCase(key.slice(5))] = value;
6
7
  }
7
8
  });
8
9
  return dataSet;
@@ -5,8 +5,9 @@ import { CSSVariables, GenerateStyleSheetsCallback, ThemeName } from '../types';
5
5
  declare const SYSTEM_THEME: "system";
6
6
  export declare class UniwindConfigBuilder {
7
7
  #private;
8
- protected themes: string[];
8
+ protected _themes: string[];
9
9
  constructor();
10
+ get themes(): Array<ThemeName>;
10
11
  get hasAdaptiveThemes(): boolean;
11
12
  get currentTheme(): ThemeName;
12
13
  private get colorScheme();
@@ -7,7 +7,7 @@ const SYSTEM_THEME = "system";
7
7
  const RN_VERSION = Platform.constants?.reactNativeVersion?.minor ?? 0;
8
8
  const UNSPECIFIED_THEME = RN_VERSION >= 82 ? "unspecified" : void 0;
9
9
  export class UniwindConfigBuilder {
10
- themes = ["light", "dark"];
10
+ _themes = ["light", "dark"];
11
11
  #hasAdaptiveThemes = true;
12
12
  #currentTheme = this.colorScheme;
13
13
  constructor() {
@@ -21,6 +21,9 @@ export class UniwindConfigBuilder {
21
21
  }
22
22
  });
23
23
  }
24
+ get themes() {
25
+ return this._themes;
26
+ }
24
27
  get hasAdaptiveThemes() {
25
28
  return this.#hasAdaptiveThemes;
26
29
  }
@@ -52,7 +55,7 @@ export class UniwindConfigBuilder {
52
55
  }
53
56
  return;
54
57
  }
55
- if (!this.themes.includes(theme)) {
58
+ if (!this._themes.includes(theme)) {
56
59
  throw new Error(`Uniwind: You're trying to setTheme to '${theme}', but it was not registered.`);
57
60
  }
58
61
  this.#hasAdaptiveThemes = false;
@@ -81,7 +84,7 @@ export class UniwindConfigBuilder {
81
84
  insets;
82
85
  }
83
86
  __reinit(_, themes) {
84
- this.themes = themes;
87
+ this._themes = themes;
85
88
  }
86
89
  onThemeChange() {
87
90
  }
@@ -1,3 +1,3 @@
1
1
  import { RNStyle, UniwindContextType } from '../types';
2
- export declare const getWebStyles: (className: string | undefined, uniwindContext: UniwindContextType) => RNStyle;
2
+ export declare const getWebStyles: (className: string | undefined, componentProps: Record<string, unknown> | undefined, uniwindContext: UniwindContextType) => RNStyle;
3
3
  export declare const getWebVariable: (name: string, uniwindContext: UniwindContextType) => string | undefined;
@@ -1,3 +1,4 @@
1
+ import { generateDataSet } from "../../components/web/generateDataSet.js";
1
2
  import { CSSListener } from "./cssListener.js";
2
3
  import { parseCSSValue } from "./parseCSSValue.js";
3
4
  const dummyParent = typeof document !== "undefined" ? Object.assign(document.createElement("div"), {
@@ -33,7 +34,7 @@ const getActiveStylesForClass = (className) => {
33
34
  });
34
35
  return extractedStyles;
35
36
  };
36
- export const getWebStyles = (className, uniwindContext) => {
37
+ export const getWebStyles = (className, componentProps, uniwindContext) => {
37
38
  if (className === void 0) {
38
39
  return {};
39
40
  }
@@ -46,7 +47,17 @@ export const getWebStyles = (className, uniwindContext) => {
46
47
  dummyParent?.removeAttribute("class");
47
48
  }
48
49
  dummy.className = className;
50
+ const dataSet = generateDataSet(componentProps ?? {});
51
+ Object.entries(dataSet).forEach(([key, value]) => {
52
+ if (value === false || value === void 0) {
53
+ return;
54
+ }
55
+ dummy.dataset[key] = String(value);
56
+ });
49
57
  const computedStyles = getActiveStylesForClass(className);
58
+ Object.keys(dataSet).forEach((key) => {
59
+ delete dummy.dataset[key];
60
+ });
50
61
  return Object.fromEntries(
51
62
  Object.entries(computedStyles).map(([key, value]) => {
52
63
  const parsedKey = key[0] === "-" ? key : key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
@@ -1,5 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useReducer } from "react";
3
+ import { generateDataSet } from "../components/web/generateDataSet.js";
3
4
  import { useUniwindContext } from "../core/context.js";
4
5
  import { CSSListener, formatColor, getWebStyles } from "../core/web/index.js";
5
6
  import { classToColor, classToStyle, isClassProperty, isColorClassProperty, isStyleProperty } from "./withUniwindUtils.js";
@@ -13,7 +14,7 @@ const withAutoUniwind = (Component2) => (props) => {
13
14
  return acc;
14
15
  }
15
16
  const className = propValue;
16
- const color = getWebStyles(className, uniwindContext).accentColor;
17
+ const color = getWebStyles(className, props, uniwindContext).accentColor;
17
18
  acc.generatedProps[colorProp] = color !== void 0 ? formatColor(color) : void 0;
18
19
  acc.classNames += `${className} `;
19
20
  return acc;
@@ -40,7 +41,8 @@ const withAutoUniwind = (Component2) => (props) => {
40
41
  Component2,
41
42
  {
42
43
  ...props,
43
- ...generatedProps
44
+ ...generatedProps,
45
+ dataSet: generateDataSet(props)
44
46
  }
45
47
  );
46
48
  };
@@ -55,7 +57,7 @@ const withManualUniwind = (Component2, options) => (props) => {
55
57
  if (props[propName] !== void 0) {
56
58
  return acc;
57
59
  }
58
- const value = getWebStyles(className, uniwindContext)[option.styleProperty];
60
+ const value = getWebStyles(className, props, uniwindContext)[option.styleProperty];
59
61
  const transformedValue = value !== void 0 && option.styleProperty.toLowerCase().includes("color") ? formatColor(value) : value;
60
62
  acc.classNames += `${className} `;
61
63
  acc.generatedProps[propName] = transformedValue;
@@ -73,7 +75,8 @@ const withManualUniwind = (Component2, options) => (props) => {
73
75
  Component2,
74
76
  {
75
77
  ...props,
76
- ...generatedProps
78
+ ...generatedProps,
79
+ dataSet: generateDataSet(props)
77
80
  }
78
81
  );
79
82
  };
@@ -13,14 +13,14 @@ const withAutoUniwind = (Component2) => (props) => {
13
13
  if (props[colorProp] !== void 0) {
14
14
  return acc;
15
15
  }
16
- const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(propValue, void 0, void 0, uniwindContext);
16
+ const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(propValue, props, void 0, uniwindContext);
17
17
  acc.dependencies.push(...dependencies2);
18
18
  acc.generatedProps[colorProp] = styles.accentColor;
19
19
  return acc;
20
20
  }
21
21
  if (isClassProperty(propName)) {
22
22
  const styleProp = classToStyle(propName);
23
- const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(propValue, void 0, void 0, uniwindContext);
23
+ const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(propValue, props, void 0, uniwindContext);
24
24
  acc.dependencies.push(...dependencies2);
25
25
  acc.generatedProps[styleProp] ??= [];
26
26
  acc.generatedProps[styleProp][0] = styles;
@@ -61,12 +61,12 @@ const withManualUniwind = (Component2, options) => (props) => {
61
61
  if (props[propName] !== void 0) {
62
62
  return acc;
63
63
  }
64
- const { styles: styles2, dependencies: dependencies3 } = UniwindStore.getStyles(className, void 0, void 0, uniwindContext);
64
+ const { styles: styles2, dependencies: dependencies3 } = UniwindStore.getStyles(className, props, void 0, uniwindContext);
65
65
  acc.generatedProps[propName] = styles2[option.styleProperty];
66
66
  acc.dependencies.push(...dependencies3);
67
67
  return acc;
68
68
  }
69
- const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(className, void 0, void 0, uniwindContext);
69
+ const { styles, dependencies: dependencies2 } = UniwindStore.getStyles(className, props, void 0, uniwindContext);
70
70
  acc.dependencies.push(...dependencies2);
71
71
  if (!isStyleProperty(propName)) {
72
72
  acc.generatedProps[propName] = styles;
@@ -5,9 +5,9 @@ const emptyState = {};
5
5
  export const useResolveClassNames = (className) => {
6
6
  const uniwindContext = useUniwindContext();
7
7
  const [styles, recreate] = useReducer(
8
- () => className !== "" ? getWebStyles(className, uniwindContext) : emptyState,
8
+ () => className !== "" ? getWebStyles(className, void 0, uniwindContext) : emptyState,
9
9
  void 0,
10
- () => className !== "" ? getWebStyles(className, uniwindContext) : emptyState
10
+ () => className !== "" ? getWebStyles(className, void 0, uniwindContext) : emptyState
11
11
  );
12
12
  useLayoutEffect(() => {
13
13
  if (className === "") {
@@ -1,4 +1,5 @@
1
+ import { ThemeName } from '../core/types';
1
2
  export declare const useUniwind: () => {
2
- theme: string;
3
+ theme: ThemeName;
3
4
  hasAdaptiveThemes: boolean;
4
5
  };
@@ -1,5 +1,6 @@
1
1
  export * from './components/ScopedTheme';
2
2
  export { Uniwind } from './core';
3
+ export type { ThemeName } from './core/types';
3
4
  export { withUniwind } from './hoc';
4
5
  export type { ApplyUniwind, ApplyUniwindOptions } from './hoc/types';
5
6
  export { useCSSVariable, useResolveClassNames, useUniwind } from './hooks';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@niibase/uniwind",
4
- "version": "1.5.1",
4
+ "version": "1.6.1",
5
5
  "description": "A fork of Uniwind with Reanimated 4 support",
6
6
  "homepage": "https://uniwind.dev",
7
7
  "author": "Unistack",
@@ -21,6 +21,7 @@
21
21
  "postinstall": "node scripts/postinstall.mjs",
22
22
  "test:native": "jest --config jest.config.native.js",
23
23
  "test:web": "jest --config jest.config.web.js",
24
+ "test:types": "tsc --project tests/type-test/tsconfig.json",
24
25
  "test:e2e": "playwright test",
25
26
  "release": "release-it"
26
27
  },
@@ -121,7 +122,7 @@
121
122
  "ts-jest": "29.4.6",
122
123
  "typescript": "catalog:",
123
124
  "unbuild": "3.6.1",
124
- "vite": "7.3.1",
125
+ "vite": "catalog:",
125
126
  "esbuild": "0.27.3"
126
127
  }
127
128
  }
@@ -1,17 +1,19 @@
1
+ const toCamelCase = (str: string) => str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
2
+
1
3
  export const generateDataSet = (props: Record<PropertyKey, any>) => {
2
4
  const dataSet: DataSet = props.dataSet !== undefined ? { ...props.dataSet } : {}
3
5
 
4
6
  Object.entries(props).forEach(([key, value]) => {
5
7
  if (key.startsWith('data-')) {
6
8
  // Remove data- prefix
7
- dataSet[key.slice(5)] = value
9
+ dataSet[toCamelCase(key.slice(5))] = value
8
10
  }
9
11
  })
10
12
 
11
13
  return dataSet
12
14
  }
13
15
 
14
- type DataSet = Record<string, string | boolean>
16
+ type DataSet = Record<string, string | boolean | undefined>
15
17
 
16
18
  declare module 'react-native' {
17
19
  interface SwitchProps {
@@ -4,7 +4,7 @@ import { StyleDependency } from '../../types'
4
4
  import './metro-injected'
5
5
 
6
6
  type UniwindWithThemes = {
7
- themes: typeof Uniwind['themes']
7
+ themes: typeof Uniwind['_themes']
8
8
  }
9
9
 
10
10
  const addClassNameToRoot = () => {
@@ -13,7 +13,7 @@ const RN_VERSION = Platform.constants?.reactNativeVersion?.minor ?? 0
13
13
  const UNSPECIFIED_THEME = RN_VERSION >= 82 ? 'unspecified' : undefined
14
14
 
15
15
  export class UniwindConfigBuilder {
16
- protected themes = ['light', 'dark']
16
+ protected _themes = ['light', 'dark']
17
17
  #hasAdaptiveThemes = true
18
18
  #currentTheme = this.colorScheme as ThemeName
19
19
 
@@ -33,6 +33,10 @@ export class UniwindConfigBuilder {
33
33
  })
34
34
  }
35
35
 
36
+ get themes() {
37
+ return this._themes as Array<ThemeName>
38
+ }
39
+
36
40
  get hasAdaptiveThemes() {
37
41
  return this.#hasAdaptiveThemes
38
42
  }
@@ -77,7 +81,7 @@ export class UniwindConfigBuilder {
77
81
  return
78
82
  }
79
83
 
80
- if (!this.themes.includes(theme)) {
84
+ if (!this._themes.includes(theme)) {
81
85
  throw new Error(`Uniwind: You're trying to setTheme to '${theme}', but it was not registered.`)
82
86
  }
83
87
 
@@ -116,7 +120,7 @@ export class UniwindConfigBuilder {
116
120
  }
117
121
 
118
122
  protected __reinit(_: GenerateStyleSheetsCallback, themes: Array<string>) {
119
- this.themes = themes
123
+ this._themes = themes
120
124
  }
121
125
 
122
126
  protected onThemeChange() {
@@ -1,3 +1,4 @@
1
+ import { generateDataSet } from '../../components/web/generateDataSet'
1
2
  import { RNStyle, UniwindContextType } from '../types'
2
3
  import { CSSListener } from './cssListener'
3
4
  import { parseCSSValue } from './parseCSSValue'
@@ -52,7 +53,11 @@ const getActiveStylesForClass = (className: string) => {
52
53
  return extractedStyles
53
54
  }
54
55
 
55
- export const getWebStyles = (className: string | undefined, uniwindContext: UniwindContextType): RNStyle => {
56
+ export const getWebStyles = (
57
+ className: string | undefined,
58
+ componentProps: Record<string, unknown> | undefined,
59
+ uniwindContext: UniwindContextType,
60
+ ): RNStyle => {
56
61
  if (className === undefined) {
57
62
  return {}
58
63
  }
@@ -69,8 +74,22 @@ export const getWebStyles = (className: string | undefined, uniwindContext: Uniw
69
74
 
70
75
  dummy.className = className
71
76
 
77
+ const dataSet = generateDataSet(componentProps ?? {})
78
+
79
+ Object.entries(dataSet).forEach(([key, value]) => {
80
+ if (value === false || value === undefined) {
81
+ return
82
+ }
83
+
84
+ dummy.dataset[key] = String(value)
85
+ })
86
+
72
87
  const computedStyles = getActiveStylesForClass(className)
73
88
 
89
+ Object.keys(dataSet).forEach(key => {
90
+ delete dummy.dataset[key]
91
+ })
92
+
74
93
  return Object.fromEntries(
75
94
  Object.entries(computedStyles)
76
95
  .map(([key, value]) => {
@@ -26,7 +26,7 @@ const withAutoUniwind = (Component: Component<AnyObject>) => (props: AnyObject)
26
26
  return acc
27
27
  }
28
28
 
29
- const { styles, dependencies } = UniwindStore.getStyles(propValue, undefined, undefined, uniwindContext)
29
+ const { styles, dependencies } = UniwindStore.getStyles(propValue, props, undefined, uniwindContext)
30
30
 
31
31
  acc.dependencies.push(...dependencies)
32
32
  acc.generatedProps[colorProp] = styles.accentColor
@@ -36,7 +36,7 @@ const withAutoUniwind = (Component: Component<AnyObject>) => (props: AnyObject)
36
36
 
37
37
  if (isClassProperty(propName)) {
38
38
  const styleProp = classToStyle(propName)
39
- const { styles, dependencies } = UniwindStore.getStyles(propValue, undefined, undefined, uniwindContext)
39
+ const { styles, dependencies } = UniwindStore.getStyles(propValue, props, undefined, uniwindContext)
40
40
 
41
41
  acc.dependencies.push(...dependencies)
42
42
  acc.generatedProps[styleProp] ??= []
@@ -90,7 +90,7 @@ const withManualUniwind = (Component: Component<AnyObject>, options: Record<Prop
90
90
  return acc
91
91
  }
92
92
 
93
- const { styles, dependencies } = UniwindStore.getStyles(className, undefined, undefined, uniwindContext)
93
+ const { styles, dependencies } = UniwindStore.getStyles(className, props, undefined, uniwindContext)
94
94
 
95
95
  acc.generatedProps[propName] = styles[option.styleProperty]
96
96
  acc.dependencies.push(...dependencies)
@@ -98,7 +98,7 @@ const withManualUniwind = (Component: Component<AnyObject>, options: Record<Prop
98
98
  return acc
99
99
  }
100
100
 
101
- const { styles, dependencies } = UniwindStore.getStyles(className, undefined, undefined, uniwindContext)
101
+ const { styles, dependencies } = UniwindStore.getStyles(className, props, undefined, uniwindContext)
102
102
  acc.dependencies.push(...dependencies)
103
103
 
104
104
  if (!isStyleProperty(propName)) {
@@ -1,4 +1,5 @@
1
1
  import { ComponentProps, useLayoutEffect, useReducer } from 'react'
2
+ import { generateDataSet } from '../components/web/generateDataSet'
2
3
  import { useUniwindContext } from '../core/context'
3
4
  import { CSSListener, formatColor, getWebStyles } from '../core/web'
4
5
  import { AnyObject, Component, OptionMapping, WithUniwind } from './types'
@@ -26,7 +27,7 @@ const withAutoUniwind = (Component: Component<AnyObject>) => (props: AnyObject)
26
27
  }
27
28
 
28
29
  const className = propValue
29
- const color = getWebStyles(className, uniwindContext).accentColor
30
+ const color = getWebStyles(className, props, uniwindContext).accentColor
30
31
 
31
32
  acc.generatedProps[colorProp] = color !== undefined
32
33
  ? formatColor(color)
@@ -67,6 +68,7 @@ const withAutoUniwind = (Component: Component<AnyObject>) => (props: AnyObject)
67
68
  <Component
68
69
  {...props}
69
70
  {...generatedProps}
71
+ dataSet={generateDataSet(props)}
70
72
  />
71
73
  )
72
74
  }
@@ -87,7 +89,7 @@ const withManualUniwind = (Component: Component<AnyObject>, options: Record<Prop
87
89
  return acc
88
90
  }
89
91
 
90
- const value = getWebStyles(className, uniwindContext)[option.styleProperty]
92
+ const value = getWebStyles(className, props, uniwindContext)[option.styleProperty]
91
93
  const transformedValue = value !== undefined && option.styleProperty.toLowerCase().includes('color')
92
94
  ? formatColor(value as string)
93
95
  : value
@@ -115,6 +117,7 @@ const withManualUniwind = (Component: Component<AnyObject>, options: Record<Prop
115
117
  <Component
116
118
  {...props}
117
119
  {...generatedProps}
120
+ dataSet={generateDataSet(props)}
118
121
  />
119
122
  )
120
123
  }
@@ -8,9 +8,9 @@ const emptyState = {} as RNStyle
8
8
  export const useResolveClassNames = (className: string) => {
9
9
  const uniwindContext = useUniwindContext()
10
10
  const [styles, recreate] = useReducer(
11
- () => className !== '' ? getWebStyles(className, uniwindContext) : emptyState,
11
+ () => className !== '' ? getWebStyles(className, undefined, uniwindContext) : emptyState,
12
12
  undefined,
13
- () => className !== '' ? getWebStyles(className, uniwindContext) : emptyState,
13
+ () => className !== '' ? getWebStyles(className, undefined, uniwindContext) : emptyState,
14
14
  )
15
15
 
16
16
  useLayoutEffect(() => {
@@ -5,7 +5,7 @@ import { UniwindListener } from '../core/listener'
5
5
  import { ThemeName } from '../core/types'
6
6
  import { StyleDependency } from '../types'
7
7
 
8
- export const useUniwind = () => {
8
+ export const useUniwind = (): { theme: ThemeName; hasAdaptiveThemes: boolean } => {
9
9
  const uniwindContext = useUniwindContext()
10
10
  const [theme, setTheme] = useState(Uniwind.currentTheme)
11
11
  const [hasAdaptiveThemes, setHasAdaptiveThemes] = useState(Uniwind.hasAdaptiveThemes)
@@ -26,7 +26,7 @@ export const useUniwind = () => {
26
26
  }, [uniwindContext])
27
27
 
28
28
  return {
29
- theme: uniwindContext.scopedTheme ?? theme as ThemeName,
29
+ theme: uniwindContext.scopedTheme ?? theme,
30
30
  hasAdaptiveThemes: uniwindContext.scopedTheme !== null ? false : hasAdaptiveThemes,
31
31
  }
32
32
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './components/ScopedTheme'
2
2
  export { Uniwind } from './core'
3
+ export type { ThemeName } from './core/types'
3
4
  export { withUniwind } from './hoc'
4
5
  export type { ApplyUniwind, ApplyUniwindOptions } from './hoc/types'
5
6
  export { useCSSVariable, useResolveClassNames, useUniwind } from './hooks'
@@ -57,8 +57,9 @@ export class Color {
57
57
  })
58
58
  }
59
59
 
60
+ const colorType = color.type === 'display-p3' ? 'p3' : color.type
60
61
  const result = this.toRgb({
61
- mode: color.type,
62
+ mode: colorType,
62
63
  ...color,
63
64
  } as ColorType)
64
65
 
@@ -434,6 +434,14 @@ export class CSS {
434
434
  return this.processValue(declarationValue[0]!)
435
435
  }
436
436
 
437
+ if (this.isTransformArray(declarationValue)) {
438
+ return declarationValue.flatMap(value => {
439
+ const result = this.processValue(value)
440
+
441
+ return Array.isArray(result) ? result : [result]
442
+ })
443
+ }
444
+
437
445
  return this.addComaBetweenTokens(declarationValue).reduce<string | number>((acc, value, index, array) => {
438
446
  if (typeof value === 'object') {
439
447
  const nextValue = array.at(index + 1)
@@ -580,6 +588,33 @@ export class CSS {
580
588
  return undefined
581
589
  }
582
590
 
591
+ // eslint-disable-next-line @typescript-eslint/member-ordering
592
+ private static readonly TRANSFORM_TYPES = new Set([
593
+ 'translate',
594
+ 'translateX',
595
+ 'translateY',
596
+ 'translateZ',
597
+ 'rotate',
598
+ 'rotateX',
599
+ 'rotateY',
600
+ 'rotateZ',
601
+ 'scale',
602
+ 'scaleX',
603
+ 'scaleY',
604
+ 'scaleZ',
605
+ 'skew',
606
+ 'skewX',
607
+ 'skewY',
608
+ 'matrix',
609
+ 'perspective',
610
+ ])
611
+
612
+ private isTransformArray(values: Array<any>) {
613
+ return values.every(
614
+ value => typeof value === 'object' && value !== null && 'type' in value && CSS.TRANSFORM_TYPES.has(value.type),
615
+ )
616
+ }
617
+
583
618
  private isOverflow(value: any): value is { x: OverflowKeyword; y: OverflowKeyword } {
584
619
  return typeof value === 'object' && 'x' in value && ['hidden', 'visible'].includes(value.x)
585
620
  }
@@ -100,6 +100,19 @@ export const serializeJSObject = (obj: Record<string, any>, serializer: (key: st
100
100
  const serializedObject = pipe(obj)(
101
101
  Object.entries,
102
102
  entries => entries.map(([key, value]) => serializer(key, serialize(value))),
103
+ serializedValues =>
104
+ serializedValues.filter(serializedValue => {
105
+ try {
106
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
107
+ new Function(`function validateJS() { const obj = ({ ${serializedValue} }) }`)
108
+
109
+ return true
110
+ } catch {
111
+ Logger.error(`Failed to serialize ${serializedValue} as a valid JS object entry. Skipping.`)
112
+
113
+ return false
114
+ }
115
+ }),
103
116
  entries => entries.join(','),
104
117
  result => {
105
118
  if (result === '') {
@@ -110,13 +123,5 @@ export const serializeJSObject = (obj: Record<string, any>, serializer: (key: st
110
123
  },
111
124
  )
112
125
 
113
- try {
114
- // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
115
- new Function(`function validateJS() { const obj = ({ ${serializedObject} }) }`)
116
- } catch {
117
- Logger.error('Failed to serialize javascript object')
118
- return ''
119
- }
120
-
121
126
  return serializedObject
122
127
  }