diff --git a/CHANGELOG.md b/CHANGELOG.md index e48dbe9..6f95fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Undocumented APIs should be considered internal and may change without warning. - `springValue`/`useSpring`: Improved performance when retargeting spring. +### Fixed + +- `rotateZ` is no longer dropped from elements with a layout or shared layout animation. + ## [13.2.0] 2026-09-03 ### Added diff --git a/packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts b/packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts index 7c98a2b..9e05082 100644 --- a/packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts +++ b/packages/motion-dom/src/projection/animation/__tests__/mix-values.test.ts @@ -97,4 +97,20 @@ describe("mixValues", () => { expect(output).toEqual({ borderTopLeftRadius: "10px" }) }) + + test("mixes rotate", () => { + const output = {} + + mixValues(output, { rotate: 0 }, { rotate: 90 }, 0.5, false, false) + + expect(output).toEqual({ rotate: 45 }) + }) + + test("mixes rotateZ", () => { + const output = {} + + mixValues(output, { rotateZ: 0 }, { rotateZ: 90 }, 0.5, false, false) + + expect(output).toEqual({ rotateZ: 45 }) + }) }) diff --git a/packages/motion-dom/src/projection/animation/mix-values.ts b/packages/motion-dom/src/projection/animation/mix-values.ts index 11f550d..3a38b97 100644 --- a/packages/motion-dom/src/projection/animation/mix-values.ts +++ b/packages/motion-dom/src/projection/animation/mix-values.ts @@ -91,6 +91,15 @@ export function mixValues( progress ) } + + // `rotateZ` is a synonym of `rotate` but arrives as its own value. + if (follow.rotateZ || lead.rotateZ) { + target.rotateZ = mixNumber( + (follow.rotateZ as number) || 0, + (lead.rotateZ as number) || 0, + progress + ) + } } function getRadius(values: ResolvedValues, radiusName: string) { diff --git a/packages/motion-dom/src/projection/styles/__tests__/transform.test.ts b/packages/motion-dom/src/projection/styles/__tests__/transform.test.ts index ca4b0f5..3f4669e 100644 --- a/packages/motion-dom/src/projection/styles/__tests__/transform.test.ts +++ b/packages/motion-dom/src/projection/styles/__tests__/transform.test.ts @@ -102,4 +102,35 @@ describe("buildProjectionTransform", () => { buildProjectionTransform(delta, { x: 1, y: 1 }, { rotate: 10 }) ).toEqual("translate3d(100px, 100px, 0px) rotate(10deg) scale(2, 4)") }) + + it("Applies rotateZ", () => { + const delta = { + x: { + translate: 100, + scale: 2, + origin: 0.5, + originPoint: 100, + }, + y: { + translate: 100, + scale: 4, + origin: 0.5, + originPoint: 100, + }, + } + + expect( + buildProjectionTransform(delta, { x: 1, y: 1 }, { rotateZ: 10 }) + ).toEqual("translate3d(100px, 100px, 0px) rotateZ(10deg) scale(2, 4)") + + expect( + buildProjectionTransform( + delta, + { x: 1, y: 1 }, + { rotate: 45, rotateX: 20, rotateY: 30, rotateZ: 10 } + ) + ).toEqual( + "translate3d(100px, 100px, 0px) rotate(45deg) rotateX(20deg) rotateY(30deg) rotateZ(10deg) scale(2, 4)" + ) + }) }) diff --git a/packages/motion-dom/src/projection/styles/transform.ts b/packages/motion-dom/src/projection/styles/transform.ts index c6d76bb..b72b3aa 100644 --- a/packages/motion-dom/src/projection/styles/transform.ts +++ b/packages/motion-dom/src/projection/styles/transform.ts @@ -36,6 +36,7 @@ export function buildProjectionTransform( pathRotation, rotateX, rotateY, + rotateZ, skewX, skewY, } = latestTransform @@ -46,6 +47,7 @@ export function buildProjectionTransform( if (pathRotation) transform += `rotate(${pathRotation}deg) ` if (rotateX) transform += `rotateX(${rotateX}deg) ` if (rotateY) transform += `rotateY(${rotateY}deg) ` + if (rotateZ) transform += `rotateZ(${rotateZ}deg) ` if (skewX) transform += `skewX(${skewX}deg) ` if (skewY) transform += `skewY(${skewY}deg) ` } diff --git a/packages/motion-dom/src/projection/utils/__tests__/has-transform.test.ts b/packages/motion-dom/src/projection/utils/__tests__/has-transform.test.ts new file mode 100644 index 0000000..cfec291 --- /dev/null +++ b/packages/motion-dom/src/projection/utils/__tests__/has-transform.test.ts @@ -0,0 +1,20 @@ +import { hasTransform } from "../has-transform" + +describe("hasTransform", () => { + test("returns false when no transform is set", () => { + expect(hasTransform({})).toBeFalsy() + expect(hasTransform({ scale: 1, scaleX: 1, scaleY: 1 })).toBeFalsy() + }) + + test("detects every distorting transform", () => { + expect(hasTransform({ scale: 2 })).toBeTruthy() + expect(hasTransform({ x: 100 })).toBeTruthy() + expect(hasTransform({ z: 100 })).toBeTruthy() + expect(hasTransform({ rotate: 45 })).toBeTruthy() + expect(hasTransform({ rotateX: 45 })).toBeTruthy() + expect(hasTransform({ rotateY: 45 })).toBeTruthy() + expect(hasTransform({ rotateZ: 45 })).toBeTruthy() + expect(hasTransform({ skewX: 45 })).toBeTruthy() + expect(hasTransform({ skewY: 45 })).toBeTruthy() + }) +}) diff --git a/packages/motion-dom/src/projection/utils/has-transform.ts b/packages/motion-dom/src/projection/utils/has-transform.ts index e76432b..b2ec3b7 100644 --- a/packages/motion-dom/src/projection/utils/has-transform.ts +++ b/packages/motion-dom/src/projection/utils/has-transform.ts @@ -21,6 +21,7 @@ export function hasTransform(values: ResolvedValues) { values.rotate || values.rotateX || values.rotateY || + values.rotateZ || values.skewX || values.skewY )