From 95b9b0b7b9c340ebb3062c7fc78887dcfa01009c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 05:37:14 +0000 Subject: [PATCH] Fix fit-to-screen drag: use newItem.x/y grid coords instead of mouse pixels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace getBoundingClientRect+clientX/Y with newItem.x/y from react-grid-layout. With noCompactor, newItem reports the free grid position where the element was dropped — reliable across all rows without pixel math or scroll issues. Also remove handleFitDrag/fitDragRef (no longer needed) and generate a full snapBack layout so displaced items snap back correctly on no-op drops. https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy --- web/src/views/live/DraggableGridLayout.tsx | 53 ++++++---------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index 1334d788a..fb341c569 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -468,41 +468,24 @@ export default function DraggableGridLayout({ }, [fitToScreen, fitGridParams, cameras, includeBirdseye, birdseyeConfig]); const [fitLayoutOverride, setFitLayoutOverride] = useState(); - const fitDragRef = useRef(null); useEffect(() => { setFitLayoutOverride(undefined); }, [fitGridParams, cameras, includeBirdseye]); - const handleFitDrag = useCallback( - ( - _layout: Layout, - _oldItem: LayoutItem | null, - newItem: LayoutItem | null, - ) => { - if (newItem) { - fitDragRef.current = newItem.i; - } - }, - [], - ); - const handleFitDragStop = useCallback( ( _layout: Layout, _oldItem: LayoutItem | null, newItem: LayoutItem | null, _placeholder: LayoutItem | null, - event: Event, + _event: Event, ) => { - if (!fitToScreen || !fitGridParams) return; + if (!fitToScreen || !fitGridParams || !newItem) return; const w = fitGridParams.gridUnitsPerCam; const colsPerRow = fitGridParams.colsPerRow; - const draggedId = fitDragRef.current ?? newItem?.i; - fitDragRef.current = null; - - if (!draggedId) return; + const draggedId = newItem.i; const currentOrder = fitLayoutOverride ?? fitLayout ?? []; const orderedNames = [...currentOrder] @@ -512,24 +495,11 @@ export default function DraggableGridLayout({ }) .map((item) => item.i); - const gridEl = containerRef.current?.querySelector( - ".grid-layout", - ) as HTMLElement | null; - if (!gridEl) return; - - const mouseEvent = event as MouseEvent; - const rect = gridEl.getBoundingClientRect(); - const mouseRelX = mouseEvent.clientX - rect.left; - const mouseRelY = mouseEvent.clientY - rect.top; - - const cellWidthPx = rect.width / colsPerRow; - const cellHeightPx = cellHeight * w; - const targetCol = Math.max( 0, - Math.min(Math.floor(mouseRelX / cellWidthPx), colsPerRow - 1), + Math.min(Math.round(newItem.x / w), colsPerRow - 1), ); - const targetRow = Math.max(0, Math.floor(mouseRelY / cellHeightPx)); + const targetRow = Math.max(0, Math.round(newItem.y / w)); const totalRows = Math.ceil(orderedNames.length / colsPerRow); const clampedRow = Math.min(targetRow, totalRows - 1); const targetIndex = Math.min( @@ -538,8 +508,16 @@ export default function DraggableGridLayout({ ); const sourceIndex = orderedNames.indexOf(draggedId); + const snapBack = orderedNames.map((name, index) => ({ + i: name, + x: (index % colsPerRow) * w, + y: Math.floor(index / colsPerRow) * w, + w, + h: w, + })); + if (sourceIndex === -1 || sourceIndex === targetIndex) { - setFitLayoutOverride((prev) => (prev ? [...prev] : prev)); + setFitLayoutOverride(snapBack); return; } @@ -559,7 +537,7 @@ export default function DraggableGridLayout({ setFitLayoutOverride(normalized); }, - [fitToScreen, fitGridParams, fitLayoutOverride, fitLayout, cellHeight, containerRef], + [fitToScreen, fitGridParams, fitLayoutOverride, fitLayout], ); const activeGridLayout = useMemo(() => { @@ -941,7 +919,6 @@ export default function DraggableGridLayout({ dragConfig={{ enabled: isEditMode, }} - onDrag={fitToScreen ? handleFitDrag : undefined} onDragStop={fitToScreen ? handleFitDragStop : handleLayoutChange} onResize={handleResize} onResizeStart={() => setShowCircles(false)}