Keep rotateZ on elements with a layout animation

The projection pipeline handled rotate, rotateX and rotateY but not
rotateZ, so a layout or shared layout animation silently dropped it from
the generated transform and never mixed it between lead and follow.
This commit is contained in:
dualfroz committed 2026-09-11 23:06:36 +02:00
1 parent 1ba1c99cc1
commit 2e01f31175
7 files changed
+83

No files matched your search

+4
View File
@@ -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
@@ -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 })
})
})
@@ -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) {
@@ -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)"
)
})
})
@@ -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) `
}
@@ -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()
})
})
@@ -21,6 +21,7 @@ export function hasTransform(values: ResolvedValues) {
values.rotate ||
values.rotateX ||
values.rotateY ||
values.rotateZ ||
values.skewX ||
values.skewY
)