luxe-frontend/node_modules/motion-dom/dist/es/animation/utils/arc.mjs

189 lines
7.8 KiB
JavaScript

import { wrap } from 'motion-utils';
import { motionValue } from '../../value/index.mjs';
import { animateMotionValue } from '../interfaces/motion-value.mjs';
import { getValueTransition } from './get-value-transition.mjs';
const MIN_LAYOUT_DISTANCE = 20;
function bezierPoint(t, origin, control, target) {
const inv = 1 - t;
return inv * inv * origin + 2 * inv * t * control + t * t * target;
}
function bezierTangentAngle(t, originX, controlX, targetX, originY, controlY, targetY) {
const dx = 2 * (1 - t) * (controlX - originX) + 2 * t * (targetX - controlX);
const dy = 2 * (1 - t) * (controlY - originY) + 2 * t * (targetY - controlY);
return Math.atan2(dy, dx) * (180 / Math.PI);
}
function computeArcControlPoint(fromX, fromY, toX, toY, strength, peak) {
const deltaX = toX - fromX;
const deltaY = toY - fromY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
const normalPerpX = -deltaY / distance;
const normalPerpY = deltaX / distance;
const desiredHeight = strength * distance;
return {
x: fromX + deltaX * peak + normalPerpX * desiredHeight,
y: fromY + deltaY * peak + normalPerpY * desiredHeight,
};
}
return { x: fromX, y: fromY };
}
/**
* The pure sampling factory: `(from, to) => (t) => point`. Internal —
* used by {@link arc} and the unit tests. Not part of the public surface.
*/
function createArcPath({ strength = 0.5, peak = 0.5, direction, rotate = false, } = {}) {
const rotationScale = rotate === true ? 1 : typeof rotate === "number" ? rotate : 0;
// Auto-direction only: persists across calls to flip the bulge back
// onto the same screen side when the dominant axis changes between
// calls. Reuse the factory (module scope / useMemo) to keep this alive.
let prevBulgeSign;
const createInterpolator = (from, to) => {
const dx = to.x - from.x;
const dy = to.y - from.y;
let signed;
if (direction === "cw") {
signed = -strength;
}
else if (direction === "ccw") {
signed = strength;
}
else {
const dom = Math.abs(dx) >= Math.abs(dy) ? dx : dy;
signed = dom < 0 ? -strength : strength;
}
let control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
if (direction === undefined) {
const isVertical = Math.abs(dx) < Math.abs(dy);
const midX = from.x + dx * peak;
const midY = from.y + dy * peak;
const bulgeSign = isVertical
? Math.sign(control.x - midX)
: Math.sign(control.y - midY);
if (prevBulgeSign !== undefined &&
bulgeSign !== 0 &&
bulgeSign !== prevBulgeSign) {
signed = -signed;
control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
}
else if (bulgeSign !== 0) {
prevBulgeSign = bulgeSign;
}
}
const tangent0 = rotationScale
? bezierTangentAngle(0, from.x, control.x, to.x, from.y, control.y, to.y)
: 0;
const tangent1 = rotationScale
? bezierTangentAngle(1, from.x, control.x, to.x, from.y, control.y, to.y)
: 0;
const tangentDelta = rotationScale
? wrap(-180, 180, tangent1 - tangent0)
: 0;
return (t) => {
const out = {
x: bezierPoint(t, from.x, control.x, to.x),
y: bezierPoint(t, from.y, control.y, to.y),
};
if (rotationScale) {
const raw = bezierTangentAngle(t, from.x, control.x, to.x, from.y, control.y, to.y);
const baseline = tangent0 + tangentDelta * t;
out.rotate = wrap(-180, 180, raw - baseline) * rotationScale;
}
return out;
};
};
return createInterpolator;
}
/**
* Creates a curved path for `transition.path`:
*
* ```ts
* <motion.div animate={{ x: 200, y: 100 }} transition={{ path: arc() }} />
* ```
*
* Reuse the returned value (module scope / useMemo / useRef) so its
* continuity closure survives re-renders — a fresh `arc()` has no memory.
*/
function arc(options = {}) {
const sample = createArcPath(options);
const path = {
interpolateProjection(delta) {
// `from` is the current translate offset (carries any in-flight
// displacement when interrupted); `to` is the new layout origin.
// The distance floor avoids visible wobble on tiny shifts.
const tx = delta.x.translate;
const ty = delta.y.translate;
if (Math.sqrt(tx * tx + ty * ty) < MIN_LAYOUT_DISTANCE) {
return undefined;
}
return sample({ x: tx, y: ty }, { x: 0, y: 0 });
},
animateVisualElement(visualElement, target, transition, delay, animations) {
if (!("x" in target || "y" in target))
return;
const xValue = visualElement.getValue("x", visualElement.latestValues["x"] ?? 0);
const yValue = visualElement.getValue("y", visualElement.latestValues["y"] ?? 0);
const xRaw = target.x;
const yRaw = target.y;
const xFrom = (Array.isArray(xRaw) && xRaw[0] != null
? xRaw[0]
: xValue?.get()) ?? 0;
const yFrom = (Array.isArray(yRaw) && yRaw[0] != null
? yRaw[0]
: yValue?.get()) ?? 0;
const xTo = (Array.isArray(xRaw)
? xRaw[xRaw.length - 1]
: xRaw ?? xFrom);
const yTo = (Array.isArray(yRaw)
? yRaw[yRaw.length - 1]
: yRaw ?? yFrom);
// Interruption needs no flag: x/y already hold the displaced
// mid-arc position, so xFrom/yFrom carry the continuity geometry.
const interpolate = sample({ x: xFrom, y: yFrom }, { x: xTo, y: yTo });
// Drive a dedicated `pathRotation` value (composed onto `rotate`
// at the build sites) rather than `rotate` itself, so a
// concurrent rotate animation composes and nothing accumulates
// on interrupt.
const pathRotationValue = interpolate(0).rotate !== undefined
? visualElement.getValue("pathRotation", 0)
: undefined;
const pathTransition = {
delay,
...getValueTransition(transition || {}, "x"),
};
delete pathTransition.path;
const progress = motionValue(0);
progress.start(animateMotionValue("", progress, [0, 1000], {
...pathTransition,
isSync: true,
velocity: 0,
onUpdate: (latest) => {
const point = interpolate(latest / 1000);
xValue?.set(point.x);
yValue?.set(point.y);
if (pathRotationValue && point.rotate !== undefined) {
pathRotationValue.set(point.rotate);
}
},
onComplete: () => {
xValue?.set(xTo);
yValue?.set(yTo);
pathRotationValue?.set(0);
},
// Interrupt/cancel must clear our additive contribution
// so it can't linger on top of the user's `rotate`.
onStop: () => pathRotationValue?.set(0),
onCancel: () => pathRotationValue?.set(0),
}));
if (progress.animation)
animations.push(progress.animation);
delete target.x;
delete target.y;
},
};
return path;
}
export { arc, createArcPath };
//# sourceMappingURL=arc.mjs.map