From b170085311b484b112eadac09412a46708e8ec40 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 29 May 2026 18:54:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(stammbaum):=20node=20tap=20stopped=20select?= =?UTF-8?q?ing=20=E2=80=94=20defer=20pointer=20capture=20to=20drag=20start?= =?UTF-8?q?=20(#692)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capturing the pointer on pointerdown made the browser dispatch the trailing click at the SVG instead of the node under the finger, so node taps silently stopped opening the person panel. Capture only once a drag crosses the threshold; a tap now reaches the node's onclick. Verified live. Co-Authored-By: Claude Opus 4.8 --- .../lib/person/genealogy/panZoomGestures.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/person/genealogy/panZoomGestures.ts b/frontend/src/lib/person/genealogy/panZoomGestures.ts index 8173d93c..45f6bf52 100644 --- a/frontend/src/lib/person/genealogy/panZoomGestures.ts +++ b/frontend/src/lib/person/genealogy/panZoomGestures.ts @@ -104,11 +104,11 @@ export const panZoomGestures: Action = (no const onPointerDown = (e: PointerEvent) => { cancelInertia(); - try { - node.setPointerCapture(e.pointerId); - } catch { - /* pointer not capturable (e.g. synthetic event) — drag still works */ - } + // NB: do NOT capture the pointer here — capturing on pointerdown makes the + // browser dispatch the trailing `click` at this element instead of the + // node under the pointer, which silently breaks node selection (a tap must + // still reach the node's onclick). Capture is deferred to the first move + // that crosses the drag threshold (see onPointerMove). pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); p.onGestureStart?.(); @@ -146,7 +146,17 @@ export const panZoomGestures: Action = (no if (!dragging) return; const dxPx = e.clientX - lastX; const dyPx = e.clientY - lastY; - if (!moved && Math.hypot(dxPx, dyPx) >= DRAG_THRESHOLD_PX) moved = true; + if (!moved && Math.hypot(dxPx, dyPx) >= DRAG_THRESHOLD_PX) { + // A real drag has started — now capture so we keep receiving move/up + // even if the pointer leaves the canvas. (Deferred from pointerdown so + // taps still select nodes.) + moved = true; + try { + node.setPointerCapture(e.pointerId); + } catch { + /* pointer not capturable (e.g. synthetic event) — drag still works */ + } + } const { dx, dy } = screenDeltaToSvg( dxPx,