SnapSort exports five pure, framework-neutral drop-priority functions from @snap-engine/snapsort/callbacks. Pass one to a destination Container’s getDropPriority callback or call it from an application callback.

import { DROP_REJECT_PRIORITY } from "@snap-engine/snapsort";
import {
  prioritizeIntersectingContainer,
  prioritizeNearestContainerEdge,
  prioritizePointerContainer,
  prioritizeTreeDepth,
  rejectDrop,
} from "@snap-engine/snapsort/callbacks";

Every helper receives a DropPriorityEvent and returns a priority without mutating SnapSort, application state, or the DOM.

DropPriorityEvent

interface DropPriorityEvent {
  session: DragSession | null;
  item: Item;
  itemId: ItemId;
  itemMetadata: ItemMetadata;
  items: Item[];
  itemIds: ItemId[];
  itemsMetadata: ItemMetadata[];
  source: DragLocation | null;
  sources: readonly (DragLocation | null)[];
  container: Container;
  containerMetadata: Record<string, unknown>;
  index: number;
  staticPriority: number;
  pointer: { x: number; y: number };
  dragRect: DropPriorityRect;
  containerRect: DropPriorityRect;
  containerContentRect: DropPriorityRect;
  depth: number;
}

session

Type: DragSession | null

The active drag handle. It is null when priority is evaluated without a pointer-driven drag, so narrow it before reading session state.

if (event.session?.dropEffect === "none") return DROP_REJECT_PRIORITY;

item

Type: Item

The primary Item being evaluated. For a multi-item drag it is the first Item in the ordered run and equals items[0].

const sourceItem = event.item;

itemId

Type: ItemId

The stable identity of item. Prefer it to object identity when comparing against application state.

if (blockedIds.has(event.itemId)) return DROP_REJECT_PRIORITY;

itemMetadata

Type: ItemMetadata

Read-only application metadata for item.

const kind = event.itemMetadata.kind;

items

Type: Item[]

The complete dragged run in stable order. A single-item drag has one entry.

if (event.items.length > 10) return DROP_REJECT_PRIORITY;

itemIds

Type: ItemId[]

Identities parallel to items: itemIds[i] identifies items[i].

const includesLockedItem = event.itemIds.some((id) => lockedIds.has(id));

itemsMetadata

Type: ItemMetadata[]

Application metadata parallel to items.

const allTasks = event.itemsMetadata.every((data) => data.kind === "task");

source

Type: DragLocation | null

The primary Item’s source location, or null when it has no source Container. It equals sources[0] when both are present.

const isSameContainer = event.source?.container === event.container;

sources

Type: readonly (DragLocation | null)[]

Source locations parallel to items. An entry can be null for an Item that does not currently belong to a Container.

const crossesContainers = event.sources.some(
  (source) => source && source.container !== event.container,
);

container

Type: Container

The candidate destination whose direct positions will receive the returned priority.

if (event.container.locked) return rejectDrop(event);

containerMetadata

Type: Record<string, unknown>

Read-only application metadata for the candidate destination.

const accepts = event.containerMetadata.accepts;

index

Type: number

The first candidate index generated for this Container. SnapSort calls policy once per candidate Container per resolution, so this value is representative only. Do not use it to allow or reject individual slots.

console.log("First candidate index", event.index);

staticPriority

Type: number

The Container’s configured dropPriority before getDropPriority overrides it. Return this value when an application condition does not apply but the configured priority should be preserved.

return matchesRule ? event.depth + 1 : event.staticPriority;

pointer

Type: { x: number; y: number }

Frozen world-space coordinates for the pointer used by this resolution.

const pointerY = event.pointer.y;

dragRect

Type: DropPriorityRect

The primary dragged Item’s virtual world-space rectangle. Its leading edge preserves the pointer-to-Item offset captured at drag start.

const leadingX = event.dragRect.x;

containerRect

Type: DropPriorityRect

The destination Container’s frozen world-space border box. All five standard callbacks use this rectangle when they need destination geometry.

const right = event.containerRect.x + event.containerRect.width;

containerContentRect

Type: DropPriorityRect

The destination Container’s frozen world-space content box. Standard helpers do not use it, but custom policies can use it when borders and padding should not count as the active region.

return pointInside(event.pointer, event.containerContentRect)
  ? 1
  : event.staticPriority;

depth

Type: number

The candidate Container’s zero-based depth within its independent SnapSort tree. prioritizeTreeDepth uses it to give deeper matching Containers larger scores.

const nestedPriority = event.depth + 1;

DropPriorityRect

Type: { x: number; y: number; width: number; height: number }

A frozen world-space rectangle captured for the current target resolution.

Priority Return Values

DROP_REJECT_PRIORITY

Type: -1

The one valid rejection result. A finite nonnegative number makes the Container eligible and ranks it; undefined preserves staticPriority. Other negative or nonfinite results throw.

return acceptsItem(event) ? 1 : DROP_REJECT_PRIORITY;

A callback result is an absolute override, not a value added to dropPriority. Returning a nonnegative number can therefore override a static -1; returning undefined preserves that static rejection.

Functions

rejectDrop

function rejectDrop(event: DropPriorityEvent): typeof DROP_REJECT_PRIORITY;

Always returns DROP_REJECT_PRIORITY (-1). Use it on a structural Container that may own nested destinations but must not directly accept Items.

const config = {
  callbacks: { getDropPriority: rejectDrop },
} satisfies ContainerConfig;

The rejection applies to positions owned directly by that Container. Nested Containers evaluate their own policies.

prioritizePointerContainer

function prioritizePointerContainer(
  event: DropPriorityEvent,
): number | undefined;

Returns 1 when event.pointer is inside or on the edge of event.containerRect; otherwise returns undefined, preserving the Container’s configured priority.

const config = {
  callbacks: { getDropPriority: prioritizePointerContainer },
} satisfies ContainerConfig;

Use it when the pointer itself should select among overlapping or differently sized destinations.

prioritizeIntersectingContainer

function prioritizeIntersectingContainer(
  event: DropPriorityEvent,
): number | undefined;

Returns 1 when event.dragRect intersects or touches event.containerRect; otherwise returns undefined. The virtual dragged rectangle preserves the initial grab offset.

const config = {
  callbacks: { getDropPriority: prioritizeIntersectingContainer },
} satisfies ContainerConfig;

Use it for bins, canvases, and drop zones that should be preferred when the dragged Item—not only the pointer—enters them.

prioritizeNearestContainerEdge

function prioritizeNearestContainerEdge(event: DropPriorityEvent): number;

Returns 1 / (1 + distance), where distance is the Euclidean distance from the pointer to the nearest point on containerRect. A pointer inside the box scores 1; scores approach 0 as distance increases.

const config = {
  callbacks: { getDropPriority: prioritizeNearestContainerEdge },
} satisfies ContainerConfig;

This helper always returns a number, so it replaces staticPriority for every candidate instead of preserving it outside the Container.

prioritizeTreeDepth

function prioritizeTreeDepth(event: DropPriorityEvent): number;

Builds this virtual probe for vertically rendered trees:

const virtualProbe = {
  x: event.dragRect.x,
  y: event.pointer.y,
};

Returns event.depth + 1 when the probe is inside containerRect; otherwise returns event.staticPriority. Using the virtual Item’s leading X edge preserves the original grab offset while pointer Y selects the current row.

const config = {
  callbacks: { getDropPriority: prioritizeTreeDepth },
} satisfies ContainerConfig;

This helper is intentionally vertical. A horizontal tree should use an application callback with the axes exchanged.

Composing Helpers

Standard callbacks are ordinary functions and can be combined with metadata or application geometry.

function prioritizeMatchingGroup(event: DropPriorityEvent) {
  const matches =
    event.source?.containerMetadata.group === event.containerMetadata.group;

  return matches ? prioritizeIntersectingContainer(event) : rejectDrop(event);
}

Appendix

Resolution Timing

During pointer-driven target resolution SnapSort:

  1. Generates positions and groups them by direct Container.
  2. Reads dropPriority and calls getDropPriority once per Container.
  3. Removes effective -1 results and keeps Containers tied at the highest nonnegative priority.
  4. Lets the active placement mode rank positions owned by those Containers.

Callbacks run synchronously and repeatedly as the pointer moves, so keep them deterministic and inexpensive. They choose Container eligibility and preference, not an individual slot. Programmatic moveItem calls bypass these policies.

For the overall target-resolution model, see Drags and Ghosts. See the Svelte Container or React Container reference for callback ownership and configuration.