feat(stammbaum): touch/mouse/wheel pan & pinch zoom gestures (#692)
Add a panZoomGestures action: one-finger/left-button drag pans, two-finger pinch and Ctrl+wheel zoom around the centroid, plain wheel pans. Pan is edge-clamped via clampPan (no infinite scroll), a real drag suppresses the trailing node click, and inertia decays after release unless prefers-reduced- motion. Canvas container switches from native scroll to overflow-hidden. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
type Layout
|
||||
} from '$lib/person/genealogy/layout/buildLayout';
|
||||
import { type PanZoomState, clampZoom, ZOOM_STEP_KB } from '$lib/person/genealogy/panZoom';
|
||||
import { panZoomGestures } from '$lib/person/genealogy/panZoomGestures';
|
||||
|
||||
type PersonNodeDTO = components['schemas']['PersonNodeDTO'];
|
||||
type RelationshipDTO = components['schemas']['RelationshipDTO'];
|
||||
@@ -67,6 +68,22 @@ $effect(() => {
|
||||
const gutterVisible = $derived(showGutter ?? isMdOrUp);
|
||||
const gutterWidth = $derived(gutterVisible ? GUTTER_WIDTH_DESKTOP : 0);
|
||||
|
||||
// Reduced-motion preference disables pan inertia and animated transitions
|
||||
// (REQ-PAN-005). Seeded synchronously like the gutter state above.
|
||||
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';
|
||||
let reducedMotion = $state(
|
||||
typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
||||
? window.matchMedia(REDUCED_MOTION_QUERY).matches
|
||||
: false
|
||||
);
|
||||
$effect(() => {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
||||
const mq = window.matchMedia(REDUCED_MOTION_QUERY);
|
||||
const handler = (e: MediaQueryListEvent) => (reducedMotion = e.matches);
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
});
|
||||
|
||||
type GutterRow = { rank: number; y: number; label: number | null };
|
||||
const gutterRows = $derived.by<GutterRow[]>(() => {
|
||||
if (gutterWidth === 0) return [];
|
||||
@@ -236,6 +253,15 @@ const parentLinks = $derived.by<ParentLinks>(() => {
|
||||
aria-label="Stammbaum"
|
||||
tabindex="0"
|
||||
onkeydown={handleCanvasKey}
|
||||
use:panZoomGestures={{
|
||||
state: panZoom,
|
||||
baseW: baseDims.w,
|
||||
baseH: baseDims.h,
|
||||
baseCentreX: baseCentre.x,
|
||||
baseCentreY: baseCentre.y,
|
||||
reducedMotion,
|
||||
onPanZoom
|
||||
}}
|
||||
class="block h-full w-full"
|
||||
>
|
||||
<!-- Gutter stripe underlay (#689) — decorative full-row bands alternating
|
||||
|
||||
Reference in New Issue
Block a user