CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-shopify--draggable

The JavaScript Drag & Drop library your grandparents warned you about.

Pending
Overview
Eval results
Files

sortable.mddocs/

Sortable Lists

Sortable extends Draggable to provide reordering functionality for elements within or between containers. It automatically tracks position changes and provides events for handling sort operations.

Capabilities

Sortable Constructor

Creates a sortable instance with the same interface as Draggable but with additional sorting-specific functionality.

/**
 * Creates a new sortable instance for reordering elements
 * @param containers - Elements that contain sortable items
 * @param options - Configuration options (same as Draggable)
 */
class Sortable<T = SortableEventNames> extends Draggable<T> {
  constructor(containers: DraggableContainer, options?: DraggableOptions);
}

Usage Example:

import { Sortable } from "@shopify/draggable";

const sortable = new Sortable(document.querySelectorAll('.sortable-list'), {
  draggable: '.sortable-item'
});

// Listen for sort events
sortable.on('sortable:sorted', (event) => {
  console.log(`Moved from index ${event.oldIndex} to ${event.newIndex}`);
  console.log('Old container:', event.oldContainer);
  console.log('New container:', event.newContainer);
});

Index Management

Methods for getting element positions within containers.

/**
 * Returns the current index of an element within its container during drag
 * @param element - Element to get index for
 * @returns Zero-based index of the element
 */
index(element: HTMLElement): number;

/**
 * Returns sortable elements for a specific container, excluding mirror and original source
 * @param container - Container to get elements from
 * @returns Array of sortable elements
 */
getSortableElementsForContainer(container: HTMLElement): HTMLElement[];

Usage Example:

sortable.on('sortable:sort', (event) => {
  const currentIndex = sortable.index(event.dragEvent.source);
  const allItems = sortable.getSortableElementsForContainer(event.dragEvent.sourceContainer);
  console.log(`Item ${currentIndex + 1} of ${allItems.length}`);
});

Sort Events

Sortable-specific events that fire during sort operations.

type SortableEventNames =
  | 'sortable:start'
  | 'sortable:sort'
  | 'sortable:sorted'
  | 'sortable:stop'
  | DraggableEventNames;

Event Details:

  • sortable:start: Fired when a sortable drag operation begins
  • sortable:sort: Fired continuously while sorting (before position changes)
  • sortable:sorted: Fired after an element has been moved to a new position
  • sortable:stop: Fired when the sort operation ends

Event Handlers Example:

sortable.on('sortable:start', (event) => {
  console.log('Sort started');
  console.log('Start index:', event.startIndex);
  console.log('Start container:', event.startContainer);
});

sortable.on('sortable:sort', (event) => {
  // This fires before the move happens - you can cancel it
  if (shouldPreventSort(event.dragEvent.over)) {
    event.cancel();
  }
});

sortable.on('sortable:sorted', (event) => {
  // This fires after the element has been moved
  updateDataModel(event.oldIndex, event.newIndex, event.oldContainer, event.newContainer);
});

sortable.on('sortable:stop', (event) => {
  console.log('Final position - Old:', event.oldIndex, 'New:', event.newIndex);
  saveChangesToServer();
});

Event Types

class SortableEvent extends AbstractEvent {
  readonly dragEvent: DragEvent;
}

class SortableStartEvent extends SortableEvent {
  readonly startIndex: number;
  readonly startContainer: HTMLElement;
}

class SortableSortEvent extends SortableEvent {
  readonly oldIndex: number;
  readonly newIndex: number;
  readonly oldContainer: HTMLElement;
  readonly newContainer: HTMLElement;
}

class SortableSortedEvent extends SortableEvent {
  readonly oldIndex: number;
  readonly newIndex: number;
  readonly oldContainer: HTMLElement;
  readonly newContainer: HTMLElement;
}

class SortableStopEvent extends SortableEvent {
  readonly oldIndex: number;
  readonly newIndex: number;
  readonly oldContainer: HTMLElement;
  readonly newContainer: HTMLElement;
}

Complete Example

import { Sortable } from "@shopify/draggable";

// Create sortable for todo lists
const todoSortable = new Sortable(document.querySelectorAll('.todo-list'), {
  draggable: '.todo-item',
  handle: '.todo-handle',
  classes: {
    'source:dragging': 'todo-dragging',
    'container:over': 'todo-drop-zone'
  }
});

// Track changes for persistence
let pendingChanges = [];

todoSortable.on('sortable:sorted', (event) => {
  const change = {
    itemId: event.dragEvent.source.dataset.id,
    oldIndex: event.oldIndex,
    newIndex: event.newIndex,
    oldListId: event.oldContainer.dataset.listId,
    newListId: event.newContainer.dataset.listId
  };
  
  pendingChanges.push(change);
  
  // Show visual feedback
  event.dragEvent.source.classList.add('recently-moved');
  setTimeout(() => {
    event.dragEvent.source.classList.remove('recently-moved');
  }, 1000);
});

todoSortable.on('sortable:stop', () => {
  // Batch save all changes
  if (pendingChanges.length > 0) {
    saveTodoChanges(pendingChanges);
    pendingChanges = [];
  }
});

// Cleanup
function destroyTodoSortable() {
  todoSortable.destroy();
}

Multi-Container Sorting

Sortable automatically handles sorting between different containers:

const kanbanSortable = new Sortable([
  document.querySelector('.backlog'),
  document.querySelector('.in-progress'), 
  document.querySelector('.done')
], {
  draggable: '.kanban-card'
});

kanbanSortable.on('sortable:sorted', (event) => {
  const card = event.dragEvent.source;
  const newStatus = event.newContainer.dataset.status;
  const oldStatus = event.oldContainer.dataset.status;
  
  if (newStatus !== oldStatus) {
    // Update card status when moved between columns
    updateCardStatus(card.dataset.id, newStatus);
    card.querySelector('.status').textContent = newStatus;
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-shopify--draggable

docs

core-draggable.md

droppable.md

events.md

index.md

plugins.md

sensors.md

sortable.md

swappable.md

tile.json