import type { EventBus } from '../event-bus/event-bus'; import { DragPreviewOptions } from './drag-preview'; /** Kinds of item a drag can carry. */ export type DragType = 'row' | 'column' | 'group'; /** Where a drop lands relative to the target it was released over. */ export type DropPosition = 'before' | 'after' | 'inside'; /** The payload of an in-flight drag. */ export interface DragItem { type: DragType; id: string; data: unknown; sourceIndex: number; } /** A registered region that can receive a drop. */ export interface DropTarget { el: HTMLElement; type: DragType; id: string; index: number; acceptsTypes: DragType[]; onDragEnter?: (item: DragItem) => void; onDragLeave?: (item: DragItem) => void; onDrop?: (item: DragItem, position: DropPosition) => void; } /** Mutable state of the gesture currently in progress. */ export interface DragSession { item: DragItem; sourceEl: HTMLElement; currentTarget: DropTarget | null; position: DropPosition | null; startX: number; startY: number; currentX: number; currentY: number; isDragging: boolean; startTime: number; } /** * Generic pointer-driven drag-and-drop coordinator. * * ### Performance model * * The gesture is split into a **sampling** path and a **frame** path. Every * `pointermove` does nothing but hand two numbers to a * {@link DragFrameScheduler}; all hit-testing, class toggling, and event * emission happens once per animation frame with the newest sample. On a * 1000 Hz pointer against a 60 Hz display that is a ~16× reduction in work, * and it removes the write→read layout thrash that a per-event handler * necessarily creates. * * Drop-target geometry is captured once into a {@link DragRectCache} at drag * start and re-read only when something actually moves it — a scroll step, a * window resize. The previous implementation called `getBoundingClientRect()` * once per registered target *per pointer event*, forcing a full layout flush * for each. * * `DRAG_OVER` is emitted only when the resolved target or position changes, so a * pointer travelling across one wide target no longer allocates a payload and * fans out to listeners on every frame. * * @example * ```ts * const engine = new DragDropEngine(eventBus); * * const releaseTarget = engine.registerDropTarget({ * el: rowEl, type: 'row', id: 'r1', index: 0, acceptsTypes: ['row'], * onDrop: (item, position) => reorder(item.id, 'r1', position), * }); * * const releaseHandle = engine.makeDraggable( * handleEl, * { type: 'row', id: 'r1', data: row, sourceIndex: 0 }, * viewportEl, * { label: row.name }, * ); * ``` */ export declare class DragDropEngine { private eventBus; private readonly dropTargets; private readonly preview; private readonly autoscroll; private readonly frames; private readonly rects; /** * Preview options per draggable element. * * Previously a single shared field, which meant the last element to call * {@link makeDraggable} silently dictated the chip for every other draggable. * A `WeakMap` keyed by the element keeps them independent and lets entries be * collected with the DOM node. */ private readonly previewOptionsByEl; /** Targets in the same order as {@link rects}, so a slot index maps back in O(1). */ private candidates; private currentSession; private readonly boundPointerMove; private readonly boundPointerUp; private readonly boundInvalidate; /** Element that captured the pointer, so capture can be released on any exit path. */ private capturedEl; private capturedPointerId; constructor(eventBus: EventBus); /** * Registers a region that can receive drops. * * @param target - The drop target. Its `id` is the map key, so re-registering * the same id replaces the previous entry. * @returns A disposer that unregisters the target. */ registerDropTarget(target: DropTarget): () => void; /** * Makes an element initiate drags for `item`. * * @param el - The grab handle. * @param item - Payload carried by the drag. * @param scrollContainer - Optional container to auto-scroll at its edges. * @param previewOpts - Chip content for this element specifically. * @returns A disposer that detaches the listener and its DOM markers. */ makeDraggable(el: HTMLElement, item: DragItem, scrollContainer?: HTMLElement, previewOpts?: DragPreviewOptions): () => void; /** * Binds the container auto-scrolled at its edges during a drag. * * @param el - The scrollable viewport. */ setScrollContainer(el: HTMLElement): void; /** * Marks cached drop-target geometry stale. * * Call after anything that moves targets on screen but is not a scroll of the * registered container — a layout change, a panel resize. The engine already * invalidates on window `scroll` and `resize` while a drag is live. */ invalidateGeometry(): void; /** Aborts the drag in progress, if any, without firing a drop. */ cancelDrag(): void; /** Tears down all engine state and global listeners. */ destroy(): void; private onPointerMove; private onPointerUp; /** * The whole drag workload, run once per frame with the newest pointer sample. * * Ordering matters: all reads (hit tests) come off the cache, and all writes * (chip transform, class toggles) happen after them, so no write→read pair * inside this function can force a synchronous layout. */ private applyFrame; /** Promotes a press past the threshold into a live drag. */ private beginDrag; /** * Snapshots the geometry of every target that accepts the dragged item. * * Filtering here rather than per frame means the hot path never re-evaluates * `acceptsTypes`, and the cache holds only rects that can actually match. */ private captureTargets; /** * Resolves the slot index under the cursor from cached geometry. * * Re-captures only when the cache has been invalidated (a scroll or resize * since the last frame), so a stationary layout costs zero layout flushes. * * @returns The index into {@link candidates}, or {@link NO_SLOT}. */ private findDropSlot; /** * Classifies where within a target the cursor sits. * * Reads the target's cached vertical span rather than calling * `getBoundingClientRect()` a second time on an element whose rect was already * measured this drag. * * @param mouseY - Client y coordinate. * @param slot - Index of the hit target in {@link candidates}. */ private calcDropPosition; private updateDropIndicator; private clearDropIndicator; private cleanupDrag; private detachGlobalListeners; } //# sourceMappingURL=drag-drop-engine.d.ts.map