Fix module drag: TypeError on droppableContainers.filter() crashing collision detection

droppableContainers in @dnd-kit/core collision detection args is a custom
NodeMap class, not a plain Array. It implements [Symbol.iterator] (so
for...of works internally in closestCenter/pointerWithin) but does NOT
have Array.prototype methods like .filter().

Calling args.droppableContainers.filter(...) threw:
  TypeError: args.droppableContainers.filter is not a function

dnd-kit silently catches errors in the collision detection callback and
treats them as no collision (over = null). Every module drag ended with
over = null, hitting the early return in handleDragEnd, causing the module
to snap back to its original slot every time.

Fix: Array.from(args.droppableContainers) converts the NodeMap iterable
to a plain array before filtering for dropType === 'slot' containers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 08:23:51 -05:00
parent a11634070f
commit 25e78b4754

View File

@@ -69,13 +69,17 @@ function ModuleDragOverlay({ label }: { label: string }) {
* (which needs the sortable rack targets) still works.
*/
const slotFirstCollision: CollisionDetection = (args) => {
// Restrict to slot droppables only
const slotContainers = args.droppableContainers.filter(
// droppableContainers is a custom NodeMap (not a plain Array) — it only
// implements [Symbol.iterator], so .filter() doesn't exist on it.
// Convert to Array first before filtering.
const allContainers = Array.from(args.droppableContainers);
const slotContainers = allContainers.filter(
(c) => c.data.current?.dropType === 'slot'
);
if (slotContainers.length > 0) {
const slotHits = pointerWithin({ ...args, droppableContainers: slotContainers });
const slotHits = pointerWithin({ ...args, droppableContainers: slotContainers as typeof args.droppableContainers });
if (slotHits.length > 0) return slotHits;
}