0
# Sortable Lists
1
2
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.
3
4
## Capabilities
5
6
### Sortable Constructor
7
8
Creates a sortable instance with the same interface as Draggable but with additional sorting-specific functionality.
9
10
```typescript { .api }
11
/**
12
* Creates a new sortable instance for reordering elements
13
* @param containers - Elements that contain sortable items
14
* @param options - Configuration options (same as Draggable)
15
*/
16
class Sortable<T = SortableEventNames> extends Draggable<T> {
17
constructor(containers: DraggableContainer, options?: DraggableOptions);
18
}
19
```
20
21
**Usage Example:**
22
23
```typescript
24
import { Sortable } from "@shopify/draggable";
25
26
const sortable = new Sortable(document.querySelectorAll('.sortable-list'), {
27
draggable: '.sortable-item'
28
});
29
30
// Listen for sort events
31
sortable.on('sortable:sorted', (event) => {
32
console.log(`Moved from index ${event.oldIndex} to ${event.newIndex}`);
33
console.log('Old container:', event.oldContainer);
34
console.log('New container:', event.newContainer);
35
});
36
```
37
38
### Index Management
39
40
Methods for getting element positions within containers.
41
42
```typescript { .api }
43
/**
44
* Returns the current index of an element within its container during drag
45
* @param element - Element to get index for
46
* @returns Zero-based index of the element
47
*/
48
index(element: HTMLElement): number;
49
50
/**
51
* Returns sortable elements for a specific container, excluding mirror and original source
52
* @param container - Container to get elements from
53
* @returns Array of sortable elements
54
*/
55
getSortableElementsForContainer(container: HTMLElement): HTMLElement[];
56
```
57
58
**Usage Example:**
59
60
```typescript
61
sortable.on('sortable:sort', (event) => {
62
const currentIndex = sortable.index(event.dragEvent.source);
63
const allItems = sortable.getSortableElementsForContainer(event.dragEvent.sourceContainer);
64
console.log(`Item ${currentIndex + 1} of ${allItems.length}`);
65
});
66
```
67
68
### Sort Events
69
70
Sortable-specific events that fire during sort operations.
71
72
```typescript { .api }
73
type SortableEventNames =
74
| 'sortable:start'
75
| 'sortable:sort'
76
| 'sortable:sorted'
77
| 'sortable:stop'
78
| DraggableEventNames;
79
```
80
81
**Event Details:**
82
83
- **sortable:start**: Fired when a sortable drag operation begins
84
- **sortable:sort**: Fired continuously while sorting (before position changes)
85
- **sortable:sorted**: Fired after an element has been moved to a new position
86
- **sortable:stop**: Fired when the sort operation ends
87
88
**Event Handlers Example:**
89
90
```typescript
91
sortable.on('sortable:start', (event) => {
92
console.log('Sort started');
93
console.log('Start index:', event.startIndex);
94
console.log('Start container:', event.startContainer);
95
});
96
97
sortable.on('sortable:sort', (event) => {
98
// This fires before the move happens - you can cancel it
99
if (shouldPreventSort(event.dragEvent.over)) {
100
event.cancel();
101
}
102
});
103
104
sortable.on('sortable:sorted', (event) => {
105
// This fires after the element has been moved
106
updateDataModel(event.oldIndex, event.newIndex, event.oldContainer, event.newContainer);
107
});
108
109
sortable.on('sortable:stop', (event) => {
110
console.log('Final position - Old:', event.oldIndex, 'New:', event.newIndex);
111
saveChangesToServer();
112
});
113
```
114
115
## Event Types
116
117
```typescript { .api }
118
class SortableEvent extends AbstractEvent {
119
readonly dragEvent: DragEvent;
120
}
121
122
class SortableStartEvent extends SortableEvent {
123
readonly startIndex: number;
124
readonly startContainer: HTMLElement;
125
}
126
127
class SortableSortEvent extends SortableEvent {
128
readonly oldIndex: number;
129
readonly newIndex: number;
130
readonly oldContainer: HTMLElement;
131
readonly newContainer: HTMLElement;
132
}
133
134
class SortableSortedEvent extends SortableEvent {
135
readonly oldIndex: number;
136
readonly newIndex: number;
137
readonly oldContainer: HTMLElement;
138
readonly newContainer: HTMLElement;
139
}
140
141
class SortableStopEvent extends SortableEvent {
142
readonly oldIndex: number;
143
readonly newIndex: number;
144
readonly oldContainer: HTMLElement;
145
readonly newContainer: HTMLElement;
146
}
147
```
148
149
## Complete Example
150
151
```typescript
152
import { Sortable } from "@shopify/draggable";
153
154
// Create sortable for todo lists
155
const todoSortable = new Sortable(document.querySelectorAll('.todo-list'), {
156
draggable: '.todo-item',
157
handle: '.todo-handle',
158
classes: {
159
'source:dragging': 'todo-dragging',
160
'container:over': 'todo-drop-zone'
161
}
162
});
163
164
// Track changes for persistence
165
let pendingChanges = [];
166
167
todoSortable.on('sortable:sorted', (event) => {
168
const change = {
169
itemId: event.dragEvent.source.dataset.id,
170
oldIndex: event.oldIndex,
171
newIndex: event.newIndex,
172
oldListId: event.oldContainer.dataset.listId,
173
newListId: event.newContainer.dataset.listId
174
};
175
176
pendingChanges.push(change);
177
178
// Show visual feedback
179
event.dragEvent.source.classList.add('recently-moved');
180
setTimeout(() => {
181
event.dragEvent.source.classList.remove('recently-moved');
182
}, 1000);
183
});
184
185
todoSortable.on('sortable:stop', () => {
186
// Batch save all changes
187
if (pendingChanges.length > 0) {
188
saveTodoChanges(pendingChanges);
189
pendingChanges = [];
190
}
191
});
192
193
// Cleanup
194
function destroyTodoSortable() {
195
todoSortable.destroy();
196
}
197
```
198
199
## Multi-Container Sorting
200
201
Sortable automatically handles sorting between different containers:
202
203
```typescript
204
const kanbanSortable = new Sortable([
205
document.querySelector('.backlog'),
206
document.querySelector('.in-progress'),
207
document.querySelector('.done')
208
], {
209
draggable: '.kanban-card'
210
});
211
212
kanbanSortable.on('sortable:sorted', (event) => {
213
const card = event.dragEvent.source;
214
const newStatus = event.newContainer.dataset.status;
215
const oldStatus = event.oldContainer.dataset.status;
216
217
if (newStatus !== oldStatus) {
218
// Update card status when moved between columns
219
updateCardStatus(card.dataset.id, newStatus);
220
card.querySelector('.status').textContent = newStatus;
221
}
222
});
223
```