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

droppable.mddocs/

0

# Droppable Zones

1

2

Droppable extends Draggable to create designated drop zones where draggable elements can be placed. It provides visual feedback for valid drop targets and handles drop validation.

3

4

## Capabilities

5

6

### Droppable Constructor

7

8

Creates a droppable instance that requires dropzone configuration to define valid drop targets.

9

10

```typescript { .api }

11

/**

12

* Creates a new droppable instance with designated drop zones

13

* @param containers - Elements that contain draggable items

14

* @param options - Configuration options including required dropzone

15

*/

16

class Droppable<T = DroppableEventNames> extends Draggable<T> {

17

constructor(containers: DraggableContainer, options: DroppableOptions);

18

}

19

20

interface DroppableOptions extends DraggableOptions {

21

dropzone: string | NodeList | HTMLElement[] | (() => NodeList | HTMLElement[]);

22

classes?: {[key in DroppableClassNames]: string};

23

}

24

25

type DroppableClassNames =

26

| DraggableClassNames

27

| 'droppable:active'

28

| 'droppable:occupied';

29

```

30

31

**Usage Example:**

32

33

```typescript

34

import { Droppable } from "@shopify/draggable";

35

36

const droppable = new Droppable(document.querySelectorAll('.drag-container'), {

37

draggable: '.draggable-item',

38

dropzone: '.drop-zone',

39

classes: {

40

'droppable:active': 'drop-zone-active',

41

'droppable:occupied': 'drop-zone-occupied'

42

}

43

});

44

45

// Listen for drop events

46

droppable.on('droppable:dropped', (event) => {

47

console.log('Item dropped into:', event.dropzone);

48

console.log('Dragged item:', event.dragEvent.source);

49

});

50

```

51

52

### Class Name Management

53

54

Specialized class name management for dropzone-specific states.

55

56

```typescript { .api }

57

/**

58

* Returns CSS class name for droppable-specific class identifiers

59

* @param name - Droppable class identifier

60

* @returns CSS class name string

61

*/

62

getClassNameFor(name: DroppableClassNames): string;

63

```

64

65

### Drop Events

66

67

Droppable-specific events that fire during drop operations.

68

69

```typescript { .api }

70

type DroppableEventNames =

71

| 'droppable:start'

72

| 'droppable:dropped'

73

| 'droppable:returned'

74

| 'droppable:stop'

75

| DraggableEventNames;

76

```

77

78

**Event Details:**

79

80

- **droppable:start**: Fired when a droppable drag operation begins

81

- **droppable:dropped**: Fired when an item is dropped into a valid dropzone

82

- **droppable:returned**: Fired when an item returns to its original dropzone

83

- **droppable:stop**: Fired when the drop operation ends

84

85

**Event Handlers Example:**

86

87

```typescript

88

droppable.on('droppable:start', (event) => {

89

console.log('Started dragging from dropzone:', event.dropzone);

90

// Highlight valid drop zones

91

document.querySelectorAll('.drop-zone').forEach(zone => {

92

zone.classList.add('available-target');

93

});

94

});

95

96

droppable.on('droppable:dropped', (event) => {

97

console.log(`Item moved to new dropzone`);

98

handleItemDrop(event.dragEvent.source, event.dropzone);

99

100

// Optionally prevent the drop

101

// event.cancel();

102

});

103

104

droppable.on('droppable:returned', (event) => {

105

console.log('Item returned to original position');

106

handleItemReturn(event.dragEvent.source, event.dropzone);

107

});

108

109

droppable.on('droppable:stop', (event) => {

110

// Clean up highlighting

111

document.querySelectorAll('.drop-zone').forEach(zone => {

112

zone.classList.remove('available-target');

113

});

114

});

115

```

116

117

## Event Types

118

119

```typescript { .api }

120

class DroppableEvent extends AbstractEvent {

121

readonly dragEvent: DragEvent;

122

}

123

124

class DroppableStartEvent extends DroppableEvent {

125

dropzone: HTMLElement;

126

}

127

128

class DroppableDroppedEvent extends DroppableEvent {

129

dropzone: HTMLElement;

130

}

131

132

class DroppableReturnedEvent extends DroppableEvent {

133

dropzone: HTMLElement;

134

}

135

136

class DroppableStopEvent extends DroppableEvent {

137

dropzone: HTMLElement;

138

}

139

```

140

141

## Dropzone Configuration

142

143

The dropzone option can be specified in multiple ways:

144

145

```typescript

146

// CSS selector string

147

const droppable1 = new Droppable(containers, {

148

draggable: '.item',

149

dropzone: '.drop-target'

150

});

151

152

// NodeList or HTMLElement array

153

const dropzones = document.querySelectorAll('.zone');

154

const droppable2 = new Droppable(containers, {

155

draggable: '.item',

156

dropzone: dropzones

157

});

158

159

// Function returning NodeList or HTMLElement array

160

const droppable3 = new Droppable(containers, {

161

draggable: '.item',

162

dropzone: () => document.querySelectorAll('.dynamic-zone')

163

});

164

```

165

166

## Complete Example

167

168

```typescript

169

import { Droppable } from "@shopify/draggable";

170

171

// Create a file upload interface with drag and drop

172

const fileDroppable = new Droppable(document.querySelector('.file-area'), {

173

draggable: '.file-item',

174

dropzone: '.upload-zone, .trash-zone',

175

classes: {

176

'droppable:active': 'zone-highlight',

177

'droppable:occupied': 'zone-has-file',

178

'source:dragging': 'file-being-dragged'

179

}

180

});

181

182

// Handle different drop zones

183

fileDroppable.on('droppable:dropped', (event) => {

184

const file = event.dragEvent.source;

185

const zone = event.dropzone;

186

187

if (zone.classList.contains('upload-zone')) {

188

// Start upload process

189

uploadFile(file.dataset.fileId);

190

showUploadProgress(file);

191

} else if (zone.classList.contains('trash-zone')) {

192

// Delete file

193

deleteFile(file.dataset.fileId);

194

file.remove();

195

}

196

});

197

198

// Provide visual feedback

199

fileDroppable.on('droppable:start', (event) => {

200

// Show drop zones when dragging starts

201

document.querySelectorAll('.drop-zone').forEach(zone => {

202

zone.style.opacity = '1';

203

zone.style.transform = 'scale(1.05)';

204

});

205

});

206

207

fileDroppable.on('droppable:stop', (event) => {

208

// Hide drop zones when dragging ends

209

document.querySelectorAll('.drop-zone').forEach(zone => {

210

zone.style.opacity = '';

211

zone.style.transform = '';

212

});

213

});

214

215

// Handle validation

216

fileDroppable.on('droppable:dropped', (event) => {

217

const file = event.dragEvent.source;

218

const zone = event.dropzone;

219

220

// Check if file type is allowed in this zone

221

const allowedTypes = zone.dataset.allowedTypes?.split(',') || [];

222

const fileType = file.dataset.type;

223

224

if (allowedTypes.length > 0 && !allowedTypes.includes(fileType)) {

225

event.cancel();

226

showError(`${fileType} files not allowed in this zone`);

227

}

228

});

229

```

230

231

## Advanced Dropzone Patterns

232

233

### Conditional Drop Zones

234

235

```typescript

236

const conditionalDroppable = new Droppable(containers, {

237

draggable: '.task',

238

dropzone: () => {

239

// Only return available drop zones based on current state

240

return Array.from(document.querySelectorAll('.task-column'))

241

.filter(column => !column.classList.contains('locked'));

242

}

243

});

244

```

245

246

### Nested Drop Zones

247

248

```typescript

249

const nestedDroppable = new Droppable(document.querySelector('.workspace'), {

250

draggable: '.widget',

251

dropzone: '.panel, .sidebar, .main-area'

252

});

253

254

// Handle different drop contexts

255

nestedDroppable.on('droppable:dropped', (event) => {

256

const widget = event.dragEvent.source;

257

const target = event.dropzone;

258

259

// Configure widget based on drop target

260

if (target.classList.contains('sidebar')) {

261

widget.classList.add('sidebar-widget');

262

resizeWidget(widget, 'narrow');

263

} else if (target.classList.contains('main-area')) {

264

widget.classList.add('main-widget');

265

resizeWidget(widget, 'wide');

266

}

267

});

268

```