diff --git a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx index 1d4a9bf40..121ec4d91 100644 --- a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx +++ b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx @@ -1639,6 +1639,7 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { const [isSaving, setIsSaving] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false); const [editTitle, setEditTitle] = useState(""); + const safariGestureScaleRef = useRef(1); const isCanvasTextEntryTarget = useCallback((target: EventTarget | null) => { if (!(target instanceof HTMLElement)) { @@ -8418,10 +8419,49 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { }); }; - const handleGesture = (event: Event) => { + const handleGestureStart = (event: Event) => { + safariGestureScaleRef.current = 1; event.preventDefault(); }; + const handleGestureChange = (event: Event) => { + event.preventDefault(); + + const gestureEvent = event as Event & { + scale?: number; + clientX?: number; + clientY?: number; + target?: EventTarget | null; + }; + + if (isCanvasTextEntryTarget(gestureEvent.target ?? null)) { + return; + } + + const currentScale = + typeof gestureEvent.scale === "number" && + Number.isFinite(gestureEvent.scale) && + gestureEvent.scale > 0 + ? gestureEvent.scale + : 1; + + const zoomFactor = currentScale / safariGestureScaleRef.current; + safariGestureScaleRef.current = currentScale; + + if (!Number.isFinite(zoomFactor) || zoomFactor <= 0 || zoomFactor === 1) { + return; + } + + applyCanvasViewportZoom(zoomFactor, { + clientX: gestureEvent.clientX, + clientY: gestureEvent.clientY, + }); + }; + + const handleGestureEnd = () => { + safariGestureScaleRef.current = 1; + }; + window.addEventListener("keydown", handleBrowserZoomKeyDown, { capture: true, }); @@ -8429,12 +8469,13 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { passive: false, capture: true, }); - window.addEventListener("gesturestart", handleGesture, { + window.addEventListener("gesturestart", handleGestureStart, { passive: false, }); - window.addEventListener("gesturechange", handleGesture, { + window.addEventListener("gesturechange", handleGestureChange, { passive: false, }); + window.addEventListener("gestureend", handleGestureEnd); return () => { window.removeEventListener("keydown", handleBrowserZoomKeyDown, { @@ -8443,8 +8484,9 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) { window.removeEventListener("wheel", handleBrowserZoomWheel, { capture: true, }); - window.removeEventListener("gesturestart", handleGesture); - window.removeEventListener("gesturechange", handleGesture); + window.removeEventListener("gesturestart", handleGestureStart); + window.removeEventListener("gesturechange", handleGestureChange); + window.removeEventListener("gestureend", handleGestureEnd); }; }, [applyCanvasViewportZoom, isCanvasTextEntryTarget, setViewport]);