Fix fit-to-screen drag: use newItem.x/y grid coords instead of mouse pixels

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
This commit is contained in:
Claude 2026-03-22 05:37:14 +00:00
parent 8fc7f6bdb7
commit 95b9b0b7b9
No known key found for this signature in database

View File

@ -468,41 +468,24 @@ export default function DraggableGridLayout({
}, [fitToScreen, fitGridParams, cameras, includeBirdseye, birdseyeConfig]); }, [fitToScreen, fitGridParams, cameras, includeBirdseye, birdseyeConfig]);
const [fitLayoutOverride, setFitLayoutOverride] = useState<Layout | undefined>(); const [fitLayoutOverride, setFitLayoutOverride] = useState<Layout | undefined>();
const fitDragRef = useRef<string | null>(null);
useEffect(() => { useEffect(() => {
setFitLayoutOverride(undefined); setFitLayoutOverride(undefined);
}, [fitGridParams, cameras, includeBirdseye]); }, [fitGridParams, cameras, includeBirdseye]);
const handleFitDrag = useCallback(
(
_layout: Layout,
_oldItem: LayoutItem | null,
newItem: LayoutItem | null,
) => {
if (newItem) {
fitDragRef.current = newItem.i;
}
},
[],
);
const handleFitDragStop = useCallback( const handleFitDragStop = useCallback(
( (
_layout: Layout, _layout: Layout,
_oldItem: LayoutItem | null, _oldItem: LayoutItem | null,
newItem: LayoutItem | null, newItem: LayoutItem | null,
_placeholder: LayoutItem | null, _placeholder: LayoutItem | null,
event: Event, _event: Event,
) => { ) => {
if (!fitToScreen || !fitGridParams) return; if (!fitToScreen || !fitGridParams || !newItem) return;
const w = fitGridParams.gridUnitsPerCam; const w = fitGridParams.gridUnitsPerCam;
const colsPerRow = fitGridParams.colsPerRow; const colsPerRow = fitGridParams.colsPerRow;
const draggedId = fitDragRef.current ?? newItem?.i; const draggedId = newItem.i;
fitDragRef.current = null;
if (!draggedId) return;
const currentOrder = fitLayoutOverride ?? fitLayout ?? []; const currentOrder = fitLayoutOverride ?? fitLayout ?? [];
const orderedNames = [...currentOrder] const orderedNames = [...currentOrder]
@ -512,24 +495,11 @@ export default function DraggableGridLayout({
}) })
.map((item) => item.i); .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( const targetCol = Math.max(
0, 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 totalRows = Math.ceil(orderedNames.length / colsPerRow);
const clampedRow = Math.min(targetRow, totalRows - 1); const clampedRow = Math.min(targetRow, totalRows - 1);
const targetIndex = Math.min( const targetIndex = Math.min(
@ -538,8 +508,16 @@ export default function DraggableGridLayout({
); );
const sourceIndex = orderedNames.indexOf(draggedId); 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) { if (sourceIndex === -1 || sourceIndex === targetIndex) {
setFitLayoutOverride((prev) => (prev ? [...prev] : prev)); setFitLayoutOverride(snapBack);
return; return;
} }
@ -559,7 +537,7 @@ export default function DraggableGridLayout({
setFitLayoutOverride(normalized); setFitLayoutOverride(normalized);
}, },
[fitToScreen, fitGridParams, fitLayoutOverride, fitLayout, cellHeight, containerRef], [fitToScreen, fitGridParams, fitLayoutOverride, fitLayout],
); );
const activeGridLayout = useMemo(() => { const activeGridLayout = useMemo(() => {
@ -941,7 +919,6 @@ export default function DraggableGridLayout({
dragConfig={{ dragConfig={{
enabled: isEditMode, enabled: isEditMode,
}} }}
onDrag={fitToScreen ? handleFitDrag : undefined}
onDragStop={fitToScreen ? handleFitDragStop : handleLayoutChange} onDragStop={fitToScreen ? handleFitDragStop : handleLayoutChange}
onResize={handleResize} onResize={handleResize}
onResizeStart={() => setShowCircles(false)} onResizeStart={() => setShowCircles(false)}