diff --git a/apps/docs/stories/react/Droppable/docs/examples/Droppable.jsx b/apps/docs/stories/react/Droppable/docs/examples/Droppable.jsx index cb0a9eef..73baf41e 100644 --- a/apps/docs/stories/react/Droppable/docs/examples/Droppable.jsx +++ b/apps/docs/stories/react/Droppable/docs/examples/Droppable.jsx @@ -11,9 +11,9 @@ export function Droppable({id, children}) { justifyContent: 'center', width: 300, height: 300, - backgroundColor: '##FFF', - border: '2px solid', - borderColor: isDropTarget ? 'lightgreen' : 'rgba(0,0,0,0.2)', + backgroundColor: isDropTarget ? '#1eb99d25' : '#ffffff', + border: '3px solid', + borderColor: isDropTarget ? '#1eb99d' : '#00000020', borderRadius: 10, }}> {children} diff --git a/apps/docs/stories/react/Sortable/SortableExample.tsx b/apps/docs/stories/react/Sortable/SortableExample.tsx index d0ebc961..0d0da4d3 100644 --- a/apps/docs/stories/react/Sortable/SortableExample.tsx +++ b/apps/docs/stories/react/Sortable/SortableExample.tsx @@ -5,12 +5,13 @@ import type { Modifiers, UniqueIdentifier, } from '@dnd-kit/abstract'; +import {FeedbackType, defaultPreset} from '@dnd-kit/dom'; +import type {SortableTransition} from '@dnd-kit/dom/sortable'; import {DragDropProvider} from '@dnd-kit/react'; import {useSortable} from '@dnd-kit/react/sortable'; -import {FeedbackType, defaultPreset} from '@dnd-kit/dom'; -import {Debug} from '@dnd-kit/dom/plugins/debug'; import {directionBiased} from '@dnd-kit/collision'; import {move} from '@dnd-kit/state-management'; +import {Debug} from '@dnd-kit/dom/plugins/debug'; import {Item, Handle} from '../components'; import {createRange, cloneDeep} from '../../utilities'; @@ -21,6 +22,7 @@ interface Props { feedback?: FeedbackType; modifiers?: Modifiers; layout?: 'vertical' | 'horizontal' | 'grid'; + transition?: SortableTransition; itemCount?: number; collisionDetector?: CollisionDetector; getItemStyle?(id: UniqueIdentifier, index: number): CSSProperties; @@ -34,6 +36,7 @@ export function SortableExample({ feedback, layout = 'vertical', modifiers, + transition, getItemStyle, }: Props) { const [items, setItems] = useState(createRange(itemCount)); @@ -70,6 +73,7 @@ export function SortableExample({ collisionDetector={collisionDetector} dragHandle={dragHandle} feedback={feedback} + transition={transition} style={getItemStyle?.(id, index)} /> ))} @@ -81,9 +85,10 @@ export function SortableExample({ interface SortableProps { id: UniqueIdentifier; index: number; + collisionDetector?: CollisionDetector; dragHandle?: boolean; feedback?: FeedbackType; - collisionDetector?: CollisionDetector; + transition?: SortableTransition; style?: React.CSSProperties; } @@ -93,6 +98,7 @@ function SortableItem({ collisionDetector = directionBiased, dragHandle, feedback, + transition, style, }: PropsWithChildren) { const [element, setElement] = useState(null); @@ -103,6 +109,7 @@ function SortableItem({ index, element, feedback, + transition, activator: activatorRef, collisionDetector, }); diff --git a/apps/docs/stories/react/Sortable/Vertical/Vertical.stories.tsx b/apps/docs/stories/react/Sortable/Vertical/Vertical.stories.tsx index 40e1d8a5..42c9a86b 100644 --- a/apps/docs/stories/react/Sortable/Vertical/Vertical.stories.tsx +++ b/apps/docs/stories/react/Sortable/Vertical/Vertical.stories.tsx @@ -66,3 +66,24 @@ export const CustomDragLayer: Story = { modifiers: [VerticalModifier], }, }; + +export const CustomTransition: Story = { + name: 'Custom transition', + args: { + debug: false, + transition: { + duration: 300, + easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', + }, + }, +}; + +export const DisableTransition: Story = { + name: 'Disable transition', + args: { + debug: false, + transition: { + duration: 0, + }, + }, +}; diff --git a/packages/dom/src/core/index.ts b/packages/dom/src/core/index.ts index bf82e6d1..ff528fe5 100644 --- a/packages/dom/src/core/index.ts +++ b/packages/dom/src/core/index.ts @@ -17,3 +17,4 @@ export { Scroller, ScrollListener, } from './plugins/index.js'; +export type {Transition} from './plugins/index.js'; diff --git a/packages/dom/src/core/nodes/draggable/draggable.ts b/packages/dom/src/core/nodes/draggable/draggable.ts index c03081d5..2e1f765b 100644 --- a/packages/dom/src/core/nodes/draggable/draggable.ts +++ b/packages/dom/src/core/nodes/draggable/draggable.ts @@ -46,31 +46,28 @@ export class Draggable extends AbstractDraggable { this.feedback = feedback; - const effects = [ - // Bind sensors - effect(() => { - const sensors = this.sensors?.map(descriptor) ?? [...manager.sensors]; - const unbindFunctions = sensors.map((entry) => { - const sensorInstance = - entry instanceof Sensor - ? entry - : manager.registry.register(entry.plugin); - const options = entry instanceof Sensor ? undefined : entry.options; + const effectCleanup = effect(() => { + const sensors = this.sensors?.map(descriptor) ?? [...manager.sensors]; + const unbindFunctions = sensors.map((entry) => { + const sensorInstance = + entry instanceof Sensor + ? entry + : manager.registry.register(entry.plugin); + const options = entry instanceof Sensor ? undefined : entry.options; - const unbind = sensorInstance.bind(this, options); - return unbind; - }); + const unbind = sensorInstance.bind(this, options); + return unbind; + }); - return function cleanup() { - unbindFunctions.forEach((unbind) => unbind()); - }; - }), - ]; + return function cleanup() { + unbindFunctions.forEach((unbind) => unbind()); + }; + }); const {destroy} = this; this.destroy = () => { - effects.forEach((cleanup) => cleanup()); + effectCleanup(); destroy(); }; } diff --git a/packages/dom/src/core/plugins/feedback/DraggableFeedback.ts b/packages/dom/src/core/plugins/feedback/DraggableFeedback.ts index bc06ef10..933a2b99 100644 --- a/packages/dom/src/core/plugins/feedback/DraggableFeedback.ts +++ b/packages/dom/src/core/plugins/feedback/DraggableFeedback.ts @@ -7,9 +7,11 @@ import type {DragDropManager} from '../../manager/index.js'; import {Overlay} from './Overlay.js'; import {patchElement} from './utilities.js'; +import type {Transition} from './types.js'; interface DraggableFeedbackOptions { tagName?: string; + transition?: Transition | null; } export class DraggableFeedback extends CorePlugin { @@ -189,16 +191,13 @@ export class DraggableFeedback extends CorePlugin { const clone = source.feedback === 'clone'; const currentElement = source.element; - const shape = new DOMRectangle(currentElement); if (!overlay) { - overlay = new Overlay( - manager, - currentElement, - shape, - options.tagName || - currentElement.parentElement?.tagName.toLowerCase() - ); + overlay = new Overlay(manager, { + anchor: currentElement, + tagName: options.tagName, + transition: options.transition, + }); } const currentPlaceholder = placeholder.peek(); @@ -227,16 +226,3 @@ export class DraggableFeedback extends CorePlugin { }; } } - -// const resizeObserver = new ResizeObserver((entries) => { -// const [entry] = entries; -// const [size] = entry.borderBoxSize; -// const {blockSize, inlineSize} = size; - -// placeholder.style.width = `${inlineSize}px`; -// placeholder.style.height = `${blockSize}px`; -// }); - -// resizeObserver.observe(element, {box: 'border-box'}); - -// cleanupFns.push(() => resizeObserver.disconnect()); diff --git a/packages/dom/src/core/plugins/feedback/Overlay.ts b/packages/dom/src/core/plugins/feedback/Overlay.ts index 605c2337..c210a8c8 100644 --- a/packages/dom/src/core/plugins/feedback/Overlay.ts +++ b/packages/dom/src/core/plugins/feedback/Overlay.ts @@ -6,22 +6,38 @@ import { scheduler, createPlaceholder, } from '@dnd-kit/dom/utilities'; -import {Rectangle} from '@dnd-kit/geometry'; +import {BoundingRectangle, Rectangle} from '@dnd-kit/geometry'; import {DragDropManager} from '../../manager/index.js'; +import type {Transition} from './types.js'; import css from './Overlay.css'; const INSIGNIFICANT_DELTA = 1; +interface Options { + anchor: Element; + boundingRectangle?: BoundingRectangle; + tagName?: string; + transition?: Transition | null; +} + +const defaultTransition: Transition = { + duration: 250, + easing: 'ease', +}; + export class Overlay { constructor( private manager: DragDropManager, - anchor: Element, - boundingRectangle = new DOMRectangle(anchor), - tagName = 'dialog' + private options: Options ) { + const { + anchor, + boundingRectangle = new DOMRectangle(anchor), + tagName = anchor.parentElement?.tagName.toLowerCase(), + } = options; const {top, left, width, height} = boundingRectangle; - const element = document.createElement(tagName); + const element = document.createElement(tagName || 'dialog'); const style = document.createElement('style'); style.innerText = css.trim().replace(/\s+/g, ''); @@ -49,7 +65,7 @@ export class Overlay { }; effect(() => { - const {source, position} = manager.dragOperation; + const {source} = manager.dragOperation; if (!source || !source.element) { return; @@ -157,13 +173,14 @@ export class Overlay { public dropAnimation() { return new Promise((resolve) => { const {manager} = this; + const {transition = defaultTransition} = this.options; const {source} = manager.dragOperation; const unmount = () => { this.element.remove(); resolve(); }; - if (!source || !manager.dragOperation.status.dropping) { + if (!transition || !source || !manager.dragOperation.status.dropping) { unmount(); return; } @@ -211,8 +228,8 @@ export class Overlay { ], }, { - duration: 250, - easing: 'ease', + duration: transition.duration, + easing: transition.easing, } ) .finished.then(() => { diff --git a/packages/dom/src/core/plugins/feedback/index.ts b/packages/dom/src/core/plugins/feedback/index.ts index 70653352..9d3170a1 100644 --- a/packages/dom/src/core/plugins/feedback/index.ts +++ b/packages/dom/src/core/plugins/feedback/index.ts @@ -1 +1,2 @@ export {DraggableFeedback} from './DraggableFeedback.js'; +export type {Transition} from './types.js'; diff --git a/packages/dom/src/core/plugins/feedback/types.ts b/packages/dom/src/core/plugins/feedback/types.ts new file mode 100644 index 00000000..f8a36eca --- /dev/null +++ b/packages/dom/src/core/plugins/feedback/types.ts @@ -0,0 +1,12 @@ +export interface Transition { + /** + * The duration of the transition in milliseconds. + * @default 250 + */ + duration?: number; + /** + * The easing function to use for the transition. + * @default 'ease-in-out' + */ + easing?: string; +} diff --git a/packages/dom/src/core/plugins/index.ts b/packages/dom/src/core/plugins/index.ts index dbb43510..0e297de7 100644 --- a/packages/dom/src/core/plugins/index.ts +++ b/packages/dom/src/core/plugins/index.ts @@ -1,5 +1,6 @@ export {Cursor} from './cursor/index.js'; export {DraggableFeedback} from './feedback/index.js'; +export type {Transition} from './feedback/types.js'; export {AutoScroller, Scroller, ScrollListener} from './scrolling/index.js'; diff --git a/packages/dom/src/core/sensors/pointer/PointerSensor.ts b/packages/dom/src/core/sensors/pointer/PointerSensor.ts index c78ea1bc..f96c3fa6 100644 --- a/packages/dom/src/core/sensors/pointer/PointerSensor.ts +++ b/packages/dom/src/core/sensors/pointer/PointerSensor.ts @@ -247,7 +247,12 @@ export class PointerSensor extends Sensor< event.stopPropagation(); // End the drag and drop operation - this.manager.actions.stop(); + const {status} = this.manager.dragOperation; + + if (!status.idle) { + const canceled = !status.initialized; + this.manager.actions.stop({canceled}); + } // Remove the pointer move and up event listeners this.cleanup?.(); @@ -277,7 +282,9 @@ export class PointerSensor extends Sensor< } protected handleCancel() { - if (this.manager.dragOperation.status.initialized) { + const {dragOperation} = this.manager; + + if (dragOperation.status.initialized) { this.manager.actions.stop({canceled: true}); } diff --git a/packages/dom/src/sortable/sortable.ts b/packages/dom/src/sortable/sortable.ts index 40a29654..12baf6d2 100644 --- a/packages/dom/src/sortable/sortable.ts +++ b/packages/dom/src/sortable/sortable.ts @@ -24,12 +24,12 @@ import {SortableKeyboardPlugin} from './SortableKeyboardPlugin.js'; export interface SortableTransition { /** * The duration of the transition in milliseconds. - * @default 200 + * @default 300 */ duration?: number; /** * The easing function to use for the transition. - * @default 'ease-in-out' + * @default 'cubic-bezier(0.25, 1, 0.5, 1)' */ easing?: string; /** @@ -49,8 +49,8 @@ export interface SortableInput } export const defaultSortableTransition: SortableTransition = { - duration: 200, - easing: 'ease-in-out', + duration: 300, + easing: 'cubic-bezier(0.25, 1, 0.5, 1)', idle: false, }; @@ -63,8 +63,6 @@ export class Sortable { transition: SortableTransition | null; - #test: any = null; - constructor( { index, @@ -141,7 +139,7 @@ export class Sortable { const {shape} = this.droppable; const {idle} = manager.dragOperation.status; - if (!shape || !transition) { + if (!shape || !transition || (idle && !transition.idle)) { return; } @@ -194,8 +192,6 @@ export class Sortable { } public set disabled(value: boolean) { - this.#test = 'blah'; - batch(() => { this.draggable.disabled = value; this.droppable.disabled = value;