@mgcrea/react-native-tailwind 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Smart merge utility for StyleObject values
3
+ * Handles array properties (like transform) by concatenating instead of overwriting
4
+ */
5
+ import type { StyleObject } from "../types/core";
6
+ /**
7
+ * Merge two StyleObject instances, handling array properties specially
8
+ *
9
+ * @param target - The target object to merge into (mutated)
10
+ * @param source - The source object to merge from
11
+ * @returns The merged target object
12
+ *
13
+ * @example
14
+ * // Standard properties are overwritten (like Object.assign)
15
+ * mergeStyles({ margin: 4 }, { padding: 8 })
16
+ * // => { margin: 4, padding: 8 }
17
+ *
18
+ * @example
19
+ * // Array properties (transform) are concatenated
20
+ * mergeStyles(
21
+ * { transform: [{ rotate: '45deg' }] },
22
+ * { transform: [{ scale: 1.1 }] }
23
+ * )
24
+ * // => { transform: [{ rotate: '45deg' }, { scale: 1.1 }] }
25
+ */
26
+ export declare function mergeStyles(target: StyleObject, source: StyleObject): StyleObject;
@@ -0,0 +1 @@
1
+ var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.mergeStyles=mergeStyles;var _toConsumableArray2=_interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));var ARRAY_MERGE_PROPERTIES=new Set(["transform"]);function mergeStyles(target,source){for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){var sourceValue=source[key];if(ARRAY_MERGE_PROPERTIES.has(key)&&Array.isArray(sourceValue)){var targetValue=target[key];if(Array.isArray(targetValue)){target[key]=[].concat((0,_toConsumableArray2.default)(targetValue),(0,_toConsumableArray2.default)(sourceValue));}else{target[key]=sourceValue;}}else{target[key]=sourceValue;}}}return target;}
@@ -0,0 +1 @@
1
+ var _vitest=require("vitest");var _mergeStyles=require("./mergeStyles");(0,_vitest.describe)("mergeStyles",function(){(0,_vitest.describe)("standard properties",function(){(0,_vitest.it)("should merge non-array properties like Object.assign",function(){var target={margin:4};var source={padding:8};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({margin:4,padding:8});});(0,_vitest.it)("should overwrite non-array properties",function(){var target={margin:4};var source={margin:8};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({margin:8});});(0,_vitest.it)("should handle shadowOffset as standard property (object, not array)",function(){var target={shadowOffset:{width:0,height:1}};var source={shadowOffset:{width:0,height:2}};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({shadowOffset:{width:0,height:2}});});});(0,_vitest.describe)("transform array merging",function(){(0,_vitest.it)("should concatenate transform arrays",function(){var target={transform:[{rotate:"45deg"}]};var source={transform:[{scale:1.1}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{rotate:"45deg"},{scale:1.1}]});});(0,_vitest.it)("should handle multiple transforms in source",function(){var target={transform:[{rotate:"45deg"}]};var source={transform:[{scale:1.1},{translateX:10}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{rotate:"45deg"},{scale:1.1},{translateX:10}]});});(0,_vitest.it)("should assign transform when target has no transform",function(){var target={margin:4};var source={transform:[{scale:1.1}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({margin:4,transform:[{scale:1.1}]});});(0,_vitest.it)("should handle empty target transform array",function(){var target={transform:[]};var source={transform:[{scale:1.1}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{scale:1.1}]});});});(0,_vitest.describe)("mixed properties",function(){(0,_vitest.it)("should handle mix of standard and transform properties",function(){var target={margin:4,transform:[{rotate:"45deg"}]};var source={padding:8,transform:[{scale:1.1}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({margin:4,padding:8,transform:[{rotate:"45deg"},{scale:1.1}]});});});(0,_vitest.describe)("mutation behavior",function(){(0,_vitest.it)("should mutate and return target object",function(){var target={margin:4};var source={padding:8};var result=(0,_mergeStyles.mergeStyles)(target,source);(0,_vitest.expect)(result).toBe(target);(0,_vitest.expect)(target).toEqual({margin:4,padding:8});});});});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Compile-time Tailwind CSS for React Native with zero runtime overhead",
5
5
  "author": "Olivier Louvignes <olivier@mgcrea.io> (https://github.com/mgcrea)",
6
6
  "homepage": "https://github.com/mgcrea/react-native-tailwind#readme",
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export { tw, twStyle } from "./stubs/tw";
9
9
  // Main parser functions
10
10
  export { parseClass, parseClassName } from "./parser";
11
11
  export { flattenColors } from "./utils/flattenColors";
12
+ export { mergeStyles } from "./utils/mergeStyles";
12
13
  export { generateStyleKey } from "./utils/styleKey";
13
14
 
14
15
  // Re-export types
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { StyleObject } from "../types";
7
+ import { mergeStyles } from "../utils/mergeStyles";
7
8
  import { parseAspectRatio } from "./aspectRatio";
8
9
  import { parseBorder } from "./borders";
9
10
  import { parseColor } from "./colors";
@@ -36,7 +37,7 @@ export function parseClassName(className: string, customTheme?: CustomTheme): St
36
37
 
37
38
  for (const cls of classes) {
38
39
  const parsedStyle = parseClass(cls, customTheme);
39
- Object.assign(style, parsedStyle);
40
+ mergeStyles(style, parsedStyle);
40
41
  }
41
42
 
42
43
  return style;
@@ -416,6 +416,43 @@ describe("parseSpacing - logical padding (RTL-aware)", () => {
416
416
  });
417
417
  });
418
418
 
419
+ describe("parseSpacing - auto margin", () => {
420
+ it("should parse m-auto", () => {
421
+ expect(parseSpacing("m-auto")).toEqual({ margin: "auto" });
422
+ });
423
+
424
+ it("should parse mx-auto", () => {
425
+ expect(parseSpacing("mx-auto")).toEqual({ marginHorizontal: "auto" });
426
+ });
427
+
428
+ it("should parse my-auto", () => {
429
+ expect(parseSpacing("my-auto")).toEqual({ marginVertical: "auto" });
430
+ });
431
+
432
+ it("should parse directional auto margins", () => {
433
+ expect(parseSpacing("mt-auto")).toEqual({ marginTop: "auto" });
434
+ expect(parseSpacing("mr-auto")).toEqual({ marginRight: "auto" });
435
+ expect(parseSpacing("mb-auto")).toEqual({ marginBottom: "auto" });
436
+ expect(parseSpacing("ml-auto")).toEqual({ marginLeft: "auto" });
437
+ });
438
+
439
+ it("should parse logical auto margins (RTL-aware)", () => {
440
+ expect(parseSpacing("ms-auto")).toEqual({ marginStart: "auto" });
441
+ expect(parseSpacing("me-auto")).toEqual({ marginEnd: "auto" });
442
+ });
443
+
444
+ it("should not parse padding auto (not valid in Tailwind)", () => {
445
+ expect(parseSpacing("p-auto")).toBeNull();
446
+ expect(parseSpacing("px-auto")).toBeNull();
447
+ expect(parseSpacing("py-auto")).toBeNull();
448
+ expect(parseSpacing("pt-auto")).toBeNull();
449
+ });
450
+
451
+ it("should not parse gap auto (not valid in Tailwind)", () => {
452
+ expect(parseSpacing("gap-auto")).toBeNull();
453
+ });
454
+ });
455
+
419
456
  describe("parseSpacing - custom spacing", () => {
420
457
  const customSpacing = {
421
458
  xs: 4,
@@ -69,7 +69,7 @@ function parseArbitrarySpacing(value: string): number | null {
69
69
 
70
70
  /**
71
71
  * Parse spacing classes (margin, padding, gap)
72
- * Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], pl-[4.5px], -m-4, -mt-[10px], ms-4, pe-2
72
+ * Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], pl-[4.5px], -m-4, -mt-[10px], ms-4, pe-2, mx-auto
73
73
  * @param cls - The class name to parse
74
74
  * @param customSpacing - Optional custom spacing values from tailwind.config
75
75
  */
@@ -77,6 +77,14 @@ export function parseSpacing(cls: string, customSpacing?: Record<string, number>
77
77
  // Merge custom spacing with defaults (custom takes precedence)
78
78
  const spacingMap = customSpacing ? { ...SPACING_SCALE, ...customSpacing } : SPACING_SCALE;
79
79
 
80
+ // Auto margins: m-auto, mx-auto, my-auto, mt-auto, mr-auto, mb-auto, ml-auto, ms-auto, me-auto
81
+ // Note: Only margins support auto values (not padding or gap)
82
+ const autoMarginMatch = cls.match(/^m([xytrblse]?)-auto$/);
83
+ if (autoMarginMatch) {
84
+ const dir = autoMarginMatch[1];
85
+ return getMarginStyle(dir, "auto");
86
+ }
87
+
80
88
  // Margin: m-4, mx-2, mt-8, ms-4, me-2, m-[16px], -m-4, -mt-2, etc.
81
89
  // Supports negative values for margins (but not padding or gap)
82
90
  // s = start (RTL-aware), e = end (RTL-aware)
@@ -143,7 +151,7 @@ export function parseSpacing(cls: string, customSpacing?: Record<string, number>
143
151
  /**
144
152
  * Get margin style object based on direction
145
153
  */
146
- function getMarginStyle(dir: string, value: number): StyleObject {
154
+ function getMarginStyle(dir: string, value: number | "auto"): StyleObject {
147
155
  switch (dir) {
148
156
  case "":
149
157
  return { margin: value };
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
+ import { parseClassName } from "./index";
2
3
  import { PERSPECTIVE_SCALE, ROTATE_MAP, SCALE_MAP, SKEW_MAP, parseTransform } from "./transforms";
3
4
 
4
5
  describe("SCALE_MAP", () => {
@@ -373,3 +374,85 @@ describe("parseTransform - custom spacing", () => {
373
374
  expect(parseTransform("perspective-500", customSpacing)).toEqual({ transform: [{ perspective: 500 }] });
374
375
  });
375
376
  });
377
+
378
+ describe("parseClassName - multiple transforms", () => {
379
+ it("should combine rotate and scale transforms", () => {
380
+ const result = parseClassName("rotate-45 scale-110");
381
+ expect(result).toEqual({
382
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }],
383
+ });
384
+ });
385
+
386
+ it("should combine scale and rotate with arbitrary values", () => {
387
+ const result = parseClassName("rotate-45 scale-[0.2]");
388
+ expect(result).toEqual({
389
+ transform: [{ rotate: "45deg" }, { scale: 0.2 }],
390
+ });
391
+ });
392
+
393
+ it("should combine multiple different transforms", () => {
394
+ const result = parseClassName("rotate-45 scale-110 translate-x-4");
395
+ expect(result).toEqual({
396
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }, { translateX: 16 }],
397
+ });
398
+ });
399
+
400
+ it("should preserve order of transforms", () => {
401
+ // Order matters in React Native transforms!
402
+ const result1 = parseClassName("rotate-45 scale-110");
403
+ const result2 = parseClassName("scale-110 rotate-45");
404
+
405
+ expect(result1.transform).toEqual([{ rotate: "45deg" }, { scale: 1.1 }]);
406
+ expect(result2.transform).toEqual([{ scale: 1.1 }, { rotate: "45deg" }]);
407
+ });
408
+
409
+ it("should combine transforms with other properties", () => {
410
+ const result = parseClassName("m-4 rotate-45 scale-110 p-2");
411
+ expect(result).toEqual({
412
+ margin: 16,
413
+ padding: 8,
414
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }],
415
+ });
416
+ });
417
+
418
+ it("should handle all transform types together", () => {
419
+ const result = parseClassName("perspective-500 rotate-45 scale-110 translate-x-4 skew-x-6");
420
+ expect(result).toEqual({
421
+ transform: [
422
+ { perspective: 500 },
423
+ { rotate: "45deg" },
424
+ { scale: 1.1 },
425
+ { translateX: 16 },
426
+ { skewX: "6deg" },
427
+ ],
428
+ });
429
+ });
430
+
431
+ it("should handle negative transforms", () => {
432
+ const result = parseClassName("-rotate-45 -translate-x-4");
433
+ expect(result).toEqual({
434
+ transform: [{ rotate: "-45deg" }, { translateX: -16 }],
435
+ });
436
+ });
437
+
438
+ it("should handle scale-x and scale-y together", () => {
439
+ const result = parseClassName("scale-x-50 scale-y-150");
440
+ expect(result).toEqual({
441
+ transform: [{ scaleX: 0.5 }, { scaleY: 1.5 }],
442
+ });
443
+ });
444
+
445
+ it("should handle single transform (backward compatibility)", () => {
446
+ const result = parseClassName("rotate-45");
447
+ expect(result).toEqual({
448
+ transform: [{ rotate: "45deg" }],
449
+ });
450
+ });
451
+
452
+ it("should handle arbitrary values combined", () => {
453
+ const result = parseClassName("rotate-[37deg] scale-[0.2] translate-x-[50px]");
454
+ expect(result).toEqual({
455
+ transform: [{ rotate: "37deg" }, { scale: 0.2 }, { translateX: 50 }],
456
+ });
457
+ });
458
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mergeStyles } from "./mergeStyles";
3
+
4
+ describe("mergeStyles", () => {
5
+ describe("standard properties", () => {
6
+ it("should merge non-array properties like Object.assign", () => {
7
+ const target = { margin: 4 };
8
+ const source = { padding: 8 };
9
+ expect(mergeStyles(target, source)).toEqual({ margin: 4, padding: 8 });
10
+ });
11
+
12
+ it("should overwrite non-array properties", () => {
13
+ const target = { margin: 4 };
14
+ const source = { margin: 8 };
15
+ expect(mergeStyles(target, source)).toEqual({ margin: 8 });
16
+ });
17
+
18
+ it("should handle shadowOffset as standard property (object, not array)", () => {
19
+ const target = { shadowOffset: { width: 0, height: 1 } };
20
+ const source = { shadowOffset: { width: 0, height: 2 } };
21
+ expect(mergeStyles(target, source)).toEqual({
22
+ shadowOffset: { width: 0, height: 2 },
23
+ });
24
+ });
25
+ });
26
+
27
+ describe("transform array merging", () => {
28
+ it("should concatenate transform arrays", () => {
29
+ const target = { transform: [{ rotate: "45deg" }] };
30
+ const source = { transform: [{ scale: 1.1 }] };
31
+ expect(mergeStyles(target, source)).toEqual({
32
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }],
33
+ });
34
+ });
35
+
36
+ it("should handle multiple transforms in source", () => {
37
+ const target = { transform: [{ rotate: "45deg" }] };
38
+ const source = { transform: [{ scale: 1.1 }, { translateX: 10 }] };
39
+ expect(mergeStyles(target, source)).toEqual({
40
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }, { translateX: 10 }],
41
+ });
42
+ });
43
+
44
+ it("should assign transform when target has no transform", () => {
45
+ const target = { margin: 4 };
46
+ const source = { transform: [{ scale: 1.1 }] };
47
+ expect(mergeStyles(target, source)).toEqual({
48
+ margin: 4,
49
+ transform: [{ scale: 1.1 }],
50
+ });
51
+ });
52
+
53
+ it("should handle empty target transform array", () => {
54
+ const target = { transform: [] as Array<{ scale?: number }> };
55
+ const source = { transform: [{ scale: 1.1 }] };
56
+ expect(mergeStyles(target, source)).toEqual({
57
+ transform: [{ scale: 1.1 }],
58
+ });
59
+ });
60
+ });
61
+
62
+ describe("mixed properties", () => {
63
+ it("should handle mix of standard and transform properties", () => {
64
+ const target = { margin: 4, transform: [{ rotate: "45deg" }] };
65
+ const source = { padding: 8, transform: [{ scale: 1.1 }] };
66
+ expect(mergeStyles(target, source)).toEqual({
67
+ margin: 4,
68
+ padding: 8,
69
+ transform: [{ rotate: "45deg" }, { scale: 1.1 }],
70
+ });
71
+ });
72
+ });
73
+
74
+ describe("mutation behavior", () => {
75
+ it("should mutate and return target object", () => {
76
+ const target = { margin: 4 };
77
+ const source = { padding: 8 };
78
+ const result = mergeStyles(target, source);
79
+ expect(result).toBe(target);
80
+ expect(target).toEqual({ margin: 4, padding: 8 });
81
+ });
82
+ });
83
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Smart merge utility for StyleObject values
3
+ * Handles array properties (like transform) by concatenating instead of overwriting
4
+ */
5
+
6
+ import type { StyleObject } from "../types/core";
7
+
8
+ /**
9
+ * Properties that should be merged as arrays (concatenated) rather than overwritten
10
+ */
11
+ const ARRAY_MERGE_PROPERTIES = new Set<string>(["transform"]);
12
+
13
+ /**
14
+ * Merge two StyleObject instances, handling array properties specially
15
+ *
16
+ * @param target - The target object to merge into (mutated)
17
+ * @param source - The source object to merge from
18
+ * @returns The merged target object
19
+ *
20
+ * @example
21
+ * // Standard properties are overwritten (like Object.assign)
22
+ * mergeStyles({ margin: 4 }, { padding: 8 })
23
+ * // => { margin: 4, padding: 8 }
24
+ *
25
+ * @example
26
+ * // Array properties (transform) are concatenated
27
+ * mergeStyles(
28
+ * { transform: [{ rotate: '45deg' }] },
29
+ * { transform: [{ scale: 1.1 }] }
30
+ * )
31
+ * // => { transform: [{ rotate: '45deg' }, { scale: 1.1 }] }
32
+ */
33
+ export function mergeStyles(target: StyleObject, source: StyleObject): StyleObject {
34
+ for (const key in source) {
35
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
36
+ const sourceValue = source[key];
37
+
38
+ // Handle array merge properties (like transform)
39
+ if (ARRAY_MERGE_PROPERTIES.has(key) && Array.isArray(sourceValue)) {
40
+ const targetValue = target[key];
41
+ if (Array.isArray(targetValue)) {
42
+ // Concatenate arrays
43
+ (target as Record<string, unknown>)[key] = [...targetValue, ...sourceValue];
44
+ } else {
45
+ // No existing array, just assign
46
+ target[key] = sourceValue;
47
+ }
48
+ } else {
49
+ // Standard Object.assign behavior for non-array properties
50
+ target[key] = sourceValue;
51
+ }
52
+ }
53
+ }
54
+
55
+ return target;
56
+ }