Vize

Drag and Drop

Headless Drag and Drop; covers React Aria useDrag and useDrop, Pragmatic drag and drop adapters, HTML Drag and Drop DataTransfer.

Package @vizejs/ui/drag-and-drop
Maturity stable
Own the source vize lib pull drag-and-drop
Requires —
Aliases drag and drop, dnd, drag handle, drop target, drop indicator
Covers React Aria useDrag and useDrop, Pragmatic drag and drop adapters, HTML Drag and Drop DataTransfer

API

createDragAndDrop

Create an SSR-safe pointer, touch, and keyboard drag-and-drop coordinator.

function createDragAndDrop<Data = unknown>( options: DragAndDropOptions<Data> = {}, ): DragAndDropController<Data>

useDragAndDrop

Create a drag-and-drop coordinator disposed with the current effect scope.

function useDragAndDrop<Data = unknown>( options: DragAndDropOptions<Data> = {}, ): DragAndDropController<Data>

createDragLiveRegion

Create a lazily mounted role="status" live region for drag announcements.

function createDragLiveRegion(): DragLiveRegion

defaultDragAnnouncements

Built-in English announcement builders; consumers override to localize.

const defaultDragAnnouncements: Required<DragAnnouncements>

createDragAutoScroller

Create an edge-proximity auto-scroller for one controller. Each pointer sample applies one immediate scroll step; while a sample stays inside a threshold band, an animation-frame loop keeps scrolling so a held pointer near an edge continues to advance the container.

function createDragAutoScroller( options: DragAutoScrollOptions | undefined, ): DragAutoScroller

readBoolean

Resolve and validate a reactive boolean option for JavaScript consumers.

function readBoolean(value: MaybeBoolean, name: string, defaultValue = false): boolean

readDistance

Resolve a finite non-negative pixel distance with an explicit default.

function readDistance( value: DragAndDropOptions["startDistance"], name: string, defaultValue: number, minimum = 0, ): number

readLabel

Resolve an announcement label falling back to the owning key.

function readLabel(value: DragSourceOptions["label"], key: string): string

validateCallbacks

Validate one callback slot eagerly so setup failures never install listeners.

function validateCallbacks(options: object, names: readonly string[]): void

validateKey

Validate a registration key and its uniqueness inside one controller.

function validateKey( key: unknown, existing: ReadonlySet<string> | ReadonlyMap<string, unknown>, ): string

validateEdges

Validate the closed drop-edge union without accepting mistyped JavaScript.

function validateEdges(edges: DropTargetOptions["edges"]): readonly DropEdge[]

normalizeRect

Normalize a measured rectangle, rejecting rects that cannot own a drop.

function normalizeRect( rect: DropTargetRect | DOMRectReadOnly | null | undefined, ): DropTargetRect | null

measureRect

Measure one element through its override or the live layout.

function measureRect( element: Element | null | undefined, getRect: (() => DropTargetRect | DOMRectReadOnly | null | undefined) | undefined, ): DropTargetRect | null

containsPoint

Whether a client point lies inside an axis-aligned rectangle.

function containsPoint(rect: DropTargetRect, point: Point): boolean

exceedsDistance

Chebyshev distance gate deciding when an armed pointer becomes a session.

function exceedsDistance(origin: Point, point: Point, threshold: number): boolean

resolveEdge

Resolve the drop edge for a point inside a rectangle. When "inside" is allowed together with directional edges, the central half of each constrained axis resolves to "inside"; otherwise the nearest allowed edge on the dominant axis wins.

function resolveEdge( rect: DropTargetRect, point: Point, edges: readonly DropEdge[], ): DropEdge

indicatorFor

Build indicator geometry: a placeholder rect plus a collapsed edge line.

function indicatorFor( targetKey: string, edge: DropEdge, rect: DropTargetRect | null, ): DropIndicatorState

compareDocumentOrder

Sort elements into document order, treating detached elements as later.

function compareDocumentOrder(left: Element, right: Element): number

hitTest

Overlay-safe hit test: measured rectangles are compared directly, so drag previews and overlays can never mask a target. Nested ownership prefers the innermost target by DOM containment, then the smallest area.

function hitTest<Candidate extends HitCandidate>( candidates: readonly Candidate[], point: Point, ): Candidate | null

createDragEvent

Build a frozen lifecycle snapshot for controller-level callbacks.

function createDragEvent<Data, Type extends DragEventType>( type: Type, pointerType: DragPointerType, sourceKey: string, payload: DragPayload<Data> | null, targetKey: string | null, edge: DropEdge | null, point: Point | null, originalEvent: Event | null, isCanceled = false, ): DragLifecycleEvent<Data, Type>

createDropTargetEvent

Build a frozen target-scoped snapshot for drop-target callbacks.

function createDropTargetEvent<Data>( type: DropTargetEventType, targetKey: string, sourceKey: string, pointerType: DragPointerType, payload: DragPayload<Data> | null, edge: DropEdge | null, point: Point | null, originalEvent: Event | null, ): DropTargetEvent<Data>

handleSourceKeydown

Handle one keydown from a source handle: grab, navigate, drop, or cancel.

function handleSourceKeydown<Data>( core: SessionCore<Data>, options: DragAndDropOptions<Data>, event: KeyboardEvent, key: string, ): void

installDragListeners

Install and exhaustively clean up the document listeners for one session.

function installDragListeners( document: Document, source: DragListenerSource, context: DragListenerContext, ): () => void

createSessionCore

Shared mutable session state and settlement logic for one controller.

function createSessionCore<Data>( options: DragAndDropOptions<Data>, liveRegion: DragLiveRegion, autoScroller: DragAutoScroller, )

DRAG_TRANSFER_TYPE

Structured data-transfer format written next to the plain-text projection.

const DRAG_TRANSFER_TYPE

writeDragTransfer

Serialize one typed payload onto a DataTransfer. The structured payload is written under DRAG_TRANSFER_TYPE; the optional plainText projection is mirrored to text/plain so external applications receive a meaningful representation.

function writeDragTransfer(dataTransfer: DataTransfer, payload: DragPayload): void

readDragTransfer

Deserialize a typed payload from a DataTransfer.

function readDragTransfer<Data = unknown>( dataTransfer: DataTransfer, ): DragPayload<Data> | null

writeClipboardTransfer

Serialize one typed payload onto a clipboard event during copy or cut. The event's default action is prevented so the written data wins.

function writeClipboardTransfer(event: ClipboardEvent, payload: DragPayload): boolean

readClipboardTransfer

Deserialize a typed payload from a clipboard event during paste.

function readClipboardTransfer<Data = unknown>( event: ClipboardEvent, ): DragPayload<Data> | null

Behavior

Normative state × input → outcome table for @vizejs/ui/drag-and-drop. Every row is exercised by src/families/interaction/drag-and-drop/drag-and-drop*.test.ts; compile-only assertions live in src/families/interaction/drag-and-drop/drag-and-drop.types.test-d.ts.

# State Input Outcome Proven by
D1 idle primary pointer, legacy mouse, or single touch press one contact is armed; selection is guarded; no session yet pointer tests
D2 armed movement below the start distance lifecycle remains silent and a later release settles silently start-distance test
D3 armed movement at or past the start distance one session starts; the payload snapshot and grab announcement emit pointer lifecycle test
D4 dragging pointer over an accepting target rectangle innermost target enters; edge, indicator, and move announcement emit hit-test tests
D5 dragging over target pointer crosses into another allowed edge edge changes emit target move callbacks and re-announce indicator test
D6 dragging over target pointer leaves every measurable target leave callback fires; over state, edge, and indicator clear leave test
D7 dragging over nested rects overlapping targets contain the point DOM containment picks the innermost target, then the smallest area nested ownership test
D8 dragging disabled or rejecting targets under the point those targets never enter hit testing or keyboard order filtering test
D9 dragging over target owning contact releases drop callback, drop announcement, and dragend settle exactly once drop test
D10 dragging outside targets owning contact releases dragend reports no target without cancellation outside-drop test
D11 armed or dragging Escape, native drag, blur, hidden document, cancel() canceled settlement runs every teardown exactly once cancellation tests
D12 dragging reactive disablement becomes true the session cancels before new movement is observed reactive teardown test
D13 dragging near container edge pointer enters the auto-scroll threshold band the container scrolls immediately and while the pointer holds auto-scroll test
D14 idle focus on handle Enter or Space without modifiers keyboard session grabs and announces the first valid target keyboard grab test
D15 keyboard session arrows, Home, and End valid targets cycle in document order with indicator and speech keyboard navigation test
D16 keyboard session Enter or Space drop lands on the current target and settles the session keyboard drop test
D17 keyboard session Escape, Tab, or focus leaving the handle the session cancels and announces the cancellation keyboard cancel test
D18 any session registration disposal of the owning source the session cancels first and the registry entry is removed disposal tests
D19 any session controller disposal listeners, live region, and reactive state release without callbacks disposal tests
D20 typed payloads data-transfer and clipboard adapters round-trip structured payloads serialize losslessly; malformed input reads null transfer tests
D21 concurrent SSR requests identical component trees byte-identical markup contains no handlers or DOM reads SSR test
D22 SSR followed by hydration pointer drag host identity remains and reactive output updates without warning hydration test
D23 public TypeScript API mutation or invalid closed union compilation rejects misuse type assertions

Accessibility and touch obligations

Every drag handle must be keyboard focusable so pointer drags have an Enter-grab, arrow-move, Enter-drop equivalent, and every session phase speaks through an owned assertive role="status" live region created lazily on the first announcement. Announcement builders are injectable so consumers localize grab, move, drop, and cancel messages. The primitive assigns no role, label, or visual style; consumers must render the indicator geometry themselves and must apply touch-action: none when continuous touch dragging must take precedence over viewport panning. Hit testing compares measured rectangles directly, so drag previews and overlays can never mask a drop target.