The JavaScript Drag & Drop library your grandparents warned you about.
—
Shopify Draggable provides a comprehensive event system with drag events, specialized events for each draggable type, and plugin-specific events. All events extend AbstractEvent and support cancellation.
All events inherit from AbstractEvent providing common functionality.
/**
* Base class for all draggable events
*/
abstract class AbstractEvent<TData = {[key: string]: any}> {
readonly type: string;
readonly cancelable: boolean;
constructor(data: TData);
cancel(): void;
canceled(): boolean;
clone(data?: Partial<TData>): AbstractEvent<TData>;
}
export {AbstractEvent as BaseEvent};Usage Example:
draggable.on('drag:start', (event) => {
console.log('Event type:', event.type);
console.log('Can cancel:', event.cancelable);
// Cancel the event if needed
if (shouldCancelDrag()) {
event.cancel();
}
// Check if event was canceled
if (event.canceled()) {
console.log('Event was canceled');
}
});Base drag events that fire during drag operations for all draggable types.
class DragEvent extends AbstractEvent {
readonly source: HTMLElement;
readonly originalSource: HTMLElement;
readonly mirror: HTMLElement;
readonly sourceContainer: HTMLElement;
readonly sensorEvent: SensorEvent;
readonly originalEvent: Event;
}
class DragStartEvent extends DragEvent {}
class DragMoveEvent extends DragEvent {}
class DragStopEvent extends DragEvent {}
class DragStoppedEvent extends DragEvent {}
class DragOverEvent extends DragEvent {
readonly overContainer: HTMLElement;
readonly over: HTMLElement;
}
class DragOutEvent extends DragEvent {
readonly overContainer: HTMLElement;
readonly over: HTMLElement;
}
class DragOverContainerEvent extends DragEvent {
readonly overContainer: HTMLElement;
}
class DragOutContainerEvent extends DragEvent {
readonly overContainer: HTMLElement;
}
class DragPressureEvent extends DragEvent {
readonly pressure: number;
}Event Names:
type DraggableEventNames =
| 'draggable:initialize'
| 'draggable:destroy'
| 'drag:start'
| 'drag:move'
| 'drag:over'
| 'drag:over:container'
| 'drag:out'
| 'drag:out:container'
| 'drag:stop'
| 'drag:stopped'
| 'drag:pressure'
| MirrorEventNames;Usage Example:
draggable.on('drag:start', (event) => {
console.log('Started dragging:', event.source);
console.log('Original element:', event.originalSource);
console.log('From container:', event.sourceContainer);
});
draggable.on('drag:over', (event) => {
console.log('Dragging over:', event.over);
console.log('In container:', event.overContainer);
});
draggable.on('drag:pressure', (event) => {
console.log('Pressure level:', event.pressure);
// Handle force touch or pressure-sensitive input
if (event.pressure > 0.8) {
showContextMenu(event.source);
}
});Events specific to Draggable instance lifecycle.
class DraggableEvent extends AbstractEvent {
readonly draggable: Draggable;
}
class DraggableInitializedEvent extends DraggableEvent {}
class DraggableDestroyEvent extends DraggableEvent {}Usage Example:
draggable.on('draggable:initialize', (event) => {
console.log('Draggable initialized:', event.draggable);
setupDragCallbacks(event.draggable);
});
draggable.on('draggable:destroy', (event) => {
console.log('Draggable being destroyed');
cleanupDragCallbacks();
});Events specific to sortable operations.
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;
}Event Names:
type SortableEventNames =
| 'sortable:start'
| 'sortable:sort'
| 'sortable:sorted'
| 'sortable:stop'
| DraggableEventNames;Events specific to droppable operations.
class DroppableEvent extends AbstractEvent {
readonly dragEvent: DragEvent;
}
class DroppableStartEvent extends DroppableEvent {
dropzone: HTMLElement;
}
class DroppableDroppedEvent extends DroppableEvent {
dropzone: HTMLElement;
}
class DroppableReturnedEvent extends DroppableEvent {
dropzone: HTMLElement;
}
class DroppableStopEvent extends DroppableEvent {
dropzone: HTMLElement;
}Event Names:
type DroppableEventNames =
| 'droppable:start'
| 'droppable:dropped'
| 'droppable:returned'
| 'droppable:stop'
| DraggableEventNames;Events specific to swappable operations.
class SwappableEvent extends AbstractEvent {
readonly dragEvent: DragEvent;
}
class SwappableStartEvent extends SwappableEvent {}
class SwappableSwapEvent extends SwappableEvent {
readonly over: HTMLElement;
readonly overContainer: HTMLElement;
}
class SwappableSwappedEvent extends SwappableEvent {
readonly swappedElement: HTMLElement;
}
class SwappableStopEvent extends SwappableEvent {}Event Names:
type SwappableEventNames =
| 'swappable:start'
| 'swappable:swap'
| 'swappable:swapped'
| 'swappable:stop'
| DraggableEventNames;Events related to the mirror plugin that creates drag representations.
class MirrorEvent extends AbstractEvent {
readonly source: HTMLElement;
readonly originalSource: HTMLElement;
readonly sourceContainer: HTMLElement;
readonly sensorEvent: SensorEvent;
readonly originalEvent: Event;
}
class MirrorCreateEvent extends MirrorEvent {}
class MirrorCreatedEvent extends MirrorEvent {
readonly mirror: HTMLElement;
}
class MirrorAttachedEvent extends MirrorEvent {
readonly mirror: HTMLElement;
}
class MirrorMoveEvent extends MirrorEvent {
readonly mirror: HTMLElement;
readonly passedThreshX: boolean;
readonly passedThreshY: boolean;
}
class MirrorMovedEvent extends MirrorEvent {
readonly mirror: HTMLElement;
readonly passedThreshX: boolean;
readonly passedThreshY: boolean;
}
class MirrorDestroyEvent extends MirrorEvent {
readonly mirror: HTMLElement;
}Event Names:
type MirrorEventNames =
| 'mirror:create'
| 'mirror:created'
| 'mirror:attached'
| 'mirror:move'
| 'mirror:moved'
| 'mirror:destroy';Events from additional plugins.
// Collidable Plugin Events
class CollidableEvent extends AbstractEvent {
readonly dragEvent: DragEvent;
readonly collidingElement: HTMLElement;
}
class CollidableInEvent extends CollidableEvent {}
class CollidableOutEvent extends CollidableEvent {}
type CollidableEventNames = 'collidable:in' | 'collidable:out';
// Snappable Plugin Events
class SnapEvent extends AbstractEvent {
readonly dragEvent: DragEvent;
readonly snappable: HTMLElement;
}
class SnapInEvent extends SnapEvent {}
class SnapOutEvent extends SnapEvent {}
type SnappableEventNames = 'snap:in' | 'snap:out';TypeScript utility type for getting specific event types from event names.
type GetEventByEventName<TEventName> =
TEventName extends 'draggable:initialize'
? DraggableInitializedEvent
: TEventName extends 'drag:start'
? DragStartEvent
: TEventName extends 'sortable:sorted'
? SortableSortedEvent
: TEventName extends 'droppable:dropped'
? DroppableDroppedEvent
: TEventName extends 'swappable:swapped'
? SwappableSwappedEvent
: AbstractEvent;Usage Example:
// TypeScript will correctly infer the event type
draggable.on('drag:start', (event) => {
// event is correctly typed as DragStartEvent
console.log(event.source, event.originalSource);
});
sortable.on('sortable:sorted', (event) => {
// event is correctly typed as SortableSortedEvent
console.log(event.oldIndex, event.newIndex);
});import { Draggable } from "@shopify/draggable";
const draggable = new Draggable(containers, options);
// Set up comprehensive event logging
const events = [
'draggable:initialize',
'drag:start',
'drag:move',
'drag:over',
'drag:out',
'drag:stop',
'drag:stopped',
'mirror:create',
'mirror:created',
'mirror:move',
'mirror:destroy'
];
events.forEach(eventName => {
draggable.on(eventName, (event) => {
console.log(`Event: ${eventName}`, {
type: event.type,
cancelable: event.cancelable,
canceled: event.canceled(),
timestamp: Date.now()
});
});
});
// Handle cancellation scenarios
draggable.on('drag:start', (event) => {
// Cancel drag if element is disabled
if (event.source.hasAttribute('disabled')) {
event.cancel();
showMessage('This item cannot be moved');
}
});Install with Tessl CLI
npx tessl i tessl/npm-shopify--draggable