refactor(stammbaum): extract + unit-test pinch and inertia math (#692)

Move the pinch-zoom (pinchZoom) and inertia-step (stepInertia) geometry out of
the panZoomGestures DOM glue into pure, unit-tested helpers in panZoom.ts, with
named FRAME_MS/INERTIA_* constants. Addresses the QA blocker that the gesture
module's core math was untested. No behaviour change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-29 18:47:29 +02:00
parent c1dd6d299f
commit f4b631e1bc
3 changed files with 75 additions and 5 deletions

View File

@@ -130,6 +130,43 @@ export function zoomAtPoint(
};
}
/** Assumed milliseconds per animation frame, used to scale inertia velocity. */
export const FRAME_MS = 16;
/** Per-frame velocity decay for pan inertia (OQ-004). */
export const INERTIA_DECAY = 0.92;
/** Inertia stops once the velocity (svg units per ms) drops below this. */
export const INERTIA_MIN_SPEED = 0.02;
/**
* Pinch zoom around the gesture centroid (US-PAN-002/003). The new zoom is the
* start zoom scaled by the finger-distance ratio (clamped); the anchor offset
* keeps the centroid fixed via {@link zoomAtPoint}.
*/
export function pinchZoom(
state: PanZoomState,
startZoom: number,
startDist: number,
currentDist: number,
anchorX: number,
anchorY: number
): PanZoomState {
const ratio = startDist > 0 ? currentDist / startDist : 1;
return zoomAtPoint(state, clampZoom(startZoom * ratio), anchorX, anchorY);
}
/**
* Advance the pan by one inertia frame: continue the release velocity (svg units
* per ms) in the drag direction, scaled by the frame duration. Zoom is untouched.
*/
export function stepInertia(
state: PanZoomState,
velX: number,
velY: number,
frameMs: number = FRAME_MS
): PanZoomState {
return { x: state.x - velX * frameMs, y: state.y - velY * frameMs, z: state.z };
}
/** Linearly interpolate between two view states (drives fit/recentre tweening). */
export function lerpView(from: PanZoomState, to: PanZoomState, t: number): PanZoomState {
return {