fix: diagram zoom not working on initial render
All checks were successful
CI / build (push) Successful in 2m10s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m32s
CI / deploy (push) Successful in 52s
CI / deploy-feature (push) Has been skipped

The wheel event listener was attached in a useEffect with empty deps,
but the SVG element doesn't exist during the loading state. Switch
svgRef from a plain ref to a callback ref that triggers re-attachment
when the SVG element becomes available after data loads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-12 22:05:35 +02:00
parent aaf9a00d67
commit d34abeb5cb

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState, type RefCallback } from 'react';
interface ZoomPanState {
scale: number;
@@ -23,7 +23,13 @@ export function useZoomPan() {
const didPan = useRef(false);
const panStart = useRef({ x: 0, y: 0 });
const containerRef = useRef<HTMLDivElement>(null);
const svgRef = useRef<SVGSVGElement>(null);
const svgElRef = useRef<SVGSVGElement | null>(null);
const [svgReady, setSvgReady] = useState(false);
const svgRef: RefCallback<SVGSVGElement> = useCallback((el: SVGSVGElement | null) => {
svgElRef.current = el;
setSvgReady(!!el);
}, []);
const clampScale = (s: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));
@@ -32,8 +38,9 @@ export function useZoomPan() {
// Attach wheel listener with { passive: false } so preventDefault() stops page scroll.
// React's onWheel is passive by default and cannot prevent scrolling.
// Re-runs when the SVG element becomes available (e.g. after loading state clears).
useEffect(() => {
const svg = svgRef.current;
const svg = svgElRef.current;
if (!svg) return;
const handler = (e: WheelEvent) => {
e.preventDefault();
@@ -56,7 +63,7 @@ export function useZoomPan() {
};
svg.addEventListener('wheel', handler, { passive: false });
return () => svg.removeEventListener('wheel', handler);
}, []);
}, [svgReady]);
const onPointerDown = useCallback(
(e: React.PointerEvent<SVGSVGElement>) => {