diff --git a/client/src/components/rack/RackPlanner.tsx b/client/src/components/rack/RackPlanner.tsx index 56e1b3c..0232b45 100644 --- a/client/src/components/rack/RackPlanner.tsx +++ b/client/src/components/rack/RackPlanner.tsx @@ -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(null); @@ -143,7 +163,7 @@ export function RackPlanner() { const rackIds = racks.map((r) => r.id); return ( - +