[eric] canvas: marquee converts through the LIVE camera, stale mid-glide selection rects sat far from the mouse

This commit is contained in:
ciregenz
2026-08-05 20:07:48 -07:00
parent b19e5da78a
commit 1a1bfd0957
2 changed files with 9 additions and 7 deletions
@@ -70,7 +70,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
const canvas = useCanvasControls(zoomSensitivity, contentBounds, isActive, mouseWheelAction);
const selection = useDashboardSelection(
{ panX: canvas.panX, panY: canvas.panY, zoom: canvas.zoom, viewportRef: canvas.viewportRef },
{ getLiveState: canvas.actions.getLiveState, viewportRef: canvas.viewportRef },
cards,
viewCards,
browserCards,
@@ -18,9 +18,9 @@ export interface MarqueeRect {
}
interface ScreenToCanvas {
panX: number;
panY: number;
zoom: number;
// The LIVE camera getter, never committed React state: a marquee drawn during a pan glide or
// inertia was converted with the stale pre-gesture camera and landed far from the mouse.
getLiveState: () => { panX: number; panY: number; zoom: number };
viewportRef: RefObject<HTMLDivElement | null>;
}
@@ -59,12 +59,14 @@ export function useDashboardSelection(
const vp = canvas.viewportRef.current;
if (!vp) return { x: 0, y: 0 };
const rect = vp.getBoundingClientRect();
const cam = canvas.getLiveState();
return {
x: (screenX - rect.left - canvas.panX) / canvas.zoom,
y: (screenY - rect.top - canvas.panY) / canvas.zoom,
x: (screenX - rect.left - cam.panX) / cam.zoom,
y: (screenY - rect.top - cam.panY) / cam.zoom,
};
},
[canvas.panX, canvas.panY, canvas.zoom, canvas.viewportRef],
// The stable members, not the wrapper: the call site builds the wrapper object fresh per render.
[canvas.getLiveState, canvas.viewportRef],
);
const isSelected = useCallback((id: string) => selectedIds.has(id), [selectedIds]);