or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-draggable.mddroppable.mdevents.mdindex.mdplugins.mdsensors.mdsortable.mdswappable.md

plugins.mddocs/

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

```