DragSession is the stable public handle for one drag gesture. It is a TypeScript interface, not a constructor; SnapSort supplies it through drag events and the root Container’s dragSession property.

import type { DragSession, DragStartEvent } from "@snap-engine/snapsort";

function onDragStart(event: DragStartEvent) {
  const session: DragSession = event.session;
  session.dragVisual = "preview";
}

Interface

interface DragSession {
  readonly root: Container;
  readonly pointerId: number;
  readonly items: readonly Item[];
  readonly sources: readonly DragLocation[];
  readonly pressedItem: Item;
  readonly primaryItem: Item;
  readonly start: Readonly<{ x: number; y: number }>;
  readonly pointer: Readonly<{ x: number; y: number }>;
  readonly status: DragSessionStatus;
  dragVisual: DragVisual;
  dropEffect: DropEffect;
  /** @deprecated */
  handoff(replacements: readonly Item[]): void;
}

Properties

root

Type: Container · Read-only

The independent root Container that owns the gesture. It receives root lifecycle callbacks and exposes this handle as root.dragSession while the session is active.

const activeSession = event.session.root.dragSession;

pointerId

Type: number · Read-only

The browser pointer identifier captured from the gesture that started the drag. Use it when correlating the session with application-level pointer state.

if (event.pointerId === session.pointerId) {
  // Both refer to the pointer driving this drag.
}

items

Type: readonly Item[] · Read-only

The complete ordered run participating in the drag. A single-item drag has one entry. Multi-drag participants are ordered by their original document position, not by which Item was pressed.

const draggedIds = session.items.map((item) => item.itemId);

The array is frozen. Do not add, remove, or reorder its entries.

sources

Type: readonly DragLocation[] · Read-only

The gesture-origin locations parallel to items: sources[i] describes items[i]. They remain the original locations when the prospective target changes and after a deprecated handoff.

const origins = session.items.map((item, index) => ({
  item,
  source: session.sources[index],
}));

The array and each location are frozen.

pressedItem

Type: Item · Read-only

The Item on which pointer input began. It anchors pointer-following geometry and can differ from primaryItem when the user presses a later member of a multi-selection.

const pressedId = session.pressedItem.itemId;

primaryItem

Type: Item · Read-only

The first participant in the ordered run; equivalent to items[0]. SnapSort uses this Item for singular event fields and swap placement.

const isSingleDrag =
  session.items.length === 1 && session.primaryItem === session.pressedItem;

start

Type: Readonly<{ x: number; y: number }> · Read-only

Frozen world-space pointer coordinates captured when the drag began.

const distanceX = session.pointer.x - session.start.x;

pointer

Type: Readonly<{ x: number; y: number }> · Read-only

The latest world-space pointer coordinates processed by SnapSort. Each value is an immutable snapshot. Read it inside callbacks; do not use it as a continuous framework render signal.

function onDropTargetChange(event: DropTargetChangeEvent) {
  console.log(event.session.pointer);
}

status

Type: DragSessionStatus · Read-only

The current public lifecycle phase:

type DragSessionStatus = "pending" | "active" | "dropping" | "ended";
  • "pending": SnapSort is running onDragStart; activation can still be vetoed and dragVisual can still be changed.
  • "active": pointer movement can resolve targets, hover, and ghost state.
  • "dropping": cancellation or pointer release is finalizing temporary and persistent state.
  • "ended": cleanup is complete and root.dragSession is already null.
if (session.status === "active") {
  session.dropEffect = "none";
}

dragVisual

Type: "item" | "preview" | "none" · Writable only while pending

Selects what follows the pointer independently of placement feedback:

  • "item" hoists and positions the real dragged Items.
  • "preview" leaves the Items in layout and creates a root-owned pointer-preview Ghost.
  • "none" leaves Items in layout without a pointer-following visual.

The default is "item" for euclidean and progressive mode, "none" for insertion mode, and "preview" for swap mode. Set it synchronously inside onDragStart; changing it after activation or assigning another value throws.

const callbacks = {
  onDragStart(event: DragStartEvent) {
    event.session.dragVisual = "preview";
  },
} satisfies ContainerCallbacks;

Placement feedback remains independent, so a preview can coexist with a target spacer or insertion marker.

dropEffect

Type: "move" | "none" · Default: "move" · Writable while pending or active

Controls whether SnapSort performs its ordinary persistent drop mutation:

  • "move" commits the resolved move or swap through the root callback.
  • "none" suppresses that mutation while onDragEnd still reports the resolved destination.

Changing it after dropping begins or assigning another value throws. A cancelled session never performs the persistent mutation.

const callbacks = {
  onDragStart(event: DragStartEvent) {
    event.session.dropEffect = "none";
  },
  onDragEnd(event: DragEndEvent) {
    if (event.destination) handleApplicationOwnedDrop(event);
  },
} satisfies ContainerCallbacks;

Supporting Types

DragLocation

interface DragLocation {
  readonly container: Container;
  readonly containerMetadata: Record<string, unknown>;
  readonly index: number;
}

container is the direct owner, containerMetadata is its read-only application metadata, and index is the zero-based position in that Container. DragSession uses this type for every entry in sources.

const source: DragLocation = session.sources[0];
console.log(source.container.name, source.index);

Methods

Deprecated handoff

/** @deprecated */
session.handoff(replacements: readonly Item[]): void;

Transfers a pending gesture to an already-mounted parallel run of replacement Items. It may only run synchronously during onDragStart. Replacements must be a unique, equally sized run of non-ghost Items attached to the same root and engine, and must not include the current participants. Invalid input or a call after the pending phase throws without changing the current participants.

This escape hatch predates the supported move-and-backfill copying recipe and has no recommended first-party use case. Do not add new integrations that use it; it may be removed in a future release.

Appendix

Obtaining and Retaining the Session

All events from one active gesture expose the same event.session object. Policy events created for programmatic work may instead have session: null. The root’s dragSession property returns the active handle during the gesture and null before startup and after cleanup; a nested Container’s property always returns null.

An ended handle remains readable, but it no longer describes an active operation. Prefer callback-local access unless the application specifically needs to correlate lifecycle events.

Lifecycle and Multi-Item Model

Returning false from onDragStart ends the pending session without activation or onDragEnd. An activated drag fires onDragEnd after temporary state is cleared, including cancellation and a return to the source.

For multi-item drags, items and sources stay parallel in document order. pressedItem anchors the gesture; primaryItem is the run head used by singular APIs. One pointer-preview can represent the whole run, while slot-preserving lifecycles may create one spacer anchor per represented Item.

For the full chronology, see Drag Start, Drags and Ghosts, and Commit and Cleanup.