Skip to content

Commit 48bb01c

Browse files
themixednutsclaude
andcommitted
Use SvelteMap for granular drag offset reactivity
- Replace $state.raw with SvelteMap for dragOffsets to enable granular reactivity where only the dragged node triggers derived updates - Fix pan/drag continuing when cursor leaves viewport via global handlers - Add perf logging for dragOffset.set operations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 26065b3 commit 48bb01c

1 file changed

Lines changed: 23 additions & 9 deletions

File tree

codeview-ui/src/lib/components/RelationshipGraph.svelte

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
computeSceneLabels,
1717
} from '$lib/renderers/graph';
1818
import { perf } from '$lib/perf';
19+
import { SvelteMap } from 'svelte/reactivity';
1920
2021
let {
2122
graph,
@@ -62,7 +63,9 @@
6263
let containerEl = $state<HTMLDivElement | null>(null);
6364
let svgEl = $state<SVGSVGElement | null>(null);
6465
let dragNodeId = $state<string | null>(null);
65-
let dragOffsets = $state.raw<Record<string, DragOffset>>({});
66+
// SvelteMap provides granular reactivity: only the dragged node's offset triggers updates,
67+
// avoiding re-evaluation of all derived computations when a single entry changes.
68+
let dragOffsets = new SvelteMap<string, DragOffset>();
6669
let dragStart = { x: 0, y: 0 };
6770
let dragStartScreen = { x: 0, y: 0 };
6871
let dragNodeStart = { x: 0, y: 0 };
@@ -130,8 +133,9 @@
130133
}
131134
132135
function handleMouseLeave() {
133-
isPanning = false;
134-
if (!dragNodeId) {
136+
// Don't stop panning/dragging on leave — let global handlers track outside the viewport
137+
// Only stop if not actively interacting
138+
if (!dragNodeId && !isPanning) {
135139
isInteracting = false;
136140
}
137141
tooltipNode = null;
@@ -141,7 +145,7 @@
141145
zoom = 1;
142146
panX = 0;
143147
panY = 0;
144-
dragOffsets = {};
148+
dragOffsets.clear();
145149
}
146150
147151
function zoomIn() {
@@ -191,7 +195,7 @@
191195
dragStart = getWorldPoint(e);
192196
dragStartScreen = { x: e.clientX, y: e.clientY };
193197
dragNodeStart = { x: visNode.x, y: visNode.y };
194-
const offset = dragOffsets[visNode.node.id] ?? { x: 0, y: 0 };
198+
const offset = dragOffsets.get(visNode.node.id) ?? { x: 0, y: 0 };
195199
dragBasePos = { x: visNode.x - offset.x, y: visNode.y - offset.y };
196200
}
197201
@@ -208,9 +212,9 @@
208212
const dy = world.y - dragStart.y;
209213
const nextX = dragNodeStart.x + dx;
210214
const nextY = dragNodeStart.y + dy;
211-
// Mutate-and-reassign: avoid spreading 100s of entries every frame
212-
dragOffsets[dragNodeId!] = { x: nextX - dragBasePos.x, y: nextY - dragBasePos.y };
213-
dragOffsets = dragOffsets;
215+
perf.frame('interact', 'dragOffset.set', () => {
216+
dragOffsets.set(dragNodeId!, { x: nextX - dragBasePos.x, y: nextY - dragBasePos.y });
217+
});
214218
});
215219
}
216220
@@ -223,16 +227,26 @@
223227
isInteracting = false;
224228
}
225229
230+
// Global handlers allow drag/pan to continue when the cursor moves outside the SVG container.
226231
function handleGlobalMouseMove(e: MouseEvent) {
227232
if (dragNodeId) {
228233
updateNodeDrag(e);
234+
} else if (isPanning) {
235+
const cur = screenToSvg(e.clientX, e.clientY);
236+
const start = screenToSvg(panStartX, panStartY);
237+
panX = panStartPanX + (cur.x - start.x);
238+
panY = panStartPanY + (cur.y - start.y);
229239
}
230240
}
231241
232242
function handleGlobalMouseUp() {
233243
if (dragNodeId) {
234244
endNodeDrag();
235245
}
246+
if (isPanning) {
247+
isPanning = false;
248+
isInteracting = false;
249+
}
236250
}
237251
238252
function showTooltip(visNode: VisNode, e: MouseEvent) {
@@ -302,7 +316,7 @@
302316
let positionedNodes = $derived.by(() => {
303317
return perf.frame('derived', 'positionedNodes', () =>
304318
baseScene.nodes.map((node) => {
305-
const offset = dragOffsets[node.node.id];
319+
const offset = dragOffsets.get(node.node.id);
306320
if (!offset) return node;
307321
return { ...node, x: node.x + offset.x, y: node.y + offset.y };
308322
})

0 commit comments

Comments
 (0)