Upstream motiondivision/motion state of the touched files before 46bc6e6fee
This commit is contained in:
commit
1ba1c99cc1
6 files changed
+6391
No files matched your search
+5971
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%"
|
||||
}
|
||||
Reference in new issue
Block a user