Skip to content

Commit d2619d8

Browse files
committed
fix: prevent droppable from colliding with own child
1 parent dc9c4cf commit d2619d8

File tree

4 files changed

+59
-4
lines changed

4 files changed

+59
-4
lines changed

.changeset/dont-collide-with-child.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@dnd-kit/dom': patch
3+
---
4+
5+
Track the path of the item to prevent a droppable from colliding with its own child.

packages/abstract/src/core/collision/observer.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class CollisionObserver<
7373
return DEFAULT_VALUE;
7474
}
7575

76-
const collisions: Collision[] = [];
76+
const collisionMap: Map<Droppable, Collision> = new Map();
7777

7878
for (const entry of entries ?? registry.droppables) {
7979
if (entry.disabled) {
@@ -90,7 +90,6 @@ export class CollisionObserver<
9090
continue;
9191
}
9292

93-
entry.shape;
9493
const collision = untracked(() =>
9594
detectCollision({
9695
droppable: entry,
@@ -103,10 +102,23 @@ export class CollisionObserver<
103102
collision.priority = entry.collisionPriority;
104103
}
105104

106-
collisions.push(collision);
105+
collisionMap.set(entry, collision);
107106
}
108107
}
109108

109+
// Filter out collisions of items that contain other items
110+
const collisions = Array.from(collisionMap.entries())
111+
.filter(([droppable]) => {
112+
if (source && droppable.path.indexOf(source.id) !== -1) {
113+
// Dragged item is parent of collision target. Filter out collision
114+
console.log(source, 'contains', droppable);
115+
return false;
116+
}
117+
118+
return true;
119+
})
120+
.map(([_, collision]) => collision);
121+
110122
collisions.sort(sortCollisions);
111123

112124
return collisions;

packages/abstract/src/core/entities/droppable/droppable.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import {derived, effects, reactive, type Effect} from '@dnd-kit/state';
22
import type {Shape} from '@dnd-kit/geometry';
33

44
import {Entity} from '../entity/index.ts';
5-
import type {EntityInput, Data, Type} from '../entity/index.ts';
5+
import type {
6+
EntityInput,
7+
Data,
8+
Type,
9+
UniqueIdentifier,
10+
} from '../entity/index.ts';
611
import {
712
CollisionPriority,
813
type CollisionDetector,
@@ -90,4 +95,6 @@ export class Droppable<
9095
public get isDropTarget() {
9196
return this.manager?.dragOperation.target?.id === this.id;
9297
}
98+
99+
public path: UniqueIdentifier[] = [];
93100
}

packages/dom/src/core/entities/droppable/droppable.ts

+31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Droppable as AbstractDroppable} from '@dnd-kit/abstract';
22
import type {
33
Data,
44
DroppableInput as AbstractDroppableInput,
5+
UniqueIdentifier,
56
} from '@dnd-kit/abstract';
67
import {defaultCollisionDetection} from '@dnd-kit/collision';
78
import type {CollisionDetector} from '@dnd-kit/collision';
@@ -19,6 +20,32 @@ export interface Input<T extends Data = Data>
1920
element?: Element;
2021
}
2122

23+
function getPathArray(
24+
droppables: DragDropManager['registry']['droppables'],
25+
target: Element
26+
): UniqueIdentifier[] {
27+
// Create a map from element to id for easy lookup
28+
const elementMap = new Map<Element, UniqueIdentifier>();
29+
Array.from(droppables.value).forEach((item) => {
30+
if (item?.element) {
31+
elementMap.set(item.element, item.id);
32+
}
33+
});
34+
35+
const path: UniqueIdentifier[] = [];
36+
let currentElement = target.parentElement;
37+
38+
while (currentElement) {
39+
const parentId = elementMap.get(currentElement);
40+
if (parentId) {
41+
path.unshift(parentId);
42+
}
43+
currentElement = currentElement.parentElement;
44+
}
45+
46+
return path;
47+
}
48+
2249
export class Droppable<T extends Data = Data> extends AbstractDroppable<
2350
T,
2451
DragDropManager
@@ -69,6 +96,10 @@ export class Droppable<T extends Data = Data> extends AbstractDroppable<
6996
!this.disabled &&
7097
this.accepts(source);
7198

99+
this.path = element
100+
? getPathArray(manager.registry.droppables, element)
101+
: [];
102+
72103
if (observePosition) {
73104
const positionObserver = new PositionObserver(
74105
element,

0 commit comments

Comments
 (0)