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

@@ -4,6 +4,11 @@ import {
clampZoom,
screenDeltaToSvg,
zoomAtPoint,
pinchZoom,
stepInertia,
FRAME_MS,
INERTIA_DECAY,
INERTIA_MIN_SPEED,
ZOOM_STEP_KB,
type PanZoomState
} from '$lib/person/genealogy/panZoom';
@@ -25,8 +30,6 @@ export interface PanZoomGesturesParams {
/** Pointer movement (px) below which a drag is treated as a tap, not a pan. */
const DRAG_THRESHOLD_PX = 4;
const INERTIA_DECAY = 0.92;
const INERTIA_MIN_SPEED = 0.02; // svg units per ms
/**
* Touch/mouse/wheel pan & zoom for the Stammbaum canvas (#692). Thin DOM glue:
@@ -86,7 +89,7 @@ export const panZoomGestures: Action<SVGSVGElement, PanZoomGesturesParams> = (no
if (Math.hypot(velX, velY) < INERTIA_MIN_SPEED) return;
const step = () => {
const before = current;
emit({ ...current, x: current.x - velX * 16, y: current.y - velY * 16 });
emit(stepInertia(current, velX, velY, FRAME_MS));
velX *= INERTIA_DECAY;
velY *= INERTIA_DECAY;
const stalled = current.x === before.x && current.y === before.y;
@@ -135,8 +138,7 @@ export const panZoomGestures: Action<SVGSVGElement, PanZoomGesturesParams> = (no
const dist = distance(a, b) || 1;
const centroid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
const anchor = anchorOffset(centroid.x, centroid.y);
const targetZoom = clampZoom(pinchStartZoom * (dist / pinchStartDist));
emit(zoomAtPoint(current, targetZoom, anchor.x, anchor.y));
emit(pinchZoom(current, pinchStartZoom, pinchStartDist, dist, anchor.x, anchor.y));
moved = true;
return;
}