0
# Plugins
1
2
Plugins extend draggable functionality through an extensible architecture. Shopify Draggable includes built-in plugins for mirrors, scrolling, accessibility, focus management, collision detection, and animations, plus supports custom plugin development.
3
4
## Capabilities
5
6
### Base Plugin Class
7
8
All plugins extend AbstractPlugin which provides the foundation for plugin functionality.
9
10
```typescript { .api }
11
/**
12
* Base class for all draggable plugins
13
*/
14
abstract class AbstractPlugin {
15
constructor(draggable: Draggable);
16
protected abstract attach(): void;
17
protected abstract detach(): void;
18
}
19
20
export {AbstractPlugin as BasePlugin};
21
```
22
23
**Usage Example:**
24
25
```typescript
26
import { AbstractPlugin } from "@shopify/draggable";
27
28
class CustomPlugin extends AbstractPlugin {
29
attach() {
30
this.draggable.on('drag:start', this.onDragStart);
31
this.draggable.on('drag:stop', this.onDragStop);
32
}
33
34
detach() {
35
this.draggable.off('drag:start', this.onDragStart);
36
this.draggable.off('drag:stop', this.onDragStop);
37
}
38
39
onDragStart = (event) => {
40
// Custom drag start logic
41
};
42
43
onDragStop = (event) => {
44
// Custom drag stop logic
45
};
46
}
47
```
48
49
### Built-in Plugins (Draggable.Plugins)
50
51
Default plugins included with every Draggable instance.
52
53
```typescript { .api }
54
class Draggable {
55
static Plugins: {
56
Announcement: typeof Announcement;
57
Focusable: typeof Focusable;
58
Mirror: typeof Mirror;
59
Scrollable: typeof Scrollable;
60
};
61
}
62
```
63
64
#### Announcement Plugin
65
66
Provides accessibility announcements for screen readers.
67
68
```typescript { .api }
69
class Announcement extends AbstractPlugin {
70
options: AnnouncementOptions;
71
protected attach(): void;
72
protected detach(): void;
73
}
74
75
interface AnnouncementOptions {
76
expire: number;
77
[key: string]: string | (() => string) | number;
78
}
79
```
80
81
**Usage Example:**
82
83
```typescript
84
const draggable = new Draggable(containers, {
85
announcements: {
86
'drag:start': (event) => `Started dragging ${event.source.textContent}`,
87
'drag:stop': (event) => `Stopped dragging ${event.source.textContent}`,
88
expire: 10000 // 10 seconds
89
}
90
});
91
```
92
93
#### Focusable Plugin
94
95
Manages focus during drag operations for keyboard accessibility.
96
97
```typescript { .api }
98
class Focusable extends AbstractPlugin {
99
getElements(): HTMLElement[];
100
protected attach(): void;
101
protected detach(): void;
102
}
103
```
104
105
#### Mirror Plugin
106
107
Creates a visual representation (ghost/mirror) of the dragged element.
108
109
```typescript { .api }
110
class Mirror extends AbstractPlugin {
111
getElements(): HTMLElement[];
112
protected attach(): void;
113
protected detach(): void;
114
}
115
116
interface MirrorOptions {
117
xAxis?: boolean;
118
yAxis?: boolean;
119
constrainDimensions?: boolean;
120
cursorOffsetX?: number;
121
cursorOffsetY?: number;
122
appendTo?: string | HTMLElement | ((source: HTMLElement) => HTMLElement);
123
}
124
```
125
126
**Usage Example:**
127
128
```typescript
129
const draggable = new Draggable(containers, {
130
mirror: {
131
constrainDimensions: true,
132
cursorOffsetX: 10,
133
cursorOffsetY: 10,
134
appendTo: document.body
135
}
136
});
137
138
// Listen to mirror events
139
draggable.on('mirror:created', (event) => {
140
console.log('Mirror created:', event.mirror);
141
event.mirror.style.opacity = '0.8';
142
});
143
```
144
145
#### Scrollable Plugin
146
147
Provides auto-scrolling when dragging near container edges.
148
149
```typescript { .api }
150
class Scrollable extends AbstractPlugin {
151
protected attach(): void;
152
protected detach(): void;
153
}
154
155
interface ScrollableOptions {
156
speed?: number;
157
sensitivity?: number;
158
scrollableElements?: HTMLElement[];
159
}
160
```
161
162
**Usage Example:**
163
164
```typescript
165
const draggable = new Draggable(containers, {
166
scrollable: {
167
speed: 20,
168
sensitivity: 40,
169
scrollableElements: [document.documentElement]
170
}
171
});
172
```
173
174
### Additional Plugins
175
176
Extended plugins available through the Plugins export.
177
178
```typescript { .api }
179
const Plugins: {
180
Collidable: typeof Collidable;
181
SwapAnimation: typeof SwapAnimation;
182
SortAnimation: typeof SortAnimation;
183
ResizeMirror: typeof ResizeMirror;
184
Snappable: typeof Snappable;
185
};
186
```
187
188
#### Collidable Plugin
189
190
Detects collisions between dragged elements and other elements.
191
192
```typescript { .api }
193
class Collidable extends AbstractPlugin {
194
protected attach(): void;
195
protected detach(): void;
196
}
197
198
type Collidables = string | NodeList | HTMLElement[] | (() => NodeList | HTMLElement[]);
199
200
// Events
201
class CollidableEvent extends AbstractEvent {
202
readonly dragEvent: DragEvent;
203
readonly collidingElement: HTMLElement;
204
}
205
206
class CollidableInEvent extends CollidableEvent {}
207
class CollidableOutEvent extends CollidableEvent {}
208
209
type CollidableEventNames = 'collidable:in' | 'collidable:out';
210
```
211
212
**Usage Example:**
213
214
```typescript
215
import { Draggable, Plugins } from "@shopify/draggable";
216
217
const draggable = new Draggable(containers, {
218
plugins: [Plugins.Collidable],
219
collidables: '.obstacle, .barrier'
220
});
221
222
draggable.on('collidable:in', (event) => {
223
console.log('Collision detected with:', event.collidingElement);
224
event.collidingElement.classList.add('collision-active');
225
});
226
227
draggable.on('collidable:out', (event) => {
228
event.collidingElement.classList.remove('collision-active');
229
});
230
```
231
232
#### Snappable Plugin
233
234
Provides snap-to-grid or snap-to-element functionality.
235
236
```typescript { .api }
237
class Snappable extends AbstractPlugin {
238
protected attach(): void;
239
protected detach(): void;
240
}
241
242
// Events
243
class SnapEvent extends AbstractEvent {
244
readonly dragEvent: DragEvent;
245
readonly snappable: HTMLElement;
246
}
247
248
class SnapInEvent extends SnapEvent {}
249
class SnapOutEvent extends SnapEvent {}
250
251
type SnappableEventNames = 'snap:in' | 'snap:out';
252
```
253
254
#### ResizeMirror Plugin
255
256
Automatically resizes the mirror to match the drop target.
257
258
```typescript { .api }
259
class ResizeMirror extends AbstractPlugin {
260
protected attach(): void;
261
protected detach(): void;
262
}
263
```
264
265
#### SwapAnimation Plugin
266
267
Provides smooth animations for element swapping.
268
269
```typescript { .api }
270
class SwapAnimation extends AbstractPlugin {
271
protected attach(): void;
272
protected detach(): void;
273
}
274
275
interface SwapAnimationOptions {
276
duration: number;
277
easingFunction: string;
278
horizontal: boolean;
279
}
280
```
281
282
#### SortAnimation Plugin
283
284
Provides smooth animations for sorting operations.
285
286
```typescript { .api }
287
class SortAnimation extends AbstractPlugin {
288
protected attach(): void;
289
protected detach(): void;
290
}
291
292
interface SortAnimationOptions {
293
duration?: number;
294
easingFunction?: string;
295
}
296
```
297
298
**Animation Plugin Example:**
299
300
```typescript
301
import { Sortable, Plugins } from "@shopify/draggable";
302
303
const sortable = new Sortable(containers, {
304
plugins: [Plugins.SortAnimation],
305
sortAnimation: {
306
duration: 300,
307
easingFunction: 'ease-in-out'
308
}
309
});
310
```
311
312
### Plugin Management
313
314
Methods for dynamically managing plugins on draggable instances.
315
316
```typescript { .api }
317
/**
318
* Add plugins to a draggable instance
319
* @param plugins - Plugin classes to add
320
*/
321
addPlugin(...plugins: (typeof AbstractPlugin)[]): this;
322
323
/**
324
* Remove plugins from a draggable instance
325
* @param plugins - Plugin classes to remove
326
*/
327
removePlugin(...plugins: (typeof AbstractPlugin)[]): this;
328
```
329
330
**Usage Example:**
331
332
```typescript
333
const draggable = new Draggable(containers);
334
335
// Add plugins after initialization
336
draggable.addPlugin(Plugins.Collidable, Plugins.Snappable);
337
338
// Remove default plugins
339
draggable.removePlugin(Draggable.Plugins.Mirror);
340
341
// Exclude plugins during initialization
342
const draggable2 = new Draggable(containers, {
343
exclude: {
344
plugins: [Draggable.Plugins.Scrollable]
345
}
346
});
347
```
348
349
## Custom Plugin Development
350
351
```typescript
352
class LoggingPlugin extends AbstractPlugin {
353
constructor(draggable) {
354
super(draggable);
355
this.logCount = 0;
356
}
357
358
attach() {
359
// Add event listeners
360
this.draggable.on('drag:start', this.onDragStart);
361
this.draggable.on('drag:move', this.onDragMove);
362
this.draggable.on('drag:stop', this.onDragStop);
363
console.log('LoggingPlugin attached');
364
}
365
366
detach() {
367
// Remove event listeners
368
this.draggable.off('drag:start', this.onDragStart);
369
this.draggable.off('drag:move', this.onDragMove);
370
this.draggable.off('drag:stop', this.onDragStop);
371
console.log('LoggingPlugin detached');
372
}
373
374
onDragStart = (event) => {
375
this.logCount++;
376
console.log(`Drag #${this.logCount} started:`, {
377
source: event.source.id || event.source.className,
378
timestamp: Date.now()
379
});
380
};
381
382
onDragMove = (event) => {
383
console.log('Drag move:', {
384
x: event.sensorEvent.clientX,
385
y: event.sensorEvent.clientY
386
});
387
};
388
389
onDragStop = (event) => {
390
console.log(`Drag #${this.logCount} ended:`, {
391
duration: Date.now() - this.startTime,
392
finalPosition: {
393
x: event.sensorEvent.clientX,
394
y: event.sensorEvent.clientY
395
}
396
});
397
};
398
}
399
400
// Use custom plugin
401
const draggable = new Draggable(containers, {
402
plugins: [LoggingPlugin]
403
});
404
```
405
406
## Advanced Plugin Example
407
408
```typescript
409
class SmartMirrorPlugin extends AbstractPlugin {
410
constructor(draggable) {
411
super(draggable);
412
this.mirrors = new Map();
413
}
414
415
attach() {
416
this.draggable.on('mirror:create', this.onMirrorCreate);
417
this.draggable.on('mirror:created', this.onMirrorCreated);
418
this.draggable.on('drag:over', this.onDragOver);
419
this.draggable.on('mirror:destroy', this.onMirrorDestroy);
420
}
421
422
detach() {
423
this.draggable.off('mirror:create', this.onMirrorCreate);
424
this.draggable.off('mirror:created', this.onMirrorCreated);
425
this.draggable.off('drag:over', this.onDragOver);
426
this.draggable.off('mirror:destroy', this.onMirrorDestroy);
427
}
428
429
onMirrorCreate = (event) => {
430
// Enhance mirror creation
431
console.log('Creating smart mirror for:', event.source);
432
};
433
434
onMirrorCreated = (event) => {
435
const mirror = event.mirror;
436
const source = event.source;
437
438
// Store mirror reference
439
this.mirrors.set(source, mirror);
440
441
// Add smart features
442
mirror.classList.add('smart-mirror');
443
mirror.style.boxShadow = '0 5px 15px rgba(0,0,0,0.3)';
444
mirror.style.transform = 'rotate(5deg) scale(1.05)';
445
446
// Add drag count badge
447
const badge = document.createElement('div');
448
badge.className = 'drag-count-badge';
449
badge.textContent = (this.dragCount || 0) + 1;
450
mirror.appendChild(badge);
451
};
452
453
onDragOver = (event) => {
454
const mirror = this.mirrors.get(event.originalSource);
455
if (!mirror) return;
456
457
// Change mirror appearance based on drop target
458
if (event.over) {
459
mirror.style.borderColor = event.over.dataset.category === 'valid' ? 'green' : 'red';
460
mirror.style.opacity = '1';
461
} else {
462
mirror.style.borderColor = 'transparent';
463
mirror.style.opacity = '0.8';
464
}
465
};
466
467
onMirrorDestroy = (event) => {
468
const source = event.originalSource;
469
this.mirrors.delete(source);
470
this.dragCount = (this.dragCount || 0) + 1;
471
};
472
}
473
474
// Use the smart mirror plugin
475
const draggable = new Draggable(containers, {
476
plugins: [SmartMirrorPlugin]
477
});
478
```
479
480
## Plugin Configuration
481
482
```typescript
483
// Configure built-in plugins
484
const draggable = new Draggable(containers, {
485
// Mirror configuration
486
mirror: {
487
constrainDimensions: true,
488
xAxis: true,
489
yAxis: true,
490
cursorOffsetX: 20,
491
cursorOffsetY: 20
492
},
493
494
// Scrollable configuration
495
scrollable: {
496
speed: 25,
497
sensitivity: 60,
498
scrollableElements: [document.querySelector('.scroll-container')]
499
},
500
501
// Announcement configuration
502
announcements: {
503
'drag:start': 'Picked up item',
504
'drag:stop': 'Dropped item',
505
expire: 5000
506
},
507
508
// Add additional plugins
509
plugins: [Plugins.Collidable, Plugins.SortAnimation],
510
511
// Plugin-specific options
512
collidables: '.obstacle',
513
sortAnimation: {
514
duration: 200,
515
easingFunction: 'cubic-bezier(0.4, 0.0, 0.2, 1)'
516
}
517
});
518
```