From 29e2003a84cfed48fc10775e28887674fd84c2d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 23:56:49 +0000 Subject: [PATCH] Fix fit-to-screen drag types to match EventCallback signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All LayoutItem args are nullable (LayoutItem | null) and event is Event not MouseEvent — matching the actual react-grid-layout EventCallback type. fitDragRef simplified to string | null. Cast event to MouseEvent inside the handler to access clientX/Y for pixel-accurate slot detection. https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy --- web/src/views/live/DraggableGridLayout.tsx | 52 +++++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index 60cde0aca..fa51fae6c 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -467,27 +467,41 @@ export default function DraggableGridLayout({ }, [fitToScreen, fitGridParams, cameras, includeBirdseye, birdseyeConfig]); const [fitLayoutOverride, setFitLayoutOverride] = useState(); - const draggedItemRef = useRef(null); + const fitDragRef = useRef(null); useEffect(() => { setFitLayoutOverride(undefined); }, [fitGridParams, cameras, includeBirdseye]); const handleFitDrag = useCallback( - (_layout: Layout, _oldItem: LayoutItem, layoutItem: LayoutItem) => { - draggedItemRef.current = layoutItem.i; + ( + _layout: Layout, + _oldItem: LayoutItem | null, + newItem: LayoutItem | null, + ) => { + if (newItem) { + fitDragRef.current = newItem.i; + } }, [], ); const handleFitDragStop = useCallback( - (newLayout: Layout, _oldItem: LayoutItem, layoutItem: LayoutItem) => { + ( + _layout: Layout, + _oldItem: LayoutItem | null, + newItem: LayoutItem | null, + _placeholder: LayoutItem | null, + event: Event, + ) => { if (!fitToScreen || !fitGridParams) return; const w = fitGridParams.gridUnitsPerCam; const colsPerRow = fitGridParams.colsPerRow; - const draggedId = draggedItemRef.current ?? layoutItem.i; - draggedItemRef.current = null; + const draggedId = fitDragRef.current ?? newItem?.i; + fitDragRef.current = null; + + if (!draggedId) return; const currentOrder = fitLayoutOverride ?? fitLayout ?? []; const orderedNames = [...currentOrder] @@ -497,10 +511,24 @@ export default function DraggableGridLayout({ }) .map((item) => item.i); - const dropCenterX = layoutItem.x + w / 2; - const dropCenterY = layoutItem.y + w / 2; - const targetCol = Math.min(Math.floor(dropCenterX / w), colsPerRow - 1); - const targetRow = Math.floor(dropCenterY / w); + 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), + ); + const targetRow = Math.max(0, Math.floor(mouseRelY / cellHeightPx)); const totalRows = Math.ceil(orderedNames.length / colsPerRow); const clampedRow = Math.min(targetRow, totalRows - 1); const targetIndex = Math.min( @@ -510,7 +538,7 @@ export default function DraggableGridLayout({ const sourceIndex = orderedNames.indexOf(draggedId); if (sourceIndex === -1 || sourceIndex === targetIndex) { - setFitLayoutOverride((prev) => prev); + setFitLayoutOverride((prev) => (prev ? [...prev] : prev)); return; } @@ -530,7 +558,7 @@ export default function DraggableGridLayout({ setFitLayoutOverride(normalized); }, - [fitToScreen, fitGridParams, fitLayoutOverride, fitLayout], + [fitToScreen, fitGridParams, fitLayoutOverride, fitLayout, cellHeight, containerRef], ); const activeGridLayout = useMemo(() => {