diff --git a/src/projection/animation/__tests__/mix-values.test.ts b/src/projection/animation/__tests__/mix-values.test.ts new file mode 100644 index 0000000000..7c98a2b7bd --- /dev/null +++ b/src/projection/animation/__tests__/mix-values.test.ts @@ -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" }) + }) +}) diff --git a/src/projection/animation/mix-values.ts b/src/projection/animation/mix-values.ts index b8890115ff..09f5589ac6 100644 --- a/src/projection/animation/mix-values.ts +++ b/src/projection/animation/mix-values.ts @@ -1,10 +1,17 @@ import { circOut, linear, mix, progress as calcProgress } from "popmotion" +import { percent, px } from "style-value-types" import { ResolvedValues } from "../../render/types" import { EasingFunction } from "../../types" const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"] const numBorders = borders.length +const asNumber = (value: string | number) => + typeof value === "string" ? parseFloat(value) : value + +const isPx = (value: string | number) => + typeof value === "number" || px.test(value) + export function mixValues( target: ResolvedValues, follow: ResolvedValues, @@ -47,17 +54,22 @@ export function mixValues( followRadius ||= 0 leadRadius ||= 0 - /** - * Currently we're only crossfading between numerical border radius. - * It would be possible to crossfade between percentages for a little - * extra bundle size. - */ - if ( - typeof followRadius === "number" && - typeof leadRadius === "number" - ) { - const radius = Math.max(mix(followRadius, leadRadius, progress), 0) - target[borderLabel] = radius + const canMix = + followRadius === 0 || + leadRadius === 0 || + isPx(followRadius) === isPx(leadRadius) + + if (canMix) { + target[borderLabel] = Math.max( + mix(asNumber(followRadius), asNumber(leadRadius), progress), + 0 + ) + + if (percent.test(leadRadius) || percent.test(followRadius)) { + target[borderLabel] += "%" + } + } else { + target[borderLabel] = leadRadius } } diff --git a/src/projection/styles/scale-border-radius.ts b/src/projection/styles/scale-border-radius.ts index 2a57d84ff9..4dabee158c 100644 --- a/src/projection/styles/scale-border-radius.ts +++ b/src/projection/styles/scale-border-radius.ts @@ -17,6 +17,7 @@ export function pixelsToPercent(pixels: number, axis: Axis): number { export const correctBorderRadius: ScaleCorrectorDefinition = { correct: (latest, node) => { if (!node.target) return latest + /** * If latest is a string, if it's a percentage we can return immediately as it's * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.