JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices
—
Collection of DOM manipulation and helper functions for advanced integrations and custom implementations.
Utilities for attaching and removing event listeners with cross-browser compatibility.
/**
* Add event listener to element
* @param el - Target element
* @param event - Event name
* @param fn - Event handler function
*/
Sortable.utils.on(el: HTMLElement, event: string, fn: Function): void;
/**
* Remove event listener from element
* @param el - Target element
* @param event - Event name
* @param fn - Event handler function
*/
Sortable.utils.off(el: HTMLElement, event: string, fn: Function): void;Usage Examples:
const { on, off } = Sortable.utils;
function handleClick(evt) {
console.log('Element clicked:', evt.target);
}
// Add event listener
on(document.getElementById('myButton'), 'click', handleClick);
// Remove event listener
off(document.getElementById('myButton'), 'click', handleClick);
// Handle multiple events
const events = ['mousedown', 'touchstart'];
events.forEach(event => on(element, event, handleStart));Utilities for getting and setting CSS properties and classes.
/**
* Get or set CSS property on element
* @param el - Target element
* @param prop - CSS property name
* @param value - CSS property value (omit to get current value)
* @returns Current property value when getting
*/
Sortable.utils.css(el: HTMLElement, prop: string, value?: string): string | void;
/**
* Toggle CSS class on element
* @param el - Target element
* @param name - Class name
* @param state - Force state (true=add, false=remove, undefined=toggle)
*/
Sortable.utils.toggleClass(el: HTMLElement, name: string, state?: boolean): void;Usage Examples:
const { css, toggleClass } = Sortable.utils;
// Get CSS property
const width = css(element, 'width'); // '200px'
// Set CSS property
css(element, 'background-color', 'red');
css(element, 'transform', 'translateX(100px)');
// Toggle class
toggleClass(element, 'active'); // Toggle
toggleClass(element, 'selected', true); // Force add
toggleClass(element, 'hidden', false); // Force removeUtilities for finding and querying DOM elements.
/**
* Find elements within a context
* @param ctx - Context element to search within
* @param tagName - Tag name to search for
* @param iterator - Optional iterator function for filtering
* @returns Array of matching elements
*/
Sortable.utils.find(ctx: HTMLElement, tagName: string, iterator?: Function): HTMLElement[];
/**
* Check if element matches selector
* @param el - Element to test
* @param selector - CSS selector
* @returns Whether element matches selector
*/
Sortable.utils.is(el: HTMLElement, selector: string): boolean;
/**
* Find closest ancestor matching selector
* @param el - Starting element
* @param selector - CSS selector
* @param ctx - Context element (search boundary)
* @returns Closest matching ancestor or null
*/
Sortable.utils.closest(el: HTMLElement, selector: string, ctx?: HTMLElement): HTMLElement | null;Usage Examples:
const { find, is, closest } = Sortable.utils;
// Find all divs within container
const divs = find(container, 'div');
// Find draggable items with custom filter
const draggableItems = find(container, '*', (el) => {
return !el.classList.contains('no-drag');
});
// Check if element matches selector
if (is(element, '.sortable-item')) {
console.log('Element is sortable item');
}
// Find closest sortable container
const sortableContainer = closest(draggedElement, '.sortable-list');Utilities for cloning, positioning, and measuring elements.
/**
* Clone an element
* @param el - Element to clone
* @returns Cloned element
*/
Sortable.utils.clone(el: HTMLElement): HTMLElement;
/**
* Get element index within parent
* @param el - Target element
* @param selector - Optional selector to filter siblings
* @returns Element index
*/
Sortable.utils.index(el: HTMLElement, selector?: string): number;
/**
* Get child element at specific index
* @param el - Parent element
* @param childNum - Child index
* @param options - Options object with draggable selector
* @returns Child element at index
*/
Sortable.utils.getChild(el: HTMLElement, childNum: number, options?: { draggable?: string }): HTMLElement;Usage Examples:
const { clone, index, getChild } = Sortable.utils;
// Clone element
const clonedElement = clone(originalElement);
document.body.appendChild(clonedElement);
// Get element index
const elementIndex = index(element); // Position among all siblings
const draggableIndex = index(element, '.draggable'); // Position among draggable siblings
// Get specific child
const firstChild = getChild(container, 0);
const firstDraggable = getChild(container, 0, { draggable: '.sortable-item' });Utilities for object manipulation and function optimization.
/**
* Extend target object with source objects
* @param dst - Target object
* @param src - Source objects
* @returns Extended target object
*/
Sortable.utils.extend(dst: object, ...src: object[]): object;
/**
* Throttle function calls
* @param fn - Function to throttle
* @param ms - Throttle interval in milliseconds
* @returns Throttled function
*/
Sortable.utils.throttle(fn: Function, ms: number): Function;Usage Examples:
const { extend, throttle } = Sortable.utils;
// Extend objects
const config = extend({}, defaultOptions, userOptions);
// Throttle expensive operations
const expensiveOperation = throttle(() => {
console.log('This only runs once every 100ms');
updateComplexUI();
}, 100);
// Call frequently, but execution is throttled
window.addEventListener('scroll', expensiveOperation);Utilities for scheduling and managing asynchronous operations.
/**
* Schedule function for next tick
* @param fn - Function to schedule
* @returns Ticket ID for cancellation
*/
Sortable.utils.nextTick(fn: Function): number;
/**
* Cancel scheduled function
* @param id - Ticket ID from nextTick
*/
Sortable.utils.cancelNextTick(id: number): void;Usage Examples:
const { nextTick, cancelNextTick } = Sortable.utils;
// Schedule for next tick
const ticketId = nextTick(() => {
console.log('This runs on next tick');
updateUI();
});
// Cancel if needed
if (shouldCancel) {
cancelNextTick(ticketId);
}
// Common pattern for DOM updates
nextTick(() => {
// DOM changes are batched and applied efficiently
element.style.transform = 'translateX(100px)';
element.classList.add('moved');
});Utility for detecting sort direction in containers.
/**
* Detect sort direction of container
* @param el - Container element
* @returns Direction string ('horizontal' or 'vertical')
*/
Sortable.utils.detectDirection(el: HTMLElement): string;Usage Examples:
const { detectDirection } = Sortable.utils;
// Detect container direction
const direction = detectDirection(container);
console.log('Container direction:', direction); // 'horizontal' or 'vertical'
// Use in custom logic
if (detectDirection(container) === 'horizontal') {
setupHorizontalScrolling();
} else {
setupVerticalScrolling();
}Utility for accessing Sortable's internal property key.
/**
* Internal property key used to store Sortable instances on elements
*/
Sortable.utils.expando: string;Usage Examples:
const { expando } = Sortable.utils;
// Access sortable instance directly (advanced usage)
const sortableInstance = element[expando];
if (sortableInstance) {
console.log('Element has sortable instance:', sortableInstance);
}interface SortableUtils {
// Event management
on(el: HTMLElement, event: string, fn: Function): void;
off(el: HTMLElement, event: string, fn: Function): void;
// CSS manipulation
css(el: HTMLElement, prop: string, value?: string): string | void;
toggleClass(el: HTMLElement, name: string, state?: boolean): void;
// Element searching
find(ctx: HTMLElement, tagName: string, iterator?: Function): HTMLElement[];
is(el: HTMLElement, selector: string): boolean;
closest(el: HTMLElement, selector: string, ctx?: HTMLElement): HTMLElement | null;
// Element manipulation
clone(el: HTMLElement): HTMLElement;
index(el: HTMLElement, selector?: string): number;
getChild(el: HTMLElement, childNum: number, options?: { draggable?: string }): HTMLElement;
// Object and function utilities
extend(dst: object, ...src: object[]): object;
throttle(fn: Function, ms: number): Function;
// Async utilities
nextTick(fn: Function): number;
cancelNextTick(id: number): void;
// Direction detection
detectDirection(el: HTMLElement): string;
// Internal property access
expando: string;
}Using utilities to build custom drag-and-drop functionality:
const { on, off, css, closest, throttle } = Sortable.utils;
class CustomDragDrop {
constructor(element) {
this.element = element;
this.items = [];
this.init();
}
init() {
on(this.element, 'mousedown', this.handleMouseDown.bind(this));
this.handleMouseMove = throttle(this.handleMouseMove.bind(this), 16);
}
handleMouseDown(evt) {
const item = closest(evt.target, '.draggable-item', this.element);
if (!item) return;
this.draggedItem = item;
css(item, 'opacity', '0.5');
on(document, 'mousemove', this.handleMouseMove);
on(document, 'mouseup', this.handleMouseUp.bind(this));
}
handleMouseMove(evt) {
if (!this.draggedItem) return;
css(this.draggedItem, 'transform', `translate(${evt.clientX}px, ${evt.clientY}px)`);
}
handleMouseUp() {
if (this.draggedItem) {
css(this.draggedItem, 'opacity', '');
css(this.draggedItem, 'transform', '');
this.draggedItem = null;
}
off(document, 'mousemove', this.handleMouseMove);
off(document, 'mouseup', this.handleMouseUp);
}
}Using utilities to integrate with UI frameworks:
const { on, off, nextTick } = Sortable.utils;
class ReactSortableHelper {
static attachToRef(ref, options, onOrderChange) {
if (!ref.current) return null;
const sortable = Sortable.create(ref.current, {
...options,
onEnd: (evt) => {
nextTick(() => {
// Ensure React state updates after DOM changes
const newOrder = sortable.toArray();
onOrderChange(newOrder);
});
options.onEnd && options.onEnd(evt);
}
});
return sortable;
}
static detachFromRef(sortable) {
if (sortable) {
sortable.destroy();
}
}
}Install with Tessl CLI
npx tessl i tessl/npm-sortablejs