diff --git a/frontend/src/lib/person/genealogy/StammbaumTree.svelte b/frontend/src/lib/person/genealogy/StammbaumTree.svelte index db74a366..e2aa74c2 100644 --- a/frontend/src/lib/person/genealogy/StammbaumTree.svelte +++ b/frontend/src/lib/person/genealogy/StammbaumTree.svelte @@ -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(() => { if (gutterWidth === 0) return []; @@ -236,6 +253,15 @@ const parentLinks = $derived.by(() => { 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" >