UNPKG

motion-v

Version:

<h1 align="center"> <img width="35" height="35" alt="Motion logo" src="https://github.com/user-attachments/assets/00d6d1c3-72c4-4c2f-a664-69da13182ffc" /><br />Motion for Vue</h1>

307 lines (306 loc) 12.5 kB
import { getContextWindow } from "../../../utils/get-context-window.mjs"; import { extractEventInfo } from "../../../events/event-info.mjs"; import { addDomEvent } from "../../../events/add-dom-event.mjs"; import { addPointerEvent } from "../../../events/add-pointer-event.mjs"; import { isHTMLElement } from "./utils/is.mjs"; import { PanSession } from "../pan/PanSession.mjs"; import { getGlobalLock } from "./lock.mjs"; import { applyConstraints, calcOrigin, calcRelativeConstraints, calcViewportConstraints, defaultElastic, rebaseAxisConstraints, resolveDragElastic } from "./utils/constraints.mjs"; import { addValueToWillChange, animateMotionValue, calcLength, convertBoundingBoxToBox, convertBoxToBoundingBox, createBox, eachAxis, frame, measurePageBox, mixNumber, percent } from "motion-dom"; import { invariant } from "hey-listen"; const elementDragControls = /* @__PURE__ */ new WeakMap(); var VisualElementDragControls = class { constructor(state) { this.openGlobalLock = null; this.isDragging = false; this.currentDirection = null; this.originPoint = { x: 0, y: 0 }; this.constraints = false; this.hasMutatedConstraints = false; this.elastic = createBox(); this.state = state; } get visualElement() { return this.state.visualElement; } start(originEvent, { snapToCursor = false } = {}) { const onSessionStart = (event) => { if (snapToCursor) this.stopAnimation(); else this.pauseAnimation(); if (snapToCursor) this.snapToCursor(extractEventInfo(event, "page").point); }; const onStart = (event, info) => { this.stopAnimation(); const { drag, dragPropagation, onDragStart } = this.getProps(); if (drag && !dragPropagation) { if (this.openGlobalLock) this.openGlobalLock(); this.openGlobalLock = getGlobalLock(drag); if (!this.openGlobalLock) return; } this.isDragging = true; this.currentDirection = null; this.resolveConstraints(); if (this.visualElement.projection) { this.visualElement.projection.isAnimationBlocked = true; this.visualElement.projection.target = void 0; } eachAxis((axis) => { let current = this.getAxisMotionValue(axis).get() || 0; if (percent.test(current)) { const { projection } = this.visualElement; if (projection && projection.layout) { const measuredAxis = projection.layout.layoutBox[axis]; if (measuredAxis) current = calcLength(measuredAxis) * (parseFloat(current) / 100); } } this.originPoint[axis] = current; }); if (onDragStart) frame.postRender(() => onDragStart(event, info)); addValueToWillChange(this.visualElement, "transform"); this.state.setActive("whileDrag", true); }; const onMove = (event, info) => { const { dragPropagation, dragDirectionLock, onDirectionLock, onDrag } = this.getProps(); if (!dragPropagation && !this.openGlobalLock) return; const { offset } = info; if (dragDirectionLock && this.currentDirection === null) { this.currentDirection = getCurrentDirection(offset); if (this.currentDirection !== null) onDirectionLock && onDirectionLock(this.currentDirection); return; } this.updateAxis("x", info.point, offset); this.updateAxis("y", info.point, offset); this.visualElement.render(); onDrag && onDrag(event, info); }; const onSessionEnd = (event, info) => this.stop(event, info); const resumeAnimation = () => eachAxis((axis) => this.getAnimationState(axis) === "paused" && this.getAxisMotionValue(axis).animation?.play()); const { dragSnapToOrigin } = this.getProps(); this.panSession = new PanSession(originEvent, { onSessionStart, onStart, onMove, onSessionEnd, resumeAnimation }, { transformPagePoint: this.visualElement.getTransformPagePoint(), dragSnapToOrigin, contextWindow: getContextWindow(this.visualElement), element: this.state.element }); } stop(event, info) { const isDragging = this.isDragging; this.cancel(); if (!isDragging) return; const { velocity } = info; this.startAnimation(velocity); const { onDragEnd } = this.getProps(); if (onDragEnd) frame.postRender(() => onDragEnd(event, info)); } cancel() { this.isDragging = false; const { projection } = this.visualElement; if (projection) projection.isAnimationBlocked = false; this.panSession && this.panSession.end(); this.panSession = void 0; const { dragPropagation } = this.getProps(); if (!dragPropagation && this.openGlobalLock) { this.openGlobalLock(); this.openGlobalLock = null; } this.state.setActive("whileDrag", false); } updateAxis(axis, _point, offset) { const { drag } = this.getProps(); if (!offset || !shouldDrag(axis, drag, this.currentDirection)) return; const axisValue = this.getAxisMotionValue(axis); let next = this.originPoint[axis] + offset[axis]; if (this.constraints && this.constraints[axis]) next = applyConstraints(next, this.constraints[axis], this.elastic[axis]); axisValue.set(next); } resolveConstraints() { const { dragConstraints, dragElastic } = this.getProps(); const layout = this.visualElement.projection && !this.visualElement.projection.layout ? this.visualElement.projection.measure(false) : this.visualElement.projection?.layout; const prevConstraints = this.constraints; if (dragConstraints && isHTMLElement(dragConstraints)) { if (!this.constraints) this.constraints = this.resolveRefConstraints(); } else if (dragConstraints && layout) this.constraints = calcRelativeConstraints(layout.layoutBox, dragConstraints); else this.constraints = false; this.elastic = resolveDragElastic(dragElastic); if (prevConstraints !== this.constraints && layout && this.constraints && !this.hasMutatedConstraints) eachAxis((axis) => { if (this.constraints !== false && this.getAxisMotionValue(axis)) this.constraints[axis] = rebaseAxisConstraints(layout.layoutBox[axis], this.constraints[axis]); }); } resolveRefConstraints() { const { dragConstraints: constraints, onMeasureDragConstraints } = this.getProps(); if (!constraints || !isHTMLElement(constraints)) return false; const constraintsElement = constraints; invariant(constraintsElement !== null, "If `dragConstraints` is set as a React ref, that ref must be passed to another component's `ref` prop."); const { projection } = this.visualElement; if (!projection || !projection.layout) return false; const constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint()); let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, constraintsBox); if (onMeasureDragConstraints) { const userConstraints = onMeasureDragConstraints(convertBoxToBoundingBox(measuredConstraints)); this.hasMutatedConstraints = !!userConstraints; if (userConstraints) measuredConstraints = convertBoundingBoxToBox(userConstraints); } return measuredConstraints; } startAnimation(velocity) { const { drag, dragMomentum, dragElastic, dragTransition, dragSnapToOrigin, onDragTransitionEnd } = this.getProps(); const constraints = this.constraints || {}; const momentumAnimations = eachAxis((axis) => { if (!shouldDrag(axis, drag, this.currentDirection)) return; let transition = constraints && constraints[axis] || {}; if (dragSnapToOrigin) transition = { min: 0, max: 0 }; const bounceStiffness = dragElastic ? 200 : 1e6; const bounceDamping = dragElastic ? 40 : 1e7; const inertia = { type: "inertia", velocity: dragMomentum ? velocity[axis] : 0, bounceStiffness, bounceDamping, timeConstant: 750, restDelta: 1, restSpeed: 10, ...dragTransition, ...transition }; return this.startAxisValueAnimation(axis, inertia); }); return Promise.all(momentumAnimations).then(onDragTransitionEnd); } startAxisValueAnimation(axis, transition) { const axisValue = this.getAxisMotionValue(axis); addValueToWillChange(this.visualElement, axis); return axisValue.start(animateMotionValue(axis, axisValue, 0, transition, this.visualElement, false)); } stopAnimation() { if (!this.visualElement.projection?.isPresent) return; eachAxis((axis) => this.getAxisMotionValue(axis).stop()); } pauseAnimation() { eachAxis((axis) => this.getAxisMotionValue(axis).animation?.pause()); } getAnimationState(axis) { return this.getAxisMotionValue(axis).animation?.state; } getAxisMotionValue(axis) { const dragKey = `_drag${axis.toUpperCase()}`; const props = this.visualElement.getProps(); return props[dragKey] || this.visualElement.getValue(axis, (props.initial ? props.initial[axis] : void 0) || 0); } snapToCursor(point) { eachAxis((axis) => { const { drag } = this.getProps(); if (!shouldDrag(axis, drag, this.currentDirection)) return; const { projection } = this.visualElement; const axisValue = this.getAxisMotionValue(axis); if (projection && projection.layout) { const { min, max } = projection.layout.layoutBox[axis]; axisValue.set(point[axis] - mixNumber(min, max, .5)); } }); } scalePositionWithinConstraints() { if (!this.visualElement.current) return; const { drag, dragConstraints } = this.getProps(); const { projection } = this.visualElement; if (!isHTMLElement(dragConstraints) || !projection || !this.constraints) return; this.stopAnimation(); const boxProgress = { x: 0, y: 0 }; eachAxis((axis) => { const axisValue = this.getAxisMotionValue(axis); if (axisValue && this.constraints !== false) { const latest = axisValue.get(); boxProgress[axis] = calcOrigin({ min: latest, max: latest }, this.constraints[axis]); } }); const { transformTemplate } = this.visualElement.getProps(); this.state.element.style.transform = transformTemplate ? transformTemplate({}, "") : "none"; projection.root && projection.root.updateScroll(); projection.updateLayout(); this.resolveConstraints(); eachAxis((axis) => { if (!shouldDrag(axis, drag, null)) return; const axisValue = this.getAxisMotionValue(axis); const { min, max } = this.constraints[axis]; axisValue.set(mixNumber(min, max, boxProgress[axis])); }); } addListeners() { if (!this.state.element) return; elementDragControls.set(this.visualElement, this); const element = this.state.element; const stopPointerListener = addPointerEvent(element, "pointerdown", (event) => { const { drag, dragListener = true } = this.getProps(); drag && dragListener && this.start(event); }); const measureDragConstraints = () => { const { dragConstraints } = this.getProps(); if (isHTMLElement(dragConstraints)) this.constraints = this.resolveRefConstraints(); }; const { projection } = this.visualElement; const stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints); if (projection && !projection.layout) { projection.root && projection.root.updateScroll(); projection.updateLayout(); } frame.read(measureDragConstraints); const stopResizeListener = addDomEvent(window, "resize", () => this.scalePositionWithinConstraints()); const stopLayoutUpdateListener = projection.addEventListener("didUpdate", (({ delta, hasLayoutChanged }) => { if (this.isDragging && hasLayoutChanged) { eachAxis((axis) => { const motionValue$1 = this.getAxisMotionValue(axis); if (!motionValue$1) return; this.originPoint[axis] += delta[axis].translate; motionValue$1.set(motionValue$1.get() + delta[axis].translate); }); this.visualElement.render(); } })); return () => { stopResizeListener(); stopPointerListener(); stopMeasureLayoutListener(); stopLayoutUpdateListener && stopLayoutUpdateListener(); }; } getProps() { const props = this.visualElement.getProps(); const { drag = false, dragDirectionLock = false, dragPropagation = false, dragConstraints = false, dragElastic = defaultElastic, dragMomentum = true } = props; return { ...props, drag, dragDirectionLock, dragPropagation, dragConstraints, dragElastic, dragMomentum }; } }; function shouldDrag(direction, drag, currentDirection) { return (drag === true || drag === direction) && (currentDirection === null || currentDirection === direction); } function getCurrentDirection(offset, lockThreshold = 10) { let direction = null; if (Math.abs(offset.y) > lockThreshold) direction = "y"; else if (Math.abs(offset.x) > lockThreshold) direction = "x"; return direction; } export { VisualElementDragControls };