Upstream motiondivision/motion state of the touched files before 46bc6e6fee

This commit is contained in:
upstream committed 2026-09-11 23:06:36 +02:00
commit 1ba1c99cc1
6 files changed
+6391

No files matched your search

+5971
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,100 @@
import { mixValues } from "../mix-values"
describe("mixValues", () => {
test("mixes borderRadius numbers", () => {
const output = {}
mixValues(
output,
{ borderTopLeftRadius: 10 },
{ borderTopLeftRadius: 20 },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: 15 })
})
test("mixes borderRadius px", () => {
const output = {}
mixValues(
output,
{ borderTopLeftRadius: "10px" },
{ borderTopLeftRadius: "20px" },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: 15 })
})
test("mixes borderRadius percentage", () => {
const output = {}
mixValues(
output,
{ borderTopLeftRadius: "10%" },
{ borderTopLeftRadius: "20%" },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: "15%" })
})
test("mixes borderRadius percentage with 0", () => {
const output = {}
mixValues(
output,
{ borderTopLeftRadius: 0 },
{ borderTopLeftRadius: "20%" },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: "10%" })
mixValues(
output,
{ borderTopLeftRadius: "20%" },
{ borderTopLeftRadius: 0 },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: "10%" })
})
test("doesn't mix % with px", () => {
const output = {}
mixValues(
output,
{ borderTopLeftRadius: "10px" },
{ borderTopLeftRadius: "20%" },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: "20%" })
mixValues(
output,
{ borderTopLeftRadius: "20%" },
{ borderTopLeftRadius: "10px" },
0.5,
false,
false
)
expect(output).toEqual({ borderTopLeftRadius: "10px" })
})
})
@@ -0,0 +1,116 @@
import { mixNumber } from "../../utils/mix/number"
import { percent, px } from "../../value/types/numbers/units"
import type { AnyResolvedKeyframe } from "../../animation/types"
import {
progress as calcProgress,
circOut,
EasingFunction,
noop,
} from "motion-utils"
import type { ResolvedValues } from "../../node/types"
import { cornerRadiusProps } from "../../utils/border-radius"
const numBorders = cornerRadiusProps.length
const asNumber = (value: AnyResolvedKeyframe) =>
typeof value === "string" ? parseFloat(value) : value
const isPx = (value: AnyResolvedKeyframe) =>
typeof value === "number" || px.test(value)
export function mixValues(
target: ResolvedValues,
follow: ResolvedValues,
lead: ResolvedValues,
progress: number,
shouldCrossfadeOpacity: boolean,
isOnlyMember: boolean
) {
if (shouldCrossfadeOpacity) {
target.opacity = mixNumber(
0,
(lead.opacity as number) ?? 1,
easeCrossfadeIn(progress)
)
target.opacityExit = mixNumber(
(follow.opacity as number) ?? 1,
0,
easeCrossfadeOut(progress)
)
} else if (isOnlyMember) {
target.opacity = mixNumber(
(follow.opacity as number) ?? 1,
(lead.opacity as number) ?? 1,
progress
)
}
/**
* Mix border radius
*/
for (let i = 0; i < numBorders; i++) {
const borderLabel = cornerRadiusProps[i]
let followRadius = getRadius(follow, borderLabel)
let leadRadius = getRadius(lead, borderLabel)
if (followRadius === undefined && leadRadius === undefined) continue
followRadius ||= 0
leadRadius ||= 0
const canMix =
followRadius === 0 ||
leadRadius === 0 ||
isPx(followRadius) === isPx(leadRadius)
if (canMix) {
target[borderLabel] = Math.max(
mixNumber(
asNumber(followRadius),
asNumber(leadRadius),
progress
),
0
)
if (percent.test(leadRadius) || percent.test(followRadius)) {
target[borderLabel] += "%"
}
} else {
target[borderLabel] = leadRadius
}
}
/**
* Mix rotation
*/
if (follow.rotate || lead.rotate) {
target.rotate = mixNumber(
(follow.rotate as number) || 0,
(lead.rotate as number) || 0,
progress
)
}
}
function getRadius(values: ResolvedValues, radiusName: string) {
return values[radiusName] !== undefined
? values[radiusName]
: values.borderRadius
}
const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut)
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop)
function compress(
min: number,
max: number,
easing: EasingFunction
): EasingFunction {
return (p: number) => {
// Could replace ifs with clamp
if (p < min) return 0
if (p > max) return 1
return easing(calcProgress(min, max, p))
}
}
@@ -0,0 +1,105 @@
import { buildProjectionTransform } from "../transform"
import { createDelta } from "../../geometry/models"
describe("buildProjectionTransform", () => {
it("Returns 'none' when no transform required", () => {
expect(buildProjectionTransform(createDelta(), { x: 1, y: 1 })).toEqual(
"none"
)
})
it("Creates the expected transform for the provided arguments", () => {
const delta = {
x: {
translate: 100,
scale: 2,
origin: 0.5,
originPoint: 100,
},
y: {
translate: 300,
scale: 4,
origin: 0.5,
originPoint: 100,
},
}
expect(buildProjectionTransform(delta, { x: 1, y: 1 })).toEqual(
"translate3d(100px, 300px, 0px) scale(2, 4)"
)
expect(buildProjectionTransform(delta, { x: 2, y: 0.5 })).toEqual(
"translate3d(50px, 600px, 0px) scale(0.5, 2) scale(4, 2)"
)
expect(
buildProjectionTransform(delta, { x: 2, y: 0.5 }, { rotate: 45 })
).toEqual(
"translate3d(50px, 600px, 0px) scale(0.5, 2) rotate(45deg) scale(4, 2)"
)
expect(
buildProjectionTransform(
{
x: {
translate: 100,
scale: 0.5,
origin: 0.5,
originPoint: 100,
},
y: {
translate: 300,
scale: 0.5,
origin: 0.5,
originPoint: 100,
},
},
{ x: 2, y: 2 },
{ rotate: 90 }
)
).toEqual(
"translate3d(50px, 150px, 0px) scale(0.5, 0.5) rotate(90deg) "
)
})
it("Doesn't apply unneccessary translation", () => {
const delta = {
x: {
translate: 0,
scale: 2,
origin: 0.5,
originPoint: 100,
},
y: {
translate: 0,
scale: 4,
origin: 0.5,
originPoint: 100,
},
}
expect(
buildProjectionTransform(delta, { x: 2, y: 4 }, { rotate: 10 })
).toEqual("scale(0.5, 0.25) rotate(10deg) scale(4, 16)")
})
it("Doesn't apply unneccessary tree scale", () => {
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 }, { rotate: 10 })
).toEqual("translate3d(100px, 100px, 0px) rotate(10deg) scale(2, 4)")
})
})
@@ -0,0 +1,64 @@
import type { Delta, Point } from "motion-utils"
import type { ResolvedValues } from "../../node/types"
export function buildProjectionTransform(
delta: Delta,
treeScale: Point,
latestTransform?: ResolvedValues
): string {
let transform = ""
/**
* The translations we use to calculate are always relative to the viewport coordinate space.
* But when we apply scales, we also scale the coordinate space of an element and its children.
* For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need
* to move an element 100 pixels, we actually need to move it 200 in within that scaled space.
*/
const xTranslate = delta.x.translate / treeScale.x
const yTranslate = delta.y.translate / treeScale.y
const zTranslate = latestTransform?.z || 0
if (xTranslate || yTranslate || zTranslate) {
transform = `translate3d(${xTranslate}px, ${yTranslate}px, ${zTranslate}px) `
}
/**
* Apply scale correction for the tree transform.
* This will apply scale to the screen-orientated axes.
*/
if (treeScale.x !== 1 || treeScale.y !== 1) {
transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `
}
if (latestTransform) {
const {
transformPerspective,
rotate,
pathRotation,
rotateX,
rotateY,
skewX,
skewY,
} = latestTransform
if (transformPerspective)
transform = `perspective(${transformPerspective}px) ${transform}`
if (rotate) transform += `rotate(${rotate}deg) `
// Additive `rotate()` so user `rotate` isn't clobbered.
if (pathRotation) transform += `rotate(${pathRotation}deg) `
if (rotateX) transform += `rotateX(${rotateX}deg) `
if (rotateY) transform += `rotateY(${rotateY}deg) `
if (skewX) transform += `skewX(${skewX}deg) `
if (skewY) transform += `skewY(${skewY}deg) `
}
/**
* Apply scale to match the size of the element to the size we want it.
* This will apply scale to the element-orientated axes.
*/
const elementScaleX = delta.x.scale * treeScale.x
const elementScaleY = delta.y.scale * treeScale.y
if (elementScaleX !== 1 || elementScaleY !== 1) {
transform += `scale(${elementScaleX}, ${elementScaleY})`
}
return transform || "none"
}
@@ -0,0 +1,35 @@
import { type AnyResolvedKeyframe } from "../../animation/types"
import { ResolvedValues } from "../../render/types"
function isIdentityScale(scale: AnyResolvedKeyframe | undefined) {
return scale === undefined || scale === 1
}
export function hasScale({ scale, scaleX, scaleY }: ResolvedValues) {
return (
!isIdentityScale(scale) ||
!isIdentityScale(scaleX) ||
!isIdentityScale(scaleY)
)
}
export function hasTransform(values: ResolvedValues) {
return (
hasScale(values) ||
has2DTranslate(values) ||
values.z ||
values.rotate ||
values.rotateX ||
values.rotateY ||
values.skewX ||
values.skewY
)
}
export function has2DTranslate(values: ResolvedValues) {
return is2DTranslate(values.x) || is2DTranslate(values.y)
}
function is2DTranslate(value: AnyResolvedKeyframe | undefined) {
return value && value !== "0%"
}