diff options
| author | nexxeln <[email protected]> | 2025-12-02 18:37:24 +0000 |
|---|---|---|
| committer | nexxeln <[email protected]> | 2025-12-02 18:37:24 +0000 |
| commit | dfb0c05ab33cb20537002eaeb896e6b2ab35af25 (patch) | |
| tree | 49ecaa46903671d96f2f9ebc5af688ab2ea2c7bd /packages/memory-graph/src/hooks/use-graph-interactions.ts | |
| parent | Fix: Update discord links in README.md and CONTRIBUTING.md (#598) (diff) | |
| download | supermemory-update-memory-graph.tar.xz supermemory-update-memory-graph.zip | |
add spaces selector with search (#600)update-memory-graph
relevant files to review:
\- memory-graph.tsx
\- spaces-dropdown.tsx
\- spaces-dropdown.css.ts
Diffstat (limited to 'packages/memory-graph/src/hooks/use-graph-interactions.ts')
| -rw-r--r-- | packages/memory-graph/src/hooks/use-graph-interactions.ts | 466 |
1 files changed, 233 insertions, 233 deletions
diff --git a/packages/memory-graph/src/hooks/use-graph-interactions.ts b/packages/memory-graph/src/hooks/use-graph-interactions.ts index fa794397..94fc88ee 100644 --- a/packages/memory-graph/src/hooks/use-graph-interactions.ts +++ b/packages/memory-graph/src/hooks/use-graph-interactions.ts @@ -1,48 +1,48 @@ -"use client"; +"use client" -import { useCallback, useRef, useState } from "react"; -import { GRAPH_SETTINGS } from "@/constants"; -import type { GraphNode } from "@/types"; +import { useCallback, useRef, useState } from "react" +import { GRAPH_SETTINGS } from "@/constants" +import type { GraphNode } from "@/types" export function useGraphInteractions( variant: "console" | "consumer" = "console", ) { - const settings = GRAPH_SETTINGS[variant]; - - const [panX, setPanX] = useState(settings.initialPanX); - const [panY, setPanY] = useState(settings.initialPanY); - const [zoom, setZoom] = useState(settings.initialZoom); - const [isPanning, setIsPanning] = useState(false); - const [panStart, setPanStart] = useState({ x: 0, y: 0 }); - const [hoveredNode, setHoveredNode] = useState<string | null>(null); - const [selectedNode, setSelectedNode] = useState<string | null>(null); - const [draggingNodeId, setDraggingNodeId] = useState<string | null>(null); + const settings = GRAPH_SETTINGS[variant] + + const [panX, setPanX] = useState(settings.initialPanX) + const [panY, setPanY] = useState(settings.initialPanY) + const [zoom, setZoom] = useState(settings.initialZoom) + const [isPanning, setIsPanning] = useState(false) + const [panStart, setPanStart] = useState({ x: 0, y: 0 }) + const [hoveredNode, setHoveredNode] = useState<string | null>(null) + const [selectedNode, setSelectedNode] = useState<string | null>(null) + const [draggingNodeId, setDraggingNodeId] = useState<string | null>(null) const [dragStart, setDragStart] = useState({ x: 0, y: 0, nodeX: 0, nodeY: 0, - }); + }) const [nodePositions, setNodePositions] = useState< Map<string, { x: number; y: number }> - >(new Map()); + >(new Map()) // Touch gesture state const [touchState, setTouchState] = useState<{ - touches: { id: number; x: number; y: number }[]; - lastDistance: number; - lastCenter: { x: number; y: number }; - isGesturing: boolean; + touches: { id: number; x: number; y: number }[] + lastDistance: number + lastCenter: { x: number; y: number } + isGesturing: boolean }>({ touches: [], lastDistance: 0, lastCenter: { x: 0, y: 0 }, isGesturing: false, - }); + }) // Animation state for smooth transitions - const animationRef = useRef<number | null>(null); - const [isAnimating, setIsAnimating] = useState(false); + const animationRef = useRef<number | null>(null) + const [isAnimating, setIsAnimating] = useState(false) // Smooth animation helper const animateToViewState = useCallback( @@ -53,219 +53,219 @@ export function useGraphInteractions( duration = 300, ) => { if (animationRef.current) { - cancelAnimationFrame(animationRef.current); + cancelAnimationFrame(animationRef.current) } - const startPanX = panX; - const startPanY = panY; - const startZoom = zoom; - const startTime = Date.now(); + const startPanX = panX + const startPanY = panY + const startZoom = zoom + const startTime = Date.now() - setIsAnimating(true); + setIsAnimating(true) const animate = () => { - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / duration, 1); + const elapsed = Date.now() - startTime + const progress = Math.min(elapsed / duration, 1) // Ease out cubic function for smooth transitions - const easeOut = 1 - (1 - progress) ** 3; + const easeOut = 1 - (1 - progress) ** 3 - const currentPanX = startPanX + (targetPanX - startPanX) * easeOut; - const currentPanY = startPanY + (targetPanY - startPanY) * easeOut; - const currentZoom = startZoom + (targetZoom - startZoom) * easeOut; + const currentPanX = startPanX + (targetPanX - startPanX) * easeOut + const currentPanY = startPanY + (targetPanY - startPanY) * easeOut + const currentZoom = startZoom + (targetZoom - startZoom) * easeOut - setPanX(currentPanX); - setPanY(currentPanY); - setZoom(currentZoom); + setPanX(currentPanX) + setPanY(currentPanY) + setZoom(currentZoom) if (progress < 1) { - animationRef.current = requestAnimationFrame(animate); + animationRef.current = requestAnimationFrame(animate) } else { - setIsAnimating(false); - animationRef.current = null; + setIsAnimating(false) + animationRef.current = null } - }; + } - animate(); + animate() }, [panX, panY, zoom], - ); + ) // Node drag handlers const handleNodeDragStart = useCallback( (nodeId: string, e: React.MouseEvent, nodes?: GraphNode[]) => { - const node = nodes?.find((n) => n.id === nodeId); - if (!node) return; + const node = nodes?.find((n) => n.id === nodeId) + if (!node) return - setDraggingNodeId(nodeId); + setDraggingNodeId(nodeId) setDragStart({ x: e.clientX, y: e.clientY, nodeX: node.x, nodeY: node.y, - }); + }) }, [], - ); + ) const handleNodeDragMove = useCallback( (e: React.MouseEvent) => { - if (!draggingNodeId) return; + if (!draggingNodeId) return - const deltaX = (e.clientX - dragStart.x) / zoom; - const deltaY = (e.clientY - dragStart.y) / zoom; + const deltaX = (e.clientX - dragStart.x) / zoom + const deltaY = (e.clientY - dragStart.y) / zoom - const newX = dragStart.nodeX + deltaX; - const newY = dragStart.nodeY + deltaY; + const newX = dragStart.nodeX + deltaX + const newY = dragStart.nodeY + deltaY setNodePositions((prev) => new Map(prev).set(draggingNodeId, { x: newX, y: newY }), - ); + ) }, [draggingNodeId, dragStart, zoom], - ); + ) const handleNodeDragEnd = useCallback(() => { - setDraggingNodeId(null); - }, []); + setDraggingNodeId(null) + }, []) // Pan handlers const handlePanStart = useCallback( (e: React.MouseEvent) => { - setIsPanning(true); - setPanStart({ x: e.clientX - panX, y: e.clientY - panY }); + setIsPanning(true) + setPanStart({ x: e.clientX - panX, y: e.clientY - panY }) }, [panX, panY], - ); + ) const handlePanMove = useCallback( (e: React.MouseEvent) => { - if (!isPanning || draggingNodeId) return; + if (!isPanning || draggingNodeId) return - const newPanX = e.clientX - panStart.x; - const newPanY = e.clientY - panStart.y; - setPanX(newPanX); - setPanY(newPanY); + const newPanX = e.clientX - panStart.x + const newPanY = e.clientY - panStart.y + setPanX(newPanX) + setPanY(newPanY) }, [isPanning, panStart, draggingNodeId], - ); + ) const handlePanEnd = useCallback(() => { - setIsPanning(false); - }, []); + setIsPanning(false) + }, []) // Zoom handlers const handleWheel = useCallback( (e: React.WheelEvent) => { // Always prevent default to stop browser navigation - e.preventDefault(); - e.stopPropagation(); + e.preventDefault() + e.stopPropagation() // Handle horizontal scrolling (trackpad swipe) by converting to pan if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { // Horizontal scroll - pan the graph instead of zooming - const panDelta = e.deltaX * 0.5; - setPanX((prev) => prev - panDelta); - return; + const panDelta = e.deltaX * 0.5 + setPanX((prev) => prev - panDelta) + return } // Vertical scroll - zoom behavior - const delta = e.deltaY > 0 ? 0.97 : 1.03; - const newZoom = Math.max(0.05, Math.min(3, zoom * delta)); + const delta = e.deltaY > 0 ? 0.97 : 1.03 + const newZoom = Math.max(0.05, Math.min(3, zoom * delta)) // Get mouse position relative to the viewport - let mouseX = e.clientX; - let mouseY = e.clientY; + let mouseX = e.clientX + let mouseY = e.clientY // Try to get the container bounds to make coordinates relative to the graph container - const target = e.currentTarget; + const target = e.currentTarget if (target && "getBoundingClientRect" in target) { - const rect = target.getBoundingClientRect(); - mouseX = e.clientX - rect.left; - mouseY = e.clientY - rect.top; + const rect = target.getBoundingClientRect() + mouseX = e.clientX - rect.left + mouseY = e.clientY - rect.top } // Calculate the world position of the mouse cursor - const worldX = (mouseX - panX) / zoom; - const worldY = (mouseY - panY) / zoom; + const worldX = (mouseX - panX) / zoom + const worldY = (mouseY - panY) / zoom // Calculate new pan to keep the mouse position stationary - const newPanX = mouseX - worldX * newZoom; - const newPanY = mouseY - worldY * newZoom; + const newPanX = mouseX - worldX * newZoom + const newPanY = mouseY - worldY * newZoom - setZoom(newZoom); - setPanX(newPanX); - setPanY(newPanY); + setZoom(newZoom) + setPanX(newPanX) + setPanY(newPanY) }, [zoom, panX, panY], - ); + ) const zoomIn = useCallback( (centerX?: number, centerY?: number, animate = true) => { - const zoomFactor = 1.2; - const newZoom = Math.min(3, zoom * zoomFactor); // Increased max zoom to 3x + const zoomFactor = 1.2 + const newZoom = Math.min(3, zoom * zoomFactor) // Increased max zoom to 3x if (centerX !== undefined && centerY !== undefined) { // Mouse-centered zoom for programmatic zoom in - const worldX = (centerX - panX) / zoom; - const worldY = (centerY - panY) / zoom; - const newPanX = centerX - worldX * newZoom; - const newPanY = centerY - worldY * newZoom; + const worldX = (centerX - panX) / zoom + const worldY = (centerY - panY) / zoom + const newPanX = centerX - worldX * newZoom + const newPanY = centerY - worldY * newZoom if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, newZoom, 200); + animateToViewState(newPanX, newPanY, newZoom, 200) } else { - setZoom(newZoom); - setPanX(newPanX); - setPanY(newPanY); + setZoom(newZoom) + setPanX(newPanX) + setPanY(newPanY) } } else { if (animate && !isAnimating) { - animateToViewState(panX, panY, newZoom, 200); + animateToViewState(panX, panY, newZoom, 200) } else { - setZoom(newZoom); + setZoom(newZoom) } } }, [zoom, panX, panY, isAnimating, animateToViewState], - ); + ) const zoomOut = useCallback( (centerX?: number, centerY?: number, animate = true) => { - const zoomFactor = 0.8; - const newZoom = Math.max(0.05, zoom * zoomFactor); // Decreased min zoom to 0.05x + const zoomFactor = 0.8 + const newZoom = Math.max(0.05, zoom * zoomFactor) // Decreased min zoom to 0.05x if (centerX !== undefined && centerY !== undefined) { // Mouse-centered zoom for programmatic zoom out - const worldX = (centerX - panX) / zoom; - const worldY = (centerY - panY) / zoom; - const newPanX = centerX - worldX * newZoom; - const newPanY = centerY - worldY * newZoom; + const worldX = (centerX - panX) / zoom + const worldY = (centerY - panY) / zoom + const newPanX = centerX - worldX * newZoom + const newPanY = centerY - worldY * newZoom if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, newZoom, 200); + animateToViewState(newPanX, newPanY, newZoom, 200) } else { - setZoom(newZoom); - setPanX(newPanX); - setPanY(newPanY); + setZoom(newZoom) + setPanX(newPanX) + setPanY(newPanY) } } else { if (animate && !isAnimating) { - animateToViewState(panX, panY, newZoom, 200); + animateToViewState(panX, panY, newZoom, 200) } else { - setZoom(newZoom); + setZoom(newZoom) } } }, [zoom, panX, panY, isAnimating, animateToViewState], - ); + ) const resetView = useCallback(() => { - setPanX(settings.initialPanX); - setPanY(settings.initialPanY); - setZoom(settings.initialZoom); - setNodePositions(new Map()); - }, [settings]); + setPanX(settings.initialPanX) + setPanY(settings.initialPanY) + setZoom(settings.initialZoom) + setNodePositions(new Map()) + }, [settings]) // Auto-fit graph to viewport const autoFitToViewport = useCallback( @@ -275,74 +275,74 @@ export function useGraphInteractions( viewportHeight: number, options?: { occludedRightPx?: number; animate?: boolean }, ) => { - if (nodes.length === 0) return; + if (nodes.length === 0) return // Find the bounds of all nodes - let minX = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - let minY = Number.POSITIVE_INFINITY; - let maxY = Number.NEGATIVE_INFINITY; + let minX = Number.POSITIVE_INFINITY + let maxX = Number.NEGATIVE_INFINITY + let minY = Number.POSITIVE_INFINITY + let maxY = Number.NEGATIVE_INFINITY nodes.forEach((node) => { - minX = Math.min(minX, node.x - node.size / 2); - maxX = Math.max(maxX, node.x + node.size / 2); - minY = Math.min(minY, node.y - node.size / 2); - maxY = Math.max(maxY, node.y + node.size / 2); - }); + minX = Math.min(minX, node.x - node.size / 2) + maxX = Math.max(maxX, node.x + node.size / 2) + minY = Math.min(minY, node.y - node.size / 2) + maxY = Math.max(maxY, node.y + node.size / 2) + }) // Calculate the center of the content - const contentCenterX = (minX + maxX) / 2; - const contentCenterY = (minY + maxY) / 2; + const contentCenterX = (minX + maxX) / 2 + const contentCenterY = (minY + maxY) / 2 // Calculate the size of the content - const contentWidth = maxX - minX; - const contentHeight = maxY - minY; + const contentWidth = maxX - minX + const contentHeight = maxY - minY // Add padding (20% on each side) - const paddingFactor = 1.4; - const paddedWidth = contentWidth * paddingFactor; - const paddedHeight = contentHeight * paddingFactor; + const paddingFactor = 1.4 + const paddedWidth = contentWidth * paddingFactor + const paddedHeight = contentHeight * paddingFactor // Account for occluded area on the right (e.g., chat panel) - const occludedRightPx = Math.max(0, options?.occludedRightPx ?? 0); - const availableWidth = Math.max(1, viewportWidth - occludedRightPx); + const occludedRightPx = Math.max(0, options?.occludedRightPx ?? 0) + const availableWidth = Math.max(1, viewportWidth - occludedRightPx) // Calculate the zoom needed to fit the content within available width - const zoomX = availableWidth / paddedWidth; - const zoomY = viewportHeight / paddedHeight; - const newZoom = Math.min(Math.max(0.05, Math.min(zoomX, zoomY)), 3); + const zoomX = availableWidth / paddedWidth + const zoomY = viewportHeight / paddedHeight + const newZoom = Math.min(Math.max(0.05, Math.min(zoomX, zoomY)), 3) // Calculate pan to center the content within available area - const availableCenterX = availableWidth / 2; - const newPanX = availableCenterX - contentCenterX * newZoom; - const newPanY = viewportHeight / 2 - contentCenterY * newZoom; + const availableCenterX = availableWidth / 2 + const newPanX = availableCenterX - contentCenterX * newZoom + const newPanY = viewportHeight / 2 - contentCenterY * newZoom // Apply the new view (optional animation) if (options?.animate) { - const steps = 8; - const durationMs = 160; // snappy - const intervalMs = Math.max(1, Math.floor(durationMs / steps)); - const startZoom = zoom; - const startPanX = panX; - const startPanY = panY; - let i = 0; - const ease = (t: number) => 1 - (1 - t) ** 2; // ease-out quad + const steps = 8 + const durationMs = 160 // snappy + const intervalMs = Math.max(1, Math.floor(durationMs / steps)) + const startZoom = zoom + const startPanX = panX + const startPanY = panY + let i = 0 + const ease = (t: number) => 1 - (1 - t) ** 2 // ease-out quad const timer = setInterval(() => { - i++; - const t = ease(i / steps); - setZoom(startZoom + (newZoom - startZoom) * t); - setPanX(startPanX + (newPanX - startPanX) * t); - setPanY(startPanY + (newPanY - startPanY) * t); - if (i >= steps) clearInterval(timer); - }, intervalMs); + i++ + const t = ease(i / steps) + setZoom(startZoom + (newZoom - startZoom) * t) + setPanX(startPanX + (newPanX - startPanX) * t) + setPanY(startPanY + (newPanY - startPanY) * t) + if (i >= steps) clearInterval(timer) + }, intervalMs) } else { - setZoom(newZoom); - setPanX(newPanX); - setPanY(newPanY); + setZoom(newZoom) + setPanX(newPanX) + setPanY(newPanY) } }, [zoom, panX, panY], - ); + ) // Touch gesture handlers for mobile pinch-to-zoom const handleTouchStart = useCallback((e: React.TouchEvent) => { @@ -350,117 +350,117 @@ export function useGraphInteractions( id: touch.identifier, x: touch.clientX, y: touch.clientY, - })); + })) if (touches.length >= 2) { // Start gesture with two or more fingers - const touch1 = touches[0]!; - const touch2 = touches[1]!; + const touch1 = touches[0]! + const touch2 = touches[1]! const distance = Math.sqrt( (touch2.x - touch1.x) ** 2 + (touch2.y - touch1.y) ** 2, - ); + ) const center = { x: (touch1.x + touch2.x) / 2, y: (touch1.y + touch2.y) / 2, - }; + } setTouchState({ touches, lastDistance: distance, lastCenter: center, isGesturing: true, - }); + }) } else { - setTouchState((prev) => ({ ...prev, touches, isGesturing: false })); + setTouchState((prev) => ({ ...prev, touches, isGesturing: false })) } - }, []); + }, []) const handleTouchMove = useCallback( (e: React.TouchEvent) => { - e.preventDefault(); + e.preventDefault() const touches = Array.from(e.touches).map((touch) => ({ id: touch.identifier, x: touch.clientX, y: touch.clientY, - })); + })) if (touches.length >= 2 && touchState.isGesturing) { - const touch1 = touches[0]!; - const touch2 = touches[1]!; + const touch1 = touches[0]! + const touch2 = touches[1]! const distance = Math.sqrt( (touch2.x - touch1.x) ** 2 + (touch2.y - touch1.y) ** 2, - ); + ) const center = { x: (touch1.x + touch2.x) / 2, y: (touch1.y + touch2.y) / 2, - }; + } // Calculate zoom change based on pinch distance change - const distanceChange = distance / touchState.lastDistance; - const newZoom = Math.max(0.05, Math.min(3, zoom * distanceChange)); + const distanceChange = distance / touchState.lastDistance + const newZoom = Math.max(0.05, Math.min(3, zoom * distanceChange)) // Get canvas bounds for center calculation - const canvas = e.currentTarget as HTMLElement; - const rect = canvas.getBoundingClientRect(); - const centerX = center.x - rect.left; - const centerY = center.y - rect.top; + const canvas = e.currentTarget as HTMLElement + const rect = canvas.getBoundingClientRect() + const centerX = center.x - rect.left + const centerY = center.y - rect.top // Calculate the world position of the pinch center - const worldX = (centerX - panX) / zoom; - const worldY = (centerY - panY) / zoom; + const worldX = (centerX - panX) / zoom + const worldY = (centerY - panY) / zoom // Calculate new pan to keep the pinch center stationary - const newPanX = centerX - worldX * newZoom; - const newPanY = centerY - worldY * newZoom; + const newPanX = centerX - worldX * newZoom + const newPanY = centerY - worldY * newZoom // Calculate pan change based on center movement - const centerDx = center.x - touchState.lastCenter.x; - const centerDy = center.y - touchState.lastCenter.y; + const centerDx = center.x - touchState.lastCenter.x + const centerDy = center.y - touchState.lastCenter.y - setZoom(newZoom); - setPanX(newPanX + centerDx); - setPanY(newPanY + centerDy); + setZoom(newZoom) + setPanX(newPanX + centerDx) + setPanY(newPanY + centerDy) setTouchState({ touches, lastDistance: distance, lastCenter: center, isGesturing: true, - }); + }) } else if (touches.length === 1 && !touchState.isGesturing && isPanning) { // Single finger pan (only if not in gesture mode) - const touch = touches[0]!; - const newPanX = touch.x - panStart.x; - const newPanY = touch.y - panStart.y; - setPanX(newPanX); - setPanY(newPanY); + const touch = touches[0]! + const newPanX = touch.x - panStart.x + const newPanY = touch.y - panStart.y + setPanX(newPanX) + setPanY(newPanY) } }, [touchState, zoom, panX, panY, isPanning, panStart], - ); + ) const handleTouchEnd = useCallback((e: React.TouchEvent) => { const touches = Array.from(e.touches).map((touch) => ({ id: touch.identifier, x: touch.clientX, y: touch.clientY, - })); + })) if (touches.length < 2) { - setTouchState((prev) => ({ ...prev, touches, isGesturing: false })); + setTouchState((prev) => ({ ...prev, touches, isGesturing: false })) } else { - setTouchState((prev) => ({ ...prev, touches })); + setTouchState((prev) => ({ ...prev, touches })) } if (touches.length === 0) { - setIsPanning(false); + setIsPanning(false) } - }, []); + }, []) // Center viewport on a specific world position (with animation) const centerViewportOn = useCallback( @@ -471,63 +471,63 @@ export function useGraphInteractions( viewportHeight: number, animate = true, ) => { - const newPanX = viewportWidth / 2 - worldX * zoom; - const newPanY = viewportHeight / 2 - worldY * zoom; + const newPanX = viewportWidth / 2 - worldX * zoom + const newPanY = viewportHeight / 2 - worldY * zoom if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, zoom, 400); + animateToViewState(newPanX, newPanY, zoom, 400) } else { - setPanX(newPanX); - setPanY(newPanY); + setPanX(newPanX) + setPanY(newPanY) } }, [zoom, isAnimating, animateToViewState], - ); + ) // Node interaction handlers const handleNodeHover = useCallback((nodeId: string | null) => { - setHoveredNode(nodeId); - }, []); + setHoveredNode(nodeId) + }, []) const handleNodeClick = useCallback( (nodeId: string) => { - setSelectedNode(selectedNode === nodeId ? null : nodeId); + setSelectedNode(selectedNode === nodeId ? null : nodeId) }, [selectedNode], - ); + ) const handleDoubleClick = useCallback( (e: React.MouseEvent) => { // Calculate new zoom (zoom in by 1.5x) - const zoomFactor = 1.5; - const newZoom = Math.min(3, zoom * zoomFactor); + const zoomFactor = 1.5 + const newZoom = Math.min(3, zoom * zoomFactor) // Get mouse position relative to the container - let mouseX = e.clientX; - let mouseY = e.clientY; + let mouseX = e.clientX + let mouseY = e.clientY // Try to get the container bounds to make coordinates relative to the graph container - const target = e.currentTarget; + const target = e.currentTarget if (target && "getBoundingClientRect" in target) { - const rect = target.getBoundingClientRect(); - mouseX = e.clientX - rect.left; - mouseY = e.clientY - rect.top; + const rect = target.getBoundingClientRect() + mouseX = e.clientX - rect.left + mouseY = e.clientY - rect.top } // Calculate the world position of the clicked point - const worldX = (mouseX - panX) / zoom; - const worldY = (mouseY - panY) / zoom; + const worldX = (mouseX - panX) / zoom + const worldY = (mouseY - panY) / zoom // Calculate new pan to keep the clicked point in the same screen position - const newPanX = mouseX - worldX * newZoom; - const newPanY = mouseY - worldY * newZoom; + const newPanX = mouseX - worldX * newZoom + const newPanY = mouseY - worldY * newZoom - setZoom(newZoom); - setPanX(newPanX); - setPanY(newPanY); + setZoom(newZoom) + setPanX(newPanX) + setPanY(newPanY) }, [zoom, panX, panY], - ); + ) return { // State @@ -560,5 +560,5 @@ export function useGraphInteractions( autoFitToViewport, centerViewportOn, setSelectedNode, - }; + } } |