Fix module drag-and-drop: custom collision detection to hit slots over racks
Root cause: SortableContext registers each rack column as a droppable.
Each column is ~1800px tall (42U x 44px). The default closestCenter
algorithm compared center-to-center distances, so the rack column's
center consistently beat the 44px RackSlot's center — meaning over.data
resolved to { dragType: 'rack' } and handleDragEnd's check for
dropType === 'slot' never matched. Drops silently did nothing.
Fix: replace closestCenter with a two-phase collision detection:
1. pointerWithin — returns droppables whose bounding rect contains
the actual pointer position. Slots are exactly hit-tested.
2. closestCenter fallback — used when the pointer is not within any
registered droppable (e.g. dragging a rack header between columns
for sortable reorder where the pointer may be in the gap).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,13 @@ import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
PointerSensor,
|
||||
pointerWithin,
|
||||
closestCenter,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragStartEvent,
|
||||
type DragEndEvent,
|
||||
type CollisionDetection,
|
||||
} from '@dnd-kit/core';
|
||||
import { SortableContext, horizontalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
|
||||
import { toast } from 'sonner';
|
||||
@@ -50,6 +53,23 @@ function ModuleDragOverlay({ label }: { label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collision detection strategy:
|
||||
* - pointerWithin first: finds droppables whose rect contains the pointer.
|
||||
* This correctly hits the 44px-tall RackSlots since the pointer is inside them.
|
||||
* - closestCenter fallback: used when the pointer is not within any droppable,
|
||||
* which is when dragging a rack header between columns (sortable reorder).
|
||||
*
|
||||
* Without this, the default closestCenter alone would favour the large rack-column
|
||||
* sortable elements (~1800px tall) over the tiny slot droppables, so over.data
|
||||
* would be { dragType: 'rack' } instead of { dropType: 'slot' }.
|
||||
*/
|
||||
const slotFirstCollision: CollisionDetection = (args) => {
|
||||
const within = pointerWithin(args);
|
||||
if (within.length > 0) return within;
|
||||
return closestCenter(args);
|
||||
};
|
||||
|
||||
export function RackPlanner() {
|
||||
const { racks, loading, fetchRacks, moveModule, updateRack } = useRackStore();
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
@@ -143,7 +163,7 @@ export function RackPlanner() {
|
||||
const rackIds = racks.map((r) => r.id);
|
||||
|
||||
return (
|
||||
<DndContext sensors={sensors} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<DndContext sensors={sensors} collisionDetection={slotFirstCollision} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<div className="flex flex-col h-screen bg-[#0f1117]">
|
||||
<RackToolbar rackCanvasRef={canvasRef} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user