@mgcrea/react-native-tailwind 0.15.2 → 0.15.3
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/README.md +5 -0
- package/dist/babel/index.cjs +66 -15
- package/dist/parser/layout.js +1 -1
- package/dist/parser/layout.test.js +1 -1
- package/dist/parser/transforms.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/utils/mergeStyles.d.ts +11 -3
- package/dist/utils/mergeStyles.js +1 -1
- package/dist/utils/mergeStyles.test.js +1 -1
- package/package.json +1 -1
- package/src/parser/layout.test.ts +38 -3
- package/src/parser/layout.ts +60 -20
- package/src/parser/transforms.test.ts +22 -0
- package/src/utils/mergeStyles.test.ts +44 -2
- package/src/utils/mergeStyles.ts +58 -11
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Smart merge utility for StyleObject values
|
|
3
|
-
* Handles
|
|
3
|
+
* Handles transform arrays with "last wins" semantics for same transform types
|
|
4
4
|
*/
|
|
5
5
|
import type { StyleObject } from "../types/core";
|
|
6
6
|
/**
|
|
7
|
-
* Merge two StyleObject instances, handling
|
|
7
|
+
* Merge two StyleObject instances, handling transform arrays specially
|
|
8
8
|
*
|
|
9
9
|
* @param target - The target object to merge into (mutated)
|
|
10
10
|
* @param source - The source object to merge from
|
|
@@ -16,11 +16,19 @@ import type { StyleObject } from "../types/core";
|
|
|
16
16
|
* // => { margin: 4, padding: 8 }
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
-
* //
|
|
19
|
+
* // Different transform types are combined
|
|
20
20
|
* mergeStyles(
|
|
21
21
|
* { transform: [{ rotate: '45deg' }] },
|
|
22
22
|
* { transform: [{ scale: 1.1 }] }
|
|
23
23
|
* )
|
|
24
24
|
* // => { transform: [{ rotate: '45deg' }, { scale: 1.1 }] }
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Same transform type: last wins (Tailwind parity)
|
|
28
|
+
* mergeStyles(
|
|
29
|
+
* { transform: [{ rotate: '45deg' }] },
|
|
30
|
+
* { transform: [{ rotate: '90deg' }] }
|
|
31
|
+
* )
|
|
32
|
+
* // => { transform: [{ rotate: '90deg' }] }
|
|
25
33
|
*/
|
|
26
34
|
export declare function mergeStyles(target: StyleObject, source: StyleObject): StyleObject;
|
|
@@ -1 +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
|
|
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"));function getTransformType(transform){return Object.keys(transform)[0];}function mergeTransforms(target,source){var result=(0,_toConsumableArray2.default)(target);var _loop=function _loop(){var sourceType=getTransformType(sourceTransform);var existingIndex=result.findIndex(function(t){return getTransformType(t)===sourceType;});if(existingIndex!==-1){result[existingIndex]=sourceTransform;}else{result.push(sourceTransform);}};for(var sourceTransform of source){_loop();}return result;}function mergeStyles(target,source){for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){var sourceValue=source[key];if(key==="transform"&&Array.isArray(sourceValue)){var targetValue=target[key];if(Array.isArray(targetValue)){target.transform=mergeTransforms(targetValue,sourceValue);}else{target[key]=sourceValue;}}else{target[key]=sourceValue;}}}return target;}
|
|
@@ -1 +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
|
|
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 - different types combined",function(){(0,_vitest.it)("should combine different transform types",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)("transform array merging - same type last wins (Tailwind parity)",function(){(0,_vitest.it)("should replace same transform type with last value",function(){var target={transform:[{rotate:"45deg"}]};var source={transform:[{rotate:"90deg"}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{rotate:"90deg"}]});});(0,_vitest.it)("should replace same scale type with last value",function(){var target={transform:[{scale:0.5}]};var source={transform:[{scale:1.1}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{scale:1.1}]});});(0,_vitest.it)("should preserve order when replacing - rotate stays in position",function(){var target={transform:[{rotate:"45deg"},{scale:1.1}]};var source={transform:[{rotate:"90deg"}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{rotate:"90deg"},{scale:1.1}]});});(0,_vitest.it)("should handle mixed: replace same types, add new types",function(){var target={transform:[{rotate:"45deg"},{scale:0.5}]};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 handle scaleX and scaleY as different types",function(){var target={transform:[{scaleX:0.5}]};var source={transform:[{scaleY:1.5}]};(0,_vitest.expect)((0,_mergeStyles.mergeStyles)(target,source)).toEqual({transform:[{scaleX:0.5},{scaleY:1.5}]});});});(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.
|
|
3
|
+
"version": "0.15.3",
|
|
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",
|
|
@@ -521,6 +521,39 @@ describe("parseLayout - positioning utilities", () => {
|
|
|
521
521
|
expect(parseLayout("left-[12.5%]")).toEqual({ left: "12.5%" });
|
|
522
522
|
expect(parseLayout("left-[-20px]")).toEqual({ left: -20 });
|
|
523
523
|
});
|
|
524
|
+
|
|
525
|
+
it("should parse negative positioning prefixes", () => {
|
|
526
|
+
// Standard spacing scale
|
|
527
|
+
expect(parseLayout("-top-1")).toEqual({ top: -4 });
|
|
528
|
+
expect(parseLayout("-top-4")).toEqual({ top: -16 });
|
|
529
|
+
expect(parseLayout("-right-1")).toEqual({ right: -4 });
|
|
530
|
+
expect(parseLayout("-right-2")).toEqual({ right: -8 });
|
|
531
|
+
expect(parseLayout("-bottom-4")).toEqual({ bottom: -16 });
|
|
532
|
+
expect(parseLayout("-bottom-8")).toEqual({ bottom: -32 });
|
|
533
|
+
expect(parseLayout("-left-2")).toEqual({ left: -8 });
|
|
534
|
+
expect(parseLayout("-left-4")).toEqual({ left: -16 });
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("should parse negative positioning with fractional values", () => {
|
|
538
|
+
expect(parseLayout("-top-0.5")).toEqual({ top: -2 });
|
|
539
|
+
expect(parseLayout("-right-1.5")).toEqual({ right: -6 });
|
|
540
|
+
expect(parseLayout("-bottom-2.5")).toEqual({ bottom: -10 });
|
|
541
|
+
expect(parseLayout("-left-3.5")).toEqual({ left: -14 });
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("should parse negative positioning with arbitrary values", () => {
|
|
545
|
+
expect(parseLayout("-top-[10px]")).toEqual({ top: -10 });
|
|
546
|
+
expect(parseLayout("-right-[20px]")).toEqual({ right: -20 });
|
|
547
|
+
expect(parseLayout("-bottom-[30]")).toEqual({ bottom: -30 });
|
|
548
|
+
expect(parseLayout("-left-[40px]")).toEqual({ left: -40 });
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("should parse negative positioning with arbitrary percentages", () => {
|
|
552
|
+
expect(parseLayout("-top-[10%]")).toEqual({ top: "-10%" });
|
|
553
|
+
expect(parseLayout("-right-[25%]")).toEqual({ right: "-25%" });
|
|
554
|
+
expect(parseLayout("-bottom-[50%]")).toEqual({ bottom: "-50%" });
|
|
555
|
+
expect(parseLayout("-left-[100%]")).toEqual({ left: "-100%" });
|
|
556
|
+
});
|
|
524
557
|
});
|
|
525
558
|
|
|
526
559
|
describe("parseLayout - inset utilities", () => {
|
|
@@ -791,9 +824,11 @@ describe("parseLayout - custom spacing", () => {
|
|
|
791
824
|
expect(parseLayout("inset-e-xl", customSpacing)).toEqual({ end: 64 });
|
|
792
825
|
});
|
|
793
826
|
|
|
794
|
-
it("should support negative values with custom spacing for
|
|
795
|
-
|
|
796
|
-
|
|
827
|
+
it("should support negative values with custom spacing for positioning", () => {
|
|
828
|
+
expect(parseLayout("-top-sm", customSpacing)).toEqual({ top: -8 });
|
|
829
|
+
expect(parseLayout("-right-md", customSpacing)).toEqual({ right: -16 });
|
|
830
|
+
expect(parseLayout("-bottom-lg", customSpacing)).toEqual({ bottom: -32 });
|
|
831
|
+
expect(parseLayout("-left-xl", customSpacing)).toEqual({ left: -64 });
|
|
797
832
|
expect(parseLayout("-start-sm", customSpacing)).toEqual({ start: -8 });
|
|
798
833
|
expect(parseLayout("-end-md", customSpacing)).toEqual({ end: -16 });
|
|
799
834
|
});
|
package/src/parser/layout.ts
CHANGED
|
@@ -260,87 +260,127 @@ export function parseLayout(cls: string, customSpacing?: Record<string, number>)
|
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
// Top positioning: top-0, top-4, top-[10px], top-[50%], etc.
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
// Top positioning: top-0, top-4, top-[10px], top-[50%], -top-4, etc.
|
|
264
|
+
const topMatch = cls.match(/^(-?)top-(.+)$/);
|
|
265
|
+
if (topMatch) {
|
|
266
|
+
const [, negPrefix, topKey] = topMatch;
|
|
267
|
+
const isNegative = negPrefix === "-";
|
|
266
268
|
|
|
267
269
|
// Auto value - return empty object (no-op, removes the property)
|
|
268
270
|
if (topKey === "auto") {
|
|
269
271
|
return {};
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
// Arbitrary values: top-[123px], top-[50%], top-[
|
|
274
|
+
// Arbitrary values: top-[123px], top-[50%], -top-[10px]
|
|
273
275
|
const arbitraryTop = parseArbitraryInset(topKey);
|
|
274
276
|
if (arbitraryTop !== null) {
|
|
277
|
+
if (typeof arbitraryTop === "number") {
|
|
278
|
+
return { top: isNegative ? -arbitraryTop : arbitraryTop };
|
|
279
|
+
}
|
|
280
|
+
// Percentage values with negative prefix
|
|
281
|
+
if (isNegative && arbitraryTop.endsWith("%")) {
|
|
282
|
+
const numValue = parseFloat(arbitraryTop);
|
|
283
|
+
return { top: `${-numValue}%` };
|
|
284
|
+
}
|
|
275
285
|
return { top: arbitraryTop };
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
const topValue = insetMap[topKey];
|
|
279
289
|
if (topValue !== undefined) {
|
|
280
|
-
return { top: topValue };
|
|
290
|
+
return { top: isNegative ? -topValue : topValue };
|
|
281
291
|
}
|
|
282
292
|
}
|
|
283
293
|
|
|
284
|
-
// Right positioning: right-0, right-4, right-[10px], right-[50%], etc.
|
|
285
|
-
|
|
286
|
-
|
|
294
|
+
// Right positioning: right-0, right-4, right-[10px], right-[50%], -right-4, etc.
|
|
295
|
+
const rightMatch = cls.match(/^(-?)right-(.+)$/);
|
|
296
|
+
if (rightMatch) {
|
|
297
|
+
const [, negPrefix, rightKey] = rightMatch;
|
|
298
|
+
const isNegative = negPrefix === "-";
|
|
287
299
|
|
|
288
300
|
// Auto value - return empty object (no-op, removes the property)
|
|
289
301
|
if (rightKey === "auto") {
|
|
290
302
|
return {};
|
|
291
303
|
}
|
|
292
304
|
|
|
293
|
-
// Arbitrary values: right-[123px], right-[50%], right-[
|
|
305
|
+
// Arbitrary values: right-[123px], right-[50%], -right-[10px]
|
|
294
306
|
const arbitraryRight = parseArbitraryInset(rightKey);
|
|
295
307
|
if (arbitraryRight !== null) {
|
|
308
|
+
if (typeof arbitraryRight === "number") {
|
|
309
|
+
return { right: isNegative ? -arbitraryRight : arbitraryRight };
|
|
310
|
+
}
|
|
311
|
+
// Percentage values with negative prefix
|
|
312
|
+
if (isNegative && arbitraryRight.endsWith("%")) {
|
|
313
|
+
const numValue = parseFloat(arbitraryRight);
|
|
314
|
+
return { right: `${-numValue}%` };
|
|
315
|
+
}
|
|
296
316
|
return { right: arbitraryRight };
|
|
297
317
|
}
|
|
298
318
|
|
|
299
319
|
const rightValue = insetMap[rightKey];
|
|
300
320
|
if (rightValue !== undefined) {
|
|
301
|
-
return { right: rightValue };
|
|
321
|
+
return { right: isNegative ? -rightValue : rightValue };
|
|
302
322
|
}
|
|
303
323
|
}
|
|
304
324
|
|
|
305
|
-
// Bottom positioning: bottom-0, bottom-4, bottom-[10px], bottom-[50%], etc.
|
|
306
|
-
|
|
307
|
-
|
|
325
|
+
// Bottom positioning: bottom-0, bottom-4, bottom-[10px], bottom-[50%], -bottom-4, etc.
|
|
326
|
+
const bottomMatch = cls.match(/^(-?)bottom-(.+)$/);
|
|
327
|
+
if (bottomMatch) {
|
|
328
|
+
const [, negPrefix, bottomKey] = bottomMatch;
|
|
329
|
+
const isNegative = negPrefix === "-";
|
|
308
330
|
|
|
309
331
|
// Auto value - return empty object (no-op, removes the property)
|
|
310
332
|
if (bottomKey === "auto") {
|
|
311
333
|
return {};
|
|
312
334
|
}
|
|
313
335
|
|
|
314
|
-
// Arbitrary values: bottom-[123px], bottom-[50%], bottom-[
|
|
336
|
+
// Arbitrary values: bottom-[123px], bottom-[50%], -bottom-[10px]
|
|
315
337
|
const arbitraryBottom = parseArbitraryInset(bottomKey);
|
|
316
338
|
if (arbitraryBottom !== null) {
|
|
339
|
+
if (typeof arbitraryBottom === "number") {
|
|
340
|
+
return { bottom: isNegative ? -arbitraryBottom : arbitraryBottom };
|
|
341
|
+
}
|
|
342
|
+
// Percentage values with negative prefix
|
|
343
|
+
if (isNegative && arbitraryBottom.endsWith("%")) {
|
|
344
|
+
const numValue = parseFloat(arbitraryBottom);
|
|
345
|
+
return { bottom: `${-numValue}%` };
|
|
346
|
+
}
|
|
317
347
|
return { bottom: arbitraryBottom };
|
|
318
348
|
}
|
|
319
349
|
|
|
320
350
|
const bottomValue = insetMap[bottomKey];
|
|
321
351
|
if (bottomValue !== undefined) {
|
|
322
|
-
return { bottom: bottomValue };
|
|
352
|
+
return { bottom: isNegative ? -bottomValue : bottomValue };
|
|
323
353
|
}
|
|
324
354
|
}
|
|
325
355
|
|
|
326
|
-
// Left positioning: left-0, left-4, left-[10px], left-[50%], etc.
|
|
327
|
-
|
|
328
|
-
|
|
356
|
+
// Left positioning: left-0, left-4, left-[10px], left-[50%], -left-4, etc.
|
|
357
|
+
const leftMatch = cls.match(/^(-?)left-(.+)$/);
|
|
358
|
+
if (leftMatch) {
|
|
359
|
+
const [, negPrefix, leftKey] = leftMatch;
|
|
360
|
+
const isNegative = negPrefix === "-";
|
|
329
361
|
|
|
330
362
|
// Auto value - return empty object (no-op, removes the property)
|
|
331
363
|
if (leftKey === "auto") {
|
|
332
364
|
return {};
|
|
333
365
|
}
|
|
334
366
|
|
|
335
|
-
// Arbitrary values: left-[123px], left-[50%], left-[
|
|
367
|
+
// Arbitrary values: left-[123px], left-[50%], -left-[10px]
|
|
336
368
|
const arbitraryLeft = parseArbitraryInset(leftKey);
|
|
337
369
|
if (arbitraryLeft !== null) {
|
|
370
|
+
if (typeof arbitraryLeft === "number") {
|
|
371
|
+
return { left: isNegative ? -arbitraryLeft : arbitraryLeft };
|
|
372
|
+
}
|
|
373
|
+
// Percentage values with negative prefix
|
|
374
|
+
if (isNegative && arbitraryLeft.endsWith("%")) {
|
|
375
|
+
const numValue = parseFloat(arbitraryLeft);
|
|
376
|
+
return { left: `${-numValue}%` };
|
|
377
|
+
}
|
|
338
378
|
return { left: arbitraryLeft };
|
|
339
379
|
}
|
|
340
380
|
|
|
341
381
|
const leftValue = insetMap[leftKey];
|
|
342
382
|
if (leftValue !== undefined) {
|
|
343
|
-
return { left: leftValue };
|
|
383
|
+
return { left: isNegative ? -leftValue : leftValue };
|
|
344
384
|
}
|
|
345
385
|
}
|
|
346
386
|
|
|
@@ -455,4 +455,26 @@ describe("parseClassName - multiple transforms", () => {
|
|
|
455
455
|
transform: [{ rotate: "37deg" }, { scale: 0.2 }, { translateX: 50 }],
|
|
456
456
|
});
|
|
457
457
|
});
|
|
458
|
+
|
|
459
|
+
// "Last wins" behavior for same transform type (Tailwind parity)
|
|
460
|
+
it("should use last value for duplicate rotate (Tailwind parity)", () => {
|
|
461
|
+
const result = parseClassName("rotate-45 rotate-90");
|
|
462
|
+
expect(result).toEqual({
|
|
463
|
+
transform: [{ rotate: "90deg" }],
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should use last value for duplicate scale (Tailwind parity)", () => {
|
|
468
|
+
const result = parseClassName("scale-50 scale-110");
|
|
469
|
+
expect(result).toEqual({
|
|
470
|
+
transform: [{ scale: 1.1 }],
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("should preserve different types while replacing duplicates", () => {
|
|
475
|
+
const result = parseClassName("rotate-45 scale-110 rotate-90");
|
|
476
|
+
expect(result).toEqual({
|
|
477
|
+
transform: [{ rotate: "90deg" }, { scale: 1.1 }],
|
|
478
|
+
});
|
|
479
|
+
});
|
|
458
480
|
});
|
|
@@ -24,8 +24,8 @@ describe("mergeStyles", () => {
|
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
describe("transform array merging", () => {
|
|
28
|
-
it("should
|
|
27
|
+
describe("transform array merging - different types combined", () => {
|
|
28
|
+
it("should combine different transform types", () => {
|
|
29
29
|
const target = { transform: [{ rotate: "45deg" }] };
|
|
30
30
|
const source = { transform: [{ scale: 1.1 }] };
|
|
31
31
|
expect(mergeStyles(target, source)).toEqual({
|
|
@@ -59,6 +59,48 @@ describe("mergeStyles", () => {
|
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
describe("transform array merging - same type last wins (Tailwind parity)", () => {
|
|
63
|
+
it("should replace same transform type with last value", () => {
|
|
64
|
+
const target = { transform: [{ rotate: "45deg" }] };
|
|
65
|
+
const source = { transform: [{ rotate: "90deg" }] };
|
|
66
|
+
expect(mergeStyles(target, source)).toEqual({
|
|
67
|
+
transform: [{ rotate: "90deg" }],
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should replace same scale type with last value", () => {
|
|
72
|
+
const target = { transform: [{ scale: 0.5 }] };
|
|
73
|
+
const source = { transform: [{ scale: 1.1 }] };
|
|
74
|
+
expect(mergeStyles(target, source)).toEqual({
|
|
75
|
+
transform: [{ scale: 1.1 }],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should preserve order when replacing - rotate stays in position", () => {
|
|
80
|
+
const target = { transform: [{ rotate: "45deg" }, { scale: 1.1 }] };
|
|
81
|
+
const source = { transform: [{ rotate: "90deg" }] };
|
|
82
|
+
expect(mergeStyles(target, source)).toEqual({
|
|
83
|
+
transform: [{ rotate: "90deg" }, { scale: 1.1 }],
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should handle mixed: replace same types, add new types", () => {
|
|
88
|
+
const target = { transform: [{ rotate: "45deg" }, { scale: 0.5 }] };
|
|
89
|
+
const source = { transform: [{ scale: 1.1 }, { translateX: 10 }] };
|
|
90
|
+
expect(mergeStyles(target, source)).toEqual({
|
|
91
|
+
transform: [{ rotate: "45deg" }, { scale: 1.1 }, { translateX: 10 }],
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should handle scaleX and scaleY as different types", () => {
|
|
96
|
+
const target = { transform: [{ scaleX: 0.5 }] };
|
|
97
|
+
const source = { transform: [{ scaleY: 1.5 }] };
|
|
98
|
+
expect(mergeStyles(target, source)).toEqual({
|
|
99
|
+
transform: [{ scaleX: 0.5 }, { scaleY: 1.5 }],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
62
104
|
describe("mixed properties", () => {
|
|
63
105
|
it("should handle mix of standard and transform properties", () => {
|
|
64
106
|
const target = { margin: 4, transform: [{ rotate: "45deg" }] };
|
package/src/utils/mergeStyles.ts
CHANGED
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Smart merge utility for StyleObject values
|
|
3
|
-
* Handles
|
|
3
|
+
* Handles transform arrays with "last wins" semantics for same transform types
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { StyleObject } from "../types/core";
|
|
6
|
+
import type { StyleObject, TransformStyle } from "../types/core";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Get the transform type key from a transform object
|
|
10
|
+
* e.g., { rotate: '45deg' } -> 'rotate', { scale: 1.1 } -> 'scale'
|
|
10
11
|
*/
|
|
11
|
-
|
|
12
|
+
function getTransformType(transform: TransformStyle): string {
|
|
13
|
+
return Object.keys(transform)[0];
|
|
14
|
+
}
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
* Merge
|
|
17
|
+
* Merge transform arrays with "last wins" semantics for duplicate transform types.
|
|
18
|
+
* Different transform types are combined, but if the same type appears twice,
|
|
19
|
+
* the later one replaces the earlier one (matching Tailwind CSS behavior).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Different types are combined
|
|
23
|
+
* mergeTransforms([{ rotate: '45deg' }], [{ scale: 1.1 }])
|
|
24
|
+
* // => [{ rotate: '45deg' }, { scale: 1.1 }]
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Same type: last wins
|
|
28
|
+
* mergeTransforms([{ rotate: '45deg' }], [{ rotate: '90deg' }])
|
|
29
|
+
* // => [{ rotate: '90deg' }]
|
|
30
|
+
*/
|
|
31
|
+
function mergeTransforms(target: TransformStyle[], source: TransformStyle[]): TransformStyle[] {
|
|
32
|
+
// Build result by processing target first, then source
|
|
33
|
+
// For each source transform, replace any existing transform of the same type
|
|
34
|
+
const result: TransformStyle[] = [...target];
|
|
35
|
+
|
|
36
|
+
for (const sourceTransform of source) {
|
|
37
|
+
const sourceType = getTransformType(sourceTransform);
|
|
38
|
+
const existingIndex = result.findIndex((t) => getTransformType(t) === sourceType);
|
|
39
|
+
|
|
40
|
+
if (existingIndex !== -1) {
|
|
41
|
+
// Replace existing transform of same type (last wins)
|
|
42
|
+
result[existingIndex] = sourceTransform;
|
|
43
|
+
} else {
|
|
44
|
+
// Add new transform type
|
|
45
|
+
result.push(sourceTransform);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Merge two StyleObject instances, handling transform arrays specially
|
|
15
54
|
*
|
|
16
55
|
* @param target - The target object to merge into (mutated)
|
|
17
56
|
* @param source - The source object to merge from
|
|
@@ -23,30 +62,38 @@ const ARRAY_MERGE_PROPERTIES = new Set<string>(["transform"]);
|
|
|
23
62
|
* // => { margin: 4, padding: 8 }
|
|
24
63
|
*
|
|
25
64
|
* @example
|
|
26
|
-
* //
|
|
65
|
+
* // Different transform types are combined
|
|
27
66
|
* mergeStyles(
|
|
28
67
|
* { transform: [{ rotate: '45deg' }] },
|
|
29
68
|
* { transform: [{ scale: 1.1 }] }
|
|
30
69
|
* )
|
|
31
70
|
* // => { transform: [{ rotate: '45deg' }, { scale: 1.1 }] }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Same transform type: last wins (Tailwind parity)
|
|
74
|
+
* mergeStyles(
|
|
75
|
+
* { transform: [{ rotate: '45deg' }] },
|
|
76
|
+
* { transform: [{ rotate: '90deg' }] }
|
|
77
|
+
* )
|
|
78
|
+
* // => { transform: [{ rotate: '90deg' }] }
|
|
32
79
|
*/
|
|
33
80
|
export function mergeStyles(target: StyleObject, source: StyleObject): StyleObject {
|
|
34
81
|
for (const key in source) {
|
|
35
82
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
36
83
|
const sourceValue = source[key];
|
|
37
84
|
|
|
38
|
-
// Handle
|
|
39
|
-
if (
|
|
85
|
+
// Handle transform arrays specially
|
|
86
|
+
if (key === "transform" && Array.isArray(sourceValue)) {
|
|
40
87
|
const targetValue = target[key];
|
|
41
88
|
if (Array.isArray(targetValue)) {
|
|
42
|
-
//
|
|
43
|
-
|
|
89
|
+
// Merge transforms with "last wins" for same types
|
|
90
|
+
target.transform = mergeTransforms(targetValue, sourceValue);
|
|
44
91
|
} else {
|
|
45
92
|
// No existing array, just assign
|
|
46
93
|
target[key] = sourceValue;
|
|
47
94
|
}
|
|
48
95
|
} else {
|
|
49
|
-
// Standard Object.assign behavior for non-
|
|
96
|
+
// Standard Object.assign behavior for non-transform properties
|
|
50
97
|
target[key] = sourceValue;
|
|
51
98
|
}
|
|
52
99
|
}
|