diff --git a/.changeset/batch-sortable.md b/.changeset/batch-sortable.md index 2adf760d..c7cd5097 100644 --- a/.changeset/batch-sortable.md +++ b/.changeset/batch-sortable.md @@ -1,5 +1,5 @@ --- -'@dnd-kit/sortable': patch +'@dnd-kit/dom': patch --- Batch write operations to `draggable` and `droppable`. Also ensured that droppable instance is registered before draggable instance. diff --git a/.changeset/distinct-sortable-element.md b/.changeset/distinct-sortable-element.md new file mode 100644 index 00000000..88f1e3d6 --- /dev/null +++ b/.changeset/distinct-sortable-element.md @@ -0,0 +1,5 @@ +--- +'@dnd-kit/dom': patch +--- + +Allow `Sortable` to have a distinct `element` from the underlying `source` and `target` elements. This can be useful if you want the collision detection to operate on a subset of the sortable element, but the entirety of the element to move when its index changes. diff --git a/.changeset/fix-idle-transitions.md b/.changeset/fix-idle-transitions.md index 37bb7215..6453b313 100644 --- a/.changeset/fix-idle-transitions.md +++ b/.changeset/fix-idle-transitions.md @@ -1,5 +1,5 @@ --- -'@dnd-kit/sortable': patch +'@dnd-kit/dom': patch --- Fix an issue where we would update the shape of sortable items while the drag operation status was idle. diff --git a/packages/dom/src/sortable/OptimisticSortingPlugin.ts b/packages/dom/src/sortable/OptimisticSortingPlugin.ts index 8aba7526..0fd2265b 100644 --- a/packages/dom/src/sortable/OptimisticSortingPlugin.ts +++ b/packages/dom/src/sortable/OptimisticSortingPlugin.ts @@ -80,9 +80,7 @@ export class OptimisticSortingPlugin extends Plugin { targetIndex ); - const sourceElement = - source.sortable.droppable.internal.element.peek() ?? - source.sortable.droppable.placeholder; + const sourceElement = source.sortable.element; const targetElement = target.element; if (!targetElement || !sourceElement) { @@ -131,7 +129,7 @@ export class OptimisticSortingPlugin extends Plugin { sortableInstances.values() ).sort((a, b) => a.index - b.index); - const sourceElement = source.sortable.droppable.element; + const sourceElement = source.sortable.element; const targetElement = orderedSortables[source.sortable.initialIndex]?.element; diff --git a/packages/dom/src/sortable/SortableKeyboardPlugin.ts b/packages/dom/src/sortable/SortableKeyboardPlugin.ts index feee4a91..1f501f70 100644 --- a/packages/dom/src/sortable/SortableKeyboardPlugin.ts +++ b/packages/dom/src/sortable/SortableKeyboardPlugin.ts @@ -2,6 +2,7 @@ import {effect} from '@dnd-kit/state'; import {Plugin} from '@dnd-kit/abstract'; import {closestCorners} from '@dnd-kit/collision'; import { + DOMRectangle, isKeyboardEvent, scheduler, scrollIntoViewIfNeeded, @@ -128,21 +129,18 @@ export class SortableKeyboardPlugin extends Plugin { actions.setDropTarget(id).then(() => { const {source} = dragOperation; - if (!source) { + if (!source || !isSortable(source)) { return; } - const droppable = registry.droppables.get(source.id); + const {element} = source.sortable; - if (!droppable?.element) { - return; - } + if (!element) return; - const {element} = droppable; scrollIntoViewIfNeeded(element); scheduler.schedule(() => { - const shape = droppable.refreshShape(); + const shape = new DOMRectangle(element); if (!shape) { return; diff --git a/packages/dom/src/sortable/index.ts b/packages/dom/src/sortable/index.ts index 40cfd78f..9eab1e90 100644 --- a/packages/dom/src/sortable/index.ts +++ b/packages/dom/src/sortable/index.ts @@ -1,2 +1,3 @@ export {Sortable, defaultSortableTransition} from './sortable.ts'; export type {SortableInput, SortableTransition} from './sortable.ts'; +export {isSortable} from './utilities.ts'; diff --git a/packages/dom/src/sortable/sortable.ts b/packages/dom/src/sortable/sortable.ts index 0d63a899..977f35a5 100644 --- a/packages/dom/src/sortable/sortable.ts +++ b/packages/dom/src/sortable/sortable.ts @@ -22,6 +22,7 @@ import { animateTransform, computeTranslate, scheduler, + ProxiedElements, } from '@dnd-kit/dom/utilities'; import {SortableKeyboardPlugin} from './SortableKeyboardPlugin.ts'; @@ -114,6 +115,8 @@ export class Sortable { }: SortableInput, manager: DragDropManager | undefined ) { + let previousGroup = group; + this.droppable = new SortableDroppable(input, manager, this); this.draggable = new SortableDraggable( { @@ -125,7 +128,13 @@ export class Sortable { this.previousIndex = this.index; }), () => { - const {index, previousIndex, manager: _} = this; + const {index, group, previousIndex, manager: _} = this; + + if (group !== previousGroup) { + previousGroup = group; + this.previousIndex = index; + return; + } // Re-run this effect whenever the index changes if (index === previousIndex) { @@ -234,13 +243,32 @@ export class Sortable { }); } + #element: Element | undefined; + public set element(element: Element | undefined) { - this.draggable.element = element; + batch(() => { + const previousElement = this.#element; + const droppableElement = this.droppable.element; + const draggableElement = this.draggable.element; + + if (!droppableElement || droppableElement === previousElement) { this.droppable.element = element; + } + + if (!draggableElement || draggableElement === previousElement) { + this.draggable.element = element; + } + + this.#element = element; + }); } public get element() { - return this.droppable.element ?? this.draggable.element; + const element = this.#element; + + if (!element) return; + + return ProxiedElements.get(element) ?? element ?? this.droppable.element; } public set target(target: Element | undefined) { @@ -342,8 +370,8 @@ export class Sortable { return this.draggable.status; } - public refreshShape(ignoreTransforms: boolean = false) { - return this.droppable.refreshShape(ignoreTransforms); + public refreshShape() { + return this.droppable.refreshShape(); } public accepts(draggable: Draggable): boolean { @@ -382,10 +410,6 @@ export class SortableDraggable extends Draggable { ) { super(input, manager); } - - public get index() { - return this.sortable.index; - } } export class SortableDroppable extends Droppable { @@ -396,12 +420,4 @@ export class SortableDroppable extends Droppable { ) { super(input, manager); } - - public refreshShape(ignoreTransforms = false) { - return super.refreshShape(ignoreTransforms); - } - - public get index() { - return this.sortable.index; - } } diff --git a/packages/dom/src/utilities/element/index.ts b/packages/dom/src/utilities/element/index.ts index 05ff28c9..52f6cef0 100644 --- a/packages/dom/src/utilities/element/index.ts +++ b/packages/dom/src/utilities/element/index.ts @@ -1,2 +1,2 @@ export {cloneElement} from './cloneElement.ts'; -export {createPlaceholder} from './createPlaceholder.ts'; +export {ProxiedElements} from './proxiedElements.ts'; diff --git a/packages/dom/src/utilities/element/proxiedElements.ts b/packages/dom/src/utilities/element/proxiedElements.ts new file mode 100644 index 00000000..62b8148f --- /dev/null +++ b/packages/dom/src/utilities/element/proxiedElements.ts @@ -0,0 +1 @@ +export const ProxiedElements = new WeakMap(); diff --git a/packages/dom/src/utilities/index.ts b/packages/dom/src/utilities/index.ts index 8e98ecb8..bd51a10f 100644 --- a/packages/dom/src/utilities/index.ts +++ b/packages/dom/src/utilities/index.ts @@ -1,11 +1,16 @@ export { getBoundingRectangle, getViewportBoundingRectangle, + PositionObserver, } from './bounding-rectangle/index.ts'; export {canUseDOM, getDocument, getWindow} from './execution-context/index.ts'; -export {cloneElement, createPlaceholder} from './element/index.ts'; +export { + cloneElement, + createPlaceholder, + ProxiedElements, +} from './element/index.ts'; export {Listeners} from './event-listeners/index.ts';