diff --git a/packages/abstract/src/core/entities/entity/entity.ts b/packages/abstract/src/core/entities/entity/entity.ts index 9a980f51..c961dbdb 100644 --- a/packages/abstract/src/core/entities/entity/entity.ts +++ b/packages/abstract/src/core/entities/entity/entity.ts @@ -3,10 +3,19 @@ import {effects, reactive, type Effect} from '@dnd-kit/state'; import type {DragDropManager} from '../../manager/index.js'; import type {Data, UniqueIdentifier} from './types.js'; +interface Options { + /** + * A boolean indicating whether the entity should automatically be registered with the manager. + * @defaultValue true + */ + register?: boolean; +} + export interface Input = Entity> { id: UniqueIdentifier; data?: T | null; disabled?: boolean; + options?: Options; effects?(): Effect[]; } @@ -34,6 +43,7 @@ export class Entity { const { effects: getEffects = getDefaultEffects, id, + options, data = null, disabled = false, } = input; @@ -45,7 +55,9 @@ export class Entity { this.disabled = disabled; queueMicrotask(() => { - manager.registry.register(this); + if (options?.register !== false) { + manager.registry.register(this); + } const cleanupEffects = effects( () => { diff --git a/packages/dom/src/plugins/debug/debug.ts b/packages/dom/src/plugins/debug/debug.ts index 98596a3c..24e6291b 100644 --- a/packages/dom/src/plugins/debug/debug.ts +++ b/packages/dom/src/plugins/debug/debug.ts @@ -21,7 +21,7 @@ export class Debug extends Plugin { const collidingIds = topCollisions.map(({id}) => id); if (draggable && dragOperation.shape) { - const element = draggableElement ?? createDebugElement('dialog'); + const element = draggableElement ?? createDebugElement(); const {boundingRectangle} = dragOperation.shape.current; if (!draggableElement) { @@ -40,7 +40,7 @@ export class Debug extends Plugin { } if (element instanceof HTMLDialogElement) { - element.showModal(); + element.showPopover(); } element.style.top = `${boundingRectangle.top}px`; @@ -74,8 +74,8 @@ export class Debug extends Plugin { debugElement.style.backgroundColor = droppable.isDropTarget ? 'rgba(13, 210, 36, 0.6)' : collidingIds.includes(droppable.id) - ? 'rgba(255, 193, 7, 0.5)' - : 'rgba(0, 0, 0, 0.1)'; + ? 'rgba(255, 193, 7, 0.5)' + : 'rgba(0, 0, 0, 0.1)'; debugElement.style.top = `${boundingRectangle.top}px`; debugElement.style.left = `${boundingRectangle.left}px`; @@ -95,6 +95,7 @@ export class Debug extends Plugin { function createDebugElement(tagName = 'div') { const element = document.createElement(tagName); + element.setAttribute('popover', ''); element.style.all = 'initial'; element.style.position = 'fixed'; element.style.display = 'flex'; diff --git a/packages/dom/src/sortable/OptimisticSortingPlugin.ts b/packages/dom/src/sortable/OptimisticSortingPlugin.ts index 92a1b550..c9c62d36 100644 --- a/packages/dom/src/sortable/OptimisticSortingPlugin.ts +++ b/packages/dom/src/sortable/OptimisticSortingPlugin.ts @@ -23,7 +23,7 @@ export class OptimisticSortingPlugin extends Plugin { if (sortableInstances.has(sortable.index)) { throw new Error( - 'Duplicate sortable index found for same sortable group. Make sure each sortable item has a unique index. Use the `group` attribute to separate sortables into different groups.' + `Duplicate sortable index found for same sortable group: ${sortable.droppable.id} and ${sortableInstances.get(sortable.index)?.droppable.id} have the same index (${sortable.index}). Make sure each sortable item has a unique index. Use the \`group\` attribute to separate sortables into different groups.` ); } diff --git a/packages/dom/src/sortable/sortable.ts b/packages/dom/src/sortable/sortable.ts index 696d761d..729343a4 100644 --- a/packages/dom/src/sortable/sortable.ts +++ b/packages/dom/src/sortable/sortable.ts @@ -267,6 +267,10 @@ export class Sortable { }); } + public get id() { + return this.droppable.id; + } + public set sensors(value: Sensors | undefined) { this.draggable.sensors = value; } diff --git a/packages/react/src/core/context/DragDropProvider.tsx b/packages/react/src/core/context/DragDropProvider.tsx index 8494ff41..5c01d1bf 100644 --- a/packages/react/src/core/context/DragDropProvider.tsx +++ b/packages/react/src/core/context/DragDropProvider.tsx @@ -8,12 +8,7 @@ import { import {type DragDropEvents} from '@dnd-kit/abstract'; import {DragDropManager, defaultPreset} from '@dnd-kit/dom'; import type {DragDropManagerInput, Draggable, Droppable} from '@dnd-kit/dom'; -import { - useConstant, - useEvent, - useLatest, - useOnValueChange, -} from '@dnd-kit/react/hooks'; +import {useConstant, useLatest, useOnValueChange} from '@dnd-kit/react/hooks'; import {DragDropContext} from './context.js'; import {useRenderer} from './renderer.js'; @@ -53,11 +48,11 @@ export const DragDropProvider = forwardRef( }); const {plugins, modifiers} = input; const handleBeforeDragStart = useLatest(onBeforeDragStart); - const handleDragStart = useEvent(onDragStart); + const handleDragStart = useLatest(onDragStart); const handleDragOver = useLatest(onDragOver); const handleDragMove = useLatest(onDragMove); const handleDragEnd = useLatest(onDragEnd); - const handleCollision = useEvent(onCollision); + const handleCollision = useLatest(onCollision); useEffect(() => { const listeners = [ @@ -71,7 +66,9 @@ export const DragDropProvider = forwardRef( } } ), - manager.monitor.addEventListener('dragstart', handleDragStart), + manager.monitor.addEventListener('dragstart', (event, manager) => + handleDragStart.current?.(event, manager) + ), manager.monitor.addEventListener('dragover', (event, manager) => { const callback = handleDragOver.current; @@ -93,7 +90,9 @@ export const DragDropProvider = forwardRef( trackRendering(() => callback(event, manager)); } }), - manager.monitor.addEventListener('collision', handleCollision), + manager.monitor.addEventListener('collision', (event, manager) => + handleCollision.current?.(event, manager) + ), ]; return () => { diff --git a/packages/react/src/core/draggable/useDraggable.ts b/packages/react/src/core/draggable/useDraggable.ts index 0bc4b5a2..c5cc3c20 100644 --- a/packages/react/src/core/draggable/useDraggable.ts +++ b/packages/react/src/core/draggable/useDraggable.ts @@ -3,7 +3,7 @@ import type {Data} from '@dnd-kit/abstract'; import {Draggable} from '@dnd-kit/dom'; import type {DraggableInput} from '@dnd-kit/dom'; import {useComputed, useOnValueChange} from '@dnd-kit/react/hooks'; -import {getCurrentValue, type RefOrValue} from '@dnd-kit/react/utilities'; +import {currentValue, type RefOrValue} from '@dnd-kit/react/utilities'; import {useInstance} from '../hooks/useInstance.js'; @@ -17,10 +17,22 @@ export function useDraggable( input: UseDraggableInput ) { const {disabled, id, sensors} = input; - const handle = getCurrentValue(input.handle); - const element = getCurrentValue(input.element); + const handle = currentValue(input.handle); + const element = currentValue(input.element); const draggable = useInstance( - (manager) => new Draggable({...input, handle, element}, manager) + (manager) => + new Draggable( + { + ...input, + handle, + element, + options: { + ...input.options, + register: false, + }, + }, + manager + ) ); const isDragSource = useComputed(() => draggable.isDragSource); diff --git a/packages/react/src/core/droppable/useDroppable.ts b/packages/react/src/core/droppable/useDroppable.ts index 940f1c82..2db99576 100644 --- a/packages/react/src/core/droppable/useDroppable.ts +++ b/packages/react/src/core/droppable/useDroppable.ts @@ -4,7 +4,7 @@ import {Droppable} from '@dnd-kit/dom'; import {deepEqual} from '@dnd-kit/state'; import type {DroppableInput} from '@dnd-kit/dom'; import {useComputed, useOnValueChange} from '@dnd-kit/react/hooks'; -import {getCurrentValue, type RefOrValue} from '@dnd-kit/react/utilities'; +import {currentValue, type RefOrValue} from '@dnd-kit/react/utilities'; import {useInstance} from '../hooks/useInstance.js'; @@ -17,9 +17,20 @@ export function useDroppable( input: UseDroppableInput ) { const {collisionDetector, disabled, id, accept, type} = input; - const element = getCurrentValue(input.element); + const element = currentValue(input.element); const droppable = useInstance( - (manager) => new Droppable({...input, element}, manager) + (manager) => + new Droppable( + { + ...input, + element, + options: { + ...input.options, + register: false, + }, + }, + manager + ) ); const isDisabled = useComputed(() => droppable.disabled); const isDropTarget = useComputed(() => droppable.isDropTarget); diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts index 59d17599..8f5d0b0f 100644 --- a/packages/react/src/hooks/index.ts +++ b/packages/react/src/hooks/index.ts @@ -1,8 +1,6 @@ export {useConstant} from './useConstant.js'; export {useComputed} from './useComputed.js'; -export {useEvent} from './useEvent.js'; export {useImmediateEffect} from './useImmediateEffect.js'; export {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect.js'; export {useLatest} from './useLatest.js'; export {useOnValueChange} from './useOnValueChange.js'; -export {useSignalEffect} from './useSignalEffect.js'; diff --git a/packages/react/src/hooks/useEvent.ts b/packages/react/src/hooks/useEvent.ts deleted file mode 100644 index 17bd3351..00000000 --- a/packages/react/src/hooks/useEvent.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {useCallback} from 'react'; - -import {useLatest} from './useLatest.js'; - -export function useEvent(handler: T | undefined) { - const handlerRef = useLatest(handler); - - return useCallback( - function (...args: any) { - return handlerRef.current?.(...args); - }, - [handlerRef] - ); -} diff --git a/packages/react/src/hooks/useOnValueChange.ts b/packages/react/src/hooks/useOnValueChange.ts index 99d43bae..98308aa7 100644 --- a/packages/react/src/hooks/useOnValueChange.ts +++ b/packages/react/src/hooks/useOnValueChange.ts @@ -6,11 +6,12 @@ export function useOnValueChange( effect = useEffect, compare = Object.is ) { - const tracked = useRef(value); + const tracked = useRef(value); effect(() => { const oldValue = tracked.current; - if (!compare(value, tracked.current)) { + + if (!compare(value, oldValue)) { tracked.current = value; onChange(value, oldValue); } diff --git a/packages/react/src/hooks/useSignalEffect.ts b/packages/react/src/hooks/useSignalEffect.ts deleted file mode 100644 index d812e9f5..00000000 --- a/packages/react/src/hooks/useSignalEffect.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {useEffect} from 'react'; -import {effect} from '@dnd-kit/state'; - -export function useSignalEffect(compute: () => void, deps: any[] = []) { - useEffect(() => effect(compute), deps); -} diff --git a/packages/react/src/sortable/useSortable.ts b/packages/react/src/sortable/useSortable.ts index 0f44c588..8cbbd138 100644 --- a/packages/react/src/sortable/useSortable.ts +++ b/packages/react/src/sortable/useSortable.ts @@ -1,4 +1,4 @@ -import {useCallback, useEffect} from 'react'; +import {useCallback, useLayoutEffect} from 'react'; import {deepEqual} from '@dnd-kit/state'; import {type Data} from '@dnd-kit/abstract'; import {Sortable, defaultSortableTransition} from '@dnd-kit/dom/sortable'; @@ -11,7 +11,7 @@ import { useImmediateEffect as immediateEffect, useIsomorphicLayoutEffect as layoutEffect, } from '@dnd-kit/react/hooks'; -import {getCurrentValue, type RefOrValue} from '@dnd-kit/react/utilities'; +import {currentValue, type RefOrValue} from '@dnd-kit/react/utilities'; export interface UseSortableInput extends Omit, 'handle' | 'element'> { @@ -27,6 +27,7 @@ export function useSortable(input: UseSortableInput) { id, data, index, + group, disabled, feedback, sensors, @@ -35,23 +36,25 @@ export function useSortable(input: UseSortableInput) { } = input; const manager = useDragDropManager(); - const handle = getCurrentValue(input.handle); - const element = getCurrentValue(input.element); - const sortable = useConstant( - () => - new Sortable( - { - ...input, - handle, - element, - feedback, + const handle = currentValue(input.handle); + const element = currentValue(input.element); + const sortable = useConstant(() => { + return new Sortable( + { + ...input, + handle, + element, + feedback, + options: { + ...input.options, + register: false, }, - manager - ), - manager - ); + }, + manager + ); + }, manager); - useEffect(() => { + useLayoutEffect(() => { manager.registry.register(sortable.draggable); manager.registry.register(sortable.droppable); @@ -59,20 +62,22 @@ export function useSortable(input: UseSortableInput) { manager.registry.unregister(sortable.draggable); manager.registry.unregister(sortable.droppable); }; - }, [manager]); + }, [sortable, manager]); const isDisabled = useComputed(() => sortable.disabled); const isDropTarget = useComputed(() => sortable.isDropTarget); const isDragSource = useComputed(() => sortable.isDragSource); + useOnValueChange(id, () => (sortable.id = id)); + useOnValueChange(index, () => (sortable.index = index), layoutEffect); + useOnValueChange(type, () => (sortable.type = type)); + useOnValueChange(group, () => (sortable.group = group)); useOnValueChange( accept, () => (sortable.accept = accept), undefined, deepEqual ); - useOnValueChange(type, () => (sortable.type = type)); - useOnValueChange(id, () => (sortable.id = id)); useOnValueChange(data, () => (sortable.data = data ?? null)); useOnValueChange( index, @@ -83,7 +88,6 @@ export function useSortable(input: UseSortableInput) { }, immediateEffect ); - useOnValueChange(index, () => (sortable.index = index), layoutEffect); useOnValueChange(handle, () => (sortable.handle = handle)); useOnValueChange(element, () => (sortable.element = element)); useOnValueChange(disabled, () => (sortable.disabled = disabled === true)); diff --git a/packages/react/src/utilities/getCurrentValue.ts b/packages/react/src/utilities/currentValue.ts similarity index 91% rename from packages/react/src/utilities/getCurrentValue.ts rename to packages/react/src/utilities/currentValue.ts index 09d35557..08b3a7c2 100644 --- a/packages/react/src/utilities/getCurrentValue.ts +++ b/packages/react/src/utilities/currentValue.ts @@ -7,7 +7,7 @@ export type RefOrValue = | null | undefined; -export function getCurrentValue( +export function currentValue( value: RefOrValue ): NonNullable | undefined { if (value == null) { diff --git a/packages/react/src/utilities/index.ts b/packages/react/src/utilities/index.ts index dae5ce58..384036e2 100644 --- a/packages/react/src/utilities/index.ts +++ b/packages/react/src/utilities/index.ts @@ -1 +1 @@ -export {getCurrentValue, type RefOrValue} from './getCurrentValue.js'; +export {currentValue, type RefOrValue} from './currentValue.js'; diff --git a/packages/state/src/comparators.ts b/packages/state/src/comparators.ts index e728de13..5f5992d2 100644 --- a/packages/state/src/comparators.ts +++ b/packages/state/src/comparators.ts @@ -4,11 +4,7 @@ export function deepEqual(a: T, b: T) { } if (Array.isArray(a)) { - if (!Array.isArray(b)) { - return false; - } - - if (a.length !== b.length) { + if (!Array.isArray(b) || a.length !== b.length) { return false; }