Container

Svelte Container component props, configuration, callbacks, and imperative API.

Container wraps a core SnapSort container and provides it to descendant Item and nested Container components through Svelte context.

Svelte owns the rendered collection. Supply a synchronous state mutation callback for every persistent operation the container accepts; the adapter does not fall back to SnapSort’s Vanilla DOM callbacks.

<script lang="ts">
  import { Container, Item } from "@snap-engine/snapsort-svelte";
  import type { ItemMoveEvent } from "@snap-engine/snapsort";

  let tasks = $state([
    { id: "design", label: "Design" },
    { id: "build", label: "Build" },
  ]);

  function onItemMove(event: ItemMoveEvent) {
    const task = tasks.find((entry) => entry.id === event.itemId);
    if (!task) return;
    const next = tasks.filter((entry) => entry.id !== event.itemId);
    next.splice(Math.max(0, Math.min(event.to.index, next.length)), 0, task);
    tasks = next;
  }
</script>

<Container
  config={{ direction: "column", groupID: "tasks", callbacks: { onItemMove } }}
  items={tasks}
  getItemId={(task) => task.id}
>
  {#snippet entry(task)}
    <Item itemId={task.id}>{task.label}</Item>
  {/snippet}
</Container>

Each entry must render exactly one Item or nested Container whose itemId matches getItemId(entry).

Props

type ContainerProps<T> = {
  config: ContainerConfig;
  items?: T[];
  getItemId?: (entry: T) => string;
  entry: Snippet<[T]>;
  ghost?: Snippet<[GhostInsertEvent]>;
  before?: Snippet;
  after?: Snippet;
  itemId?: string;
  container?: Container;
  locked?: boolean;
  selected?: boolean;
  className?: string;
  metadata?: Record<string, unknown>;
};
PropDefaultDescription
configRequiredSnapSort container configuration.
items[]Data entries for this container. Omit for containers that only receive drops or hold static children.
getItemId(entry) => entry.idStable ID for each entry.
entryRequiredRenders one Item or nested Container for each entry.
ghostDefault spacerRenders flow-mode target ghosts. Custom snippets should render <Ghost {event}>...</Ghost>.
beforeundefinedNon-sortable content rendered before entries.
afterundefinedNon-sortable content rendered after entries.
itemIdundefinedRequired when this container is rendered as a sortable entry inside another container.
containerBindableReceives the created core container object.
lockedtrueKeeps the container itself from being dragged when nested. Set to false for draggable nested containers.
selectedfalseConsumer-owned selection flag for multi-item drags. Only meaningful when locked is false.
className""Extra class string applied to the rendered element.
metadata{}Metadata assigned to the container object and exposed in callback events.

Container renders a div with snapsort-container plus a mode-specific class.

Config

OptionDefaultDescription
mode"euclidean"Drag/drop mode for this tree: "euclidean", "progressive", "insertion", or "swap".
strategyResolved from modeAdvanced custom { dropTarget, lifecycle } strategy pair that overrides mode.
direction"column"Main layout direction: "column" or "row".
groupID"default-group"Containers with the same group can exchange items.
mainAxisAlign"start"Main-axis alignment for virtual placement. Supports "start" and "center".
nameGeneratedHuman-readable container name.
animationDefault 100ms animationsReorder, drop, and click-move animation settings. Set to null to disable configured animations.
disableFlipfalseDisables FLIP movement animation.
noDropfalsePrevents the container from being a drop target. Useful for root layout containers.
dropAreafalseTreats the container as an explicit drop area for collision filtering.
callbacksNo persistent mutation defaultRequired framework-state callbacks plus lifecycle and validation hooks.

Mode is resolved from the root container when a drag starts, so nested containers in one drag tree should use the same mode.

Callbacks

CallbackWhen it runs
onItemMove(event)An item moves to a new container or index. Preferred for state-backed lists because it includes both from and to.
onItemSwap(event)Swap mode commits a pairwise slot exchange. Required for swap mode in the Svelte adapter.
onItemInsert(event)An item is inserted into a container; this is the lower-level fallback for onItemMove.
onItemRemove(event)An item is removed through removeItem(id).
onDragStart(event)A drag is starting. Return false to veto it.
onDragEnd(event)A drag has ended and its mutation has committed.
onDropTargetChange(event)The prospective drop container or index changed. Fires on the root container.
canDrop(event)Resolves whether this container accepts the current drag. Keep it cheap.
onGhostInsert(event)Optional notification after the Svelte adapter inserts or moves its framework-owned ghost entry.
onGhostRemove(event)Optional notification after the Svelte adapter removes its framework-owned ghost entry.
createGhost(event)Core/Vanilla hook. Unsupported by the Svelte adapter; customize framework-owned ghosts with the ghost snippet.
flushMutation(mutation)Runs a structural callback inside the adapter’s synchronous Svelte commit. Supplied automatically.
awaitMutation()Deprecated compatibility callback.

Update Svelte arrays synchronously in onItemMove or onItemSwap. The adapter uses flushSync, allowing SnapSort to read final DOM geometry before paint. Missing callbacks fail clearly instead of mutating framework-rendered DOM.

Bindable Core Object

Use bind:container when buttons, keyboard handlers, or other controls need the core container API.

<script lang="ts">
  import { Container, Item } from "@snap-engine/snapsort-svelte";
  import type { Container as SnapSortContainer, ItemMoveEvent } from "@snap-engine/snapsort";

  let source: SnapSortContainer | undefined = $state();
  let target: SnapSortContainer | undefined = $state();
  let sourceCards = $state([{ id: "card-1", label: "Card 1" }]);
  let targetCards = $state<{ id: string; label: string }[]>([]);

  function onItemMove(event: ItemMoveEvent) {
    const card = [...sourceCards, ...targetCards].find((entry) => entry.id === event.itemId);
    if (!card) return;
    sourceCards = sourceCards.filter((entry) => entry.id !== event.itemId);
    targetCards = targetCards.filter((entry) => entry.id !== event.itemId);
    const targetList = event.to.container === target ? targetCards : sourceCards;
    targetList.splice(Math.max(0, Math.min(event.to.index, targetList.length)), 0, card);
    if (event.to.container === target) targetCards = targetList;
    else sourceCards = targetList;
  }

  function moveToTarget(id: string) {
    if (!source || !target) return;
    source.moveItem(id, target, target.numberOfItems);
  }
</script>

<Container
  bind:container={source}
  config={{ direction: "column", groupID: "cards", callbacks: { onItemMove } }}
  items={sourceCards}
  getItemId={(card) => card.id}
>
  {#snippet entry(card)}
    <Item itemId={card.id}>
      <button type="button" onclick={() => moveToTarget(card.id)}>Move card</button>
    </Item>
  {/snippet}
</Container>

<Container
  bind:container={target}
  config={{ direction: "column", groupID: "cards", callbacks: { onItemMove } }}
  items={targetCards}
  getItemId={(card) => card.id}
>
  {#snippet entry(card)}
    <Item itemId={card.id}>{card.label}</Item>
  {/snippet}
</Container>
APIDescription
container.moveItem(id, targetContainer, index)Moves an item into another container or index.
container.removeItem(id)Removes an item by itemId or internal object ID.
container.numberOfItemsNumber of tracked child items.
container.groupIDConfigured group ID.
container.directionGets or sets "column" or "row".
container.mainAxisAlignGets or sets "start" or "center".
container.dropAreaGets or sets explicit drop-area behavior.
container.modeGets or sets the drag/drop mode.
container.configLive ContainerConfig object.
container.callbacksConfigured callback object.
container.dragSessionActive DragSession, or null.

Custom Ghost

Use a ghost snippet with Ghost to customize every framework-owned ghost kind: flow spacers, insertion markers, and swap pointers.

<Container config={{ mode: "progressive", direction: "row", callbacks: { onItemMove } }} items={tiles}>
  {#snippet entry(tile)}
    <Item itemId={tile.id}>{tile.text}</Item>
  {/snippet}

  {#snippet ghost(event)}
    <Ghost {event}>
      <span class="tile-ghost">{event.originalItemId}</span>
    </Ghost>
  {/snippet}
</Container>