From bd0c918e161d2e5f71bfe63ae64a340233eef372 Mon Sep 17 00:00:00 2001 From: Chris Villa Date: Sun, 10 Nov 2024 17:17:22 +0000 Subject: [PATCH] fix: prevent droppable from colliding with own child --- .changeset/dont-collide-with-child.md | 5 +++ .../abstract/src/core/collision/observer.ts | 17 ++++++++-- .../src/core/entities/droppable/droppable.ts | 9 +++++- .../src/core/entities/droppable/droppable.ts | 31 +++++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 .changeset/dont-collide-with-child.md diff --git a/.changeset/dont-collide-with-child.md b/.changeset/dont-collide-with-child.md new file mode 100644 index 00000000..56899cf0 --- /dev/null +++ b/.changeset/dont-collide-with-child.md @@ -0,0 +1,5 @@ +--- +'@dnd-kit/dom': patch +--- + +Track the path of the item to prevent a droppable from colliding with its own child. diff --git a/packages/abstract/src/core/collision/observer.ts b/packages/abstract/src/core/collision/observer.ts index fd3c4a39..c410296d 100644 --- a/packages/abstract/src/core/collision/observer.ts +++ b/packages/abstract/src/core/collision/observer.ts @@ -73,7 +73,7 @@ export class CollisionObserver< return DEFAULT_VALUE; } - const collisions: Collision[] = []; + const collisionMap: Map = new Map(); for (const entry of entries ?? registry.droppables) { if (entry.disabled) { @@ -90,7 +90,6 @@ export class CollisionObserver< continue; } - entry.shape; const collision = untracked(() => detectCollision({ droppable: entry, @@ -103,10 +102,22 @@ export class CollisionObserver< collision.priority = entry.collisionPriority; } - collisions.push(collision); + collisionMap.set(entry, collision); } } + // Filter out collisions of items that contain other items + const collisions = Array.from(collisionMap.entries()) + .filter(([droppable]) => { + if (source && droppable.path.indexOf(source.id) !== -1) { + // Dragged item is parent of collision target. Filter out collision + return false; + } + + return true; + }) + .map(([_, collision]) => collision); + collisions.sort(sortCollisions); return collisions; diff --git a/packages/abstract/src/core/entities/droppable/droppable.ts b/packages/abstract/src/core/entities/droppable/droppable.ts index c9cad0dd..a5883a09 100644 --- a/packages/abstract/src/core/entities/droppable/droppable.ts +++ b/packages/abstract/src/core/entities/droppable/droppable.ts @@ -2,7 +2,12 @@ import {derived, effects, reactive, type Effect} from '@dnd-kit/state'; import type {Shape} from '@dnd-kit/geometry'; import {Entity} from '../entity/index.ts'; -import type {EntityInput, Data, Type} from '../entity/index.ts'; +import type { + EntityInput, + Data, + Type, + UniqueIdentifier, +} from '../entity/index.ts'; import { CollisionPriority, type CollisionDetector, @@ -90,4 +95,6 @@ export class Droppable< public get isDropTarget() { return this.manager?.dragOperation.target?.id === this.id; } + + public path: UniqueIdentifier[] = []; } diff --git a/packages/dom/src/core/entities/droppable/droppable.ts b/packages/dom/src/core/entities/droppable/droppable.ts index 2d68fe16..fd9bec53 100644 --- a/packages/dom/src/core/entities/droppable/droppable.ts +++ b/packages/dom/src/core/entities/droppable/droppable.ts @@ -2,6 +2,7 @@ import {Droppable as AbstractDroppable} from '@dnd-kit/abstract'; import type { Data, DroppableInput as AbstractDroppableInput, + UniqueIdentifier, } from '@dnd-kit/abstract'; import {defaultCollisionDetection} from '@dnd-kit/collision'; import type {CollisionDetector} from '@dnd-kit/collision'; @@ -19,6 +20,32 @@ export interface Input element?: Element; } +function getPathArray( + droppables: DragDropManager['registry']['droppables'], + target: Element +): UniqueIdentifier[] { + // Create a map from element to id for easy lookup + const elementMap = new Map(); + Array.from(droppables.value).forEach((item) => { + if (item?.element) { + elementMap.set(item.element, item.id); + } + }); + + const path: UniqueIdentifier[] = []; + let currentElement = target.parentElement; + + while (currentElement) { + const parentId = elementMap.get(currentElement); + if (parentId) { + path.unshift(parentId); + } + currentElement = currentElement.parentElement; + } + + return path; +} + export class Droppable extends AbstractDroppable< T, DragDropManager @@ -69,6 +96,10 @@ export class Droppable extends AbstractDroppable< !this.disabled && this.accepts(source); + this.path = element + ? getPathArray(manager.registry.droppables, element) + : []; + if (observePosition) { const positionObserver = new PositionObserver( element,