|
| 1 | +--- |
| 2 | +title: Drag and Drop |
| 3 | +--- |
| 4 | + |
| 5 | +# Drag and Drop |
| 6 | + |
| 7 | +*UI5 Web Components has built-in drag and drop for lists, trees, tables, and tabs. This guide shows you how to use it.* |
| 8 | + |
| 9 | +You can drag items to reorder them or move them between components. The framework handles visual feedback and multiple selection for you. |
| 10 | + |
| 11 | +## Supported Components |
| 12 | + |
| 13 | +These components support drag and drop: |
| 14 | + |
| 15 | +### Lists and Trees |
| 16 | +- `ui5-list` - Lists with movable items |
| 17 | +- `ui5-li` - List items you can drag |
| 18 | +- `ui5-li-custom` - Custom list items you can drag |
| 19 | +- `ui5-tree` - Trees with movable items |
| 20 | +- `ui5-tree-item` - Tree items you can drag |
| 21 | +- `ui5-tree-item-custom` - Custom tree items you can drag |
| 22 | + |
| 23 | +### Tables |
| 24 | +- `ui5-table` - Tables with movable rows |
| 25 | +- `ui5-table-row` - Table rows you can drag |
| 26 | + |
| 27 | +### Tab Containers |
| 28 | +- `ui5-tab-container` - Tab containers with movable tabs |
| 29 | +- `ui5-tab` - Tabs you can drag |
| 30 | + |
| 31 | +### Other Components |
| 32 | +- `ui5-li-group` - List groups that support drag events |
| 33 | + |
| 34 | +## Basic Setup |
| 35 | + |
| 36 | +### Make List Items Draggable |
| 37 | + |
| 38 | +Set the `movable` property to `true`: |
| 39 | + |
| 40 | +```html |
| 41 | +<ui5-list id="myList" header-text="Draggable Items"> |
| 42 | + <ui5-li movable>Item 1</ui5-li> |
| 43 | + <ui5-li movable>Item 2</ui5-li> |
| 44 | + <ui5-li movable>Item 3</ui5-li> |
| 45 | +</ui5-list> |
| 46 | +``` |
| 47 | + |
| 48 | +### Handle Drag Events |
| 49 | + |
| 50 | +Lists fire two events for drag operations: |
| 51 | + |
| 52 | +#### `move-over` Event |
| 53 | + |
| 54 | +This fires when you drag an item over a drop target. Use it to validate if the drop is allowed: |
| 55 | + |
| 56 | +```javascript |
| 57 | +list.addEventListener("ui5-move-over", (event) => { |
| 58 | + const { source, destination } = event.detail; |
| 59 | + |
| 60 | + // Allow drop before or after items |
| 61 | + if (destination.placement === "Before" || destination.placement === "After") { |
| 62 | + event.preventDefault(); // Allow the drop |
| 63 | + } |
| 64 | + |
| 65 | + // Conditionally allow nesting |
| 66 | + if (destination.placement === "On" && destination.element.dataset.allowsNesting) { |
| 67 | + event.preventDefault(); |
| 68 | + } |
| 69 | +}); |
| 70 | +``` |
| 71 | + |
| 72 | +#### `move` Event |
| 73 | + |
| 74 | +This fires when you drop an item. It only fires if you prevented the default action in the `move-over` event: |
| 75 | + |
| 76 | +```javascript |
| 77 | +list.addEventListener("ui5-move", (event) => { |
| 78 | + const { source, destination } = event.detail; |
| 79 | + |
| 80 | + switch (destination.placement) { |
| 81 | + case "Before": |
| 82 | + destination.element.before(source.element); |
| 83 | + break; |
| 84 | + case "After": |
| 85 | + destination.element.after(source.element); |
| 86 | + break; |
| 87 | + case "On": |
| 88 | + destination.element.prepend(source.element); |
| 89 | + break; |
| 90 | + } |
| 91 | +}); |
| 92 | +``` |
| 93 | + |
| 94 | +## Event Details |
| 95 | + |
| 96 | +Both events give you this information: |
| 97 | + |
| 98 | +```typescript |
| 99 | +{ |
| 100 | + source: { |
| 101 | + element: HTMLElement // The element being dragged |
| 102 | + }, |
| 103 | + destination: { |
| 104 | + element: HTMLElement, // The target element |
| 105 | + placement: "Before" | "After" | "On" // Where the item should be placed |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +### Where Items Can Go |
| 111 | + |
| 112 | +- **`Before`**: Place the item before the target |
| 113 | +- **`After`**: Place the item after the target |
| 114 | +- **`On`**: Place the item inside the target (for nesting) |
| 115 | + |
| 116 | +## Multiple Item Drag |
| 117 | + |
| 118 | +You can drag multiple selected items at once when using lists with multiple selection. |
| 119 | + |
| 120 | +### Enable Multiple Selection |
| 121 | + |
| 122 | +```html |
| 123 | +<ui5-list selection-mode="Multiple"> |
| 124 | + <ui5-li movable>Item 1</ui5-li> |
| 125 | + <ui5-li movable>Item 2</ui5-li> |
| 126 | + <ui5-li movable>Item 3</ui5-li> |
| 127 | +</ui5-list> |
| 128 | +``` |
| 129 | + |
| 130 | +### Handle Multiple Item Drag |
| 131 | + |
| 132 | +When you select multiple items and drag one of them, all selected items move together: |
| 133 | + |
| 134 | +```javascript |
| 135 | +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/DragAndDrop.js"; |
| 136 | + |
| 137 | +list.addEventListener("dragstart", (event) => { |
| 138 | + const selectedItems = list.getItems().filter(item => item.selected); |
| 139 | + const draggedItem = event.target; |
| 140 | + |
| 141 | + // If dragged item is not selected, select only it |
| 142 | + if (!draggedItem.selected) { |
| 143 | + selectedItems.forEach(item => item.selected = false); |
| 144 | + draggedItem.selected = true; |
| 145 | + } |
| 146 | + |
| 147 | + const currentSelected = list.getItems().filter(item => item.selected); |
| 148 | + |
| 149 | + // Start multiple drag if more than one item is selected |
| 150 | + if (currentSelected.length > 1) { |
| 151 | + startMultipleDrag(currentSelected.length); |
| 152 | + } |
| 153 | +}); |
| 154 | +``` |
| 155 | + |
| 156 | +Remember to move the selected items in the `move` event instead of just the dragged item: |
| 157 | + |
| 158 | +```javascript |
| 159 | +function handleMove(event) { |
| 160 | + const { source, destination } = event.detail; |
| 161 | + |
| 162 | + // Get the source list to find all selected items |
| 163 | + const sourceList = source.element.closest('ui5-list'); |
| 164 | + const selectedItems = sourceList.getItems().filter(item => item.selected); |
| 165 | + |
| 166 | + // Determine which items to move: all selected items or just the dragged item |
| 167 | + const itemsToMove = selectedItems.length > 1 && selectedItems.includes(source.element) |
| 168 | + ? selectedItems |
| 169 | + : [source.element]; |
| 170 | + |
| 171 | + // Move the items using spread operator |
| 172 | + switch (destination.placement) { |
| 173 | + case MovePlacement.Before: |
| 174 | + destination.element.before(...itemsToMove); |
| 175 | + break; |
| 176 | + case MovePlacement.After: |
| 177 | + destination.element.after(...itemsToMove); |
| 178 | + break; |
| 179 | + case MovePlacement.On: |
| 180 | + destination.element.prepend(...itemsToMove); |
| 181 | + break; |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | +## Advanced Features |
| 186 | + |
| 187 | +### Custom Drag Images |
| 188 | + |
| 189 | +You can create custom drag images for better visual feedback: |
| 190 | + |
| 191 | +```css |
| 192 | +.drag-image { |
| 193 | + background: linear-gradient(135deg, #8b5cf6, #a855f7); |
| 194 | + border: 2px solid #7c3aed; |
| 195 | + border-radius: 0.5rem; |
| 196 | + padding: 1rem 1.5rem; |
| 197 | + color: white; |
| 198 | + font-weight: 600; |
| 199 | + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4); |
| 200 | + white-space: nowrap; |
| 201 | + position: absolute; |
| 202 | + top: -1000px; /* Hide off-screen */ |
| 203 | + left: -1000px; /* Hide off-screen */ |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +Then create a custom drag image element in JavaScript: |
| 208 | + |
| 209 | +```javascript |
| 210 | +function createCustomDragImage(count) { |
| 211 | + const element = document.createElement("div"); |
| 212 | + element.innerHTML = ` |
| 213 | + <div class="drag-image"> |
| 214 | + ${count} Items |
| 215 | + </div> |
| 216 | + `; |
| 217 | + document.body.appendChild(element); |
| 218 | + return element; |
| 219 | +} |
| 220 | + |
| 221 | +list.addEventListener("dragstart", (event) => { |
| 222 | + const selectedItems = list.getItems().filter(item => item.selected); |
| 223 | + if (selectedItems.length > 1) { |
| 224 | + const dragElement = createCustomDragImage(selectedItems.length); |
| 225 | + event.dataTransfer.setDragImage(dragElement, 30, 15); |
| 226 | + |
| 227 | + // Clean up after drag starts |
| 228 | + requestAnimationFrame(() => { |
| 229 | + if (dragElement.parentNode) { |
| 230 | + dragElement.parentNode.removeChild(dragElement); |
| 231 | + } |
| 232 | + }); |
| 233 | + } |
| 234 | +}); |
| 235 | +``` |
| 236 | + |
| 237 | +### Conditional Drag and Drop |
| 238 | + |
| 239 | +You can control which items accept drops based on their properties: |
| 240 | + |
| 241 | +```javascript |
| 242 | +// Mark certain items as fixed (non-movable) |
| 243 | +list.addEventListener("ui5-move-over", (event) => { |
| 244 | + const { destination } = event.detail; |
| 245 | + |
| 246 | + // Prevent dropping on fixed items |
| 247 | + if (destination.element.dataset.fixed) { |
| 248 | + return; // Don't prevent default, disallow drop |
| 249 | + } |
| 250 | + |
| 251 | + event.preventDefault(); // Allow drop |
| 252 | +}); |
| 253 | +``` |
| 254 | + |
| 255 | +### Drag Between Lists |
| 256 | + |
| 257 | +You can drag items between different lists: |
| 258 | + |
| 259 | +```javascript |
| 260 | +const list1 = document.getElementById("list1"); |
| 261 | +const list2 = document.getElementById("list2"); |
| 262 | + |
| 263 | +function handleCrossListMove(event) { |
| 264 | + const { source, destination } = event.detail; |
| 265 | + |
| 266 | + // Allow drops from other lists |
| 267 | + if (!event.currentTarget.contains(source.element)) { |
| 268 | + event.preventDefault(); |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +list1.addEventListener("ui5-move-over", handleCrossListMove); |
| 273 | +list2.addEventListener("ui5-move-over", handleCrossListMove); |
| 274 | + |
| 275 | +// Handle the actual move |
| 276 | +[list1, list2].forEach(list => { |
| 277 | + list.addEventListener("ui5-move", (event) => { |
| 278 | + const { source, destination } = event.detail; |
| 279 | + |
| 280 | + switch (destination.placement) { |
| 281 | + case "Before": |
| 282 | + destination.element.before(source.element); |
| 283 | + break; |
| 284 | + case "After": |
| 285 | + destination.element.after(source.element); |
| 286 | + break; |
| 287 | + } |
| 288 | + }); |
| 289 | +}); |
| 290 | +``` |
| 291 | + |
| 292 | +## DragRegistry API |
| 293 | + |
| 294 | +For advanced cases, use the DragRegistry API to control multiple drag operations programmatically: |
| 295 | + |
| 296 | +```javascript |
| 297 | +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/DragAndDrop.js"; |
| 298 | + |
| 299 | +// Start a multiple drag operation |
| 300 | +startMultipleDrag(itemCount); |
| 301 | +``` |
| 302 | + |
| 303 | +### DragRegistry Methods |
| 304 | + |
| 305 | +- **`startMultipleDrag(count: number)`**: Starts a multiple drag operation with a custom ghost showing the item count |
| 306 | + |
| 307 | +## Best Practices |
| 308 | + |
| 309 | +### 1. Validate Drop Operations |
| 310 | + |
| 311 | +Always check if drops are valid in the `move-over` event: |
| 312 | + |
| 313 | +```javascript |
| 314 | +list.addEventListener("ui5-move-over", (event) => { |
| 315 | + const { source, destination } = event.detail; |
| 316 | + |
| 317 | + // Example: Prevent dropping item on itself |
| 318 | + if (source.element === destination.element) { |
| 319 | + return; |
| 320 | + } |
| 321 | + |
| 322 | + // Example: Check business logic |
| 323 | + if (isValidDrop(source.element, destination.element)) { |
| 324 | + event.preventDefault(); |
| 325 | + } |
| 326 | +}); |
| 327 | +``` |
| 328 | + |
| 329 | +### 2. Handle Edge Cases |
| 330 | + |
| 331 | +Think about fixed items, disabled states, and loading states: |
| 332 | + |
| 333 | +```javascript |
| 334 | +list.addEventListener("ui5-move-over", (event) => { |
| 335 | + const { destination, source } = event.detail; |
| 336 | + |
| 337 | + // Don't allow drops during loading |
| 338 | + if (list.loading) { |
| 339 | + return; |
| 340 | + } |
| 341 | + |
| 342 | + // Don't allow drops on disabled items |
| 343 | + if (destination.element.disabled) { |
| 344 | + return; |
| 345 | + } |
| 346 | + |
| 347 | + event.preventDefault(); |
| 348 | +}); |
| 349 | +``` |
| 350 | + |
| 351 | +### 3. Make It Accessible |
| 352 | + |
| 353 | +Make sure drag and drop works for everyone: |
| 354 | + |
| 355 | +- Add keyboard alternatives for drag and drop operations |
| 356 | +- Use proper ARIA labels and descriptions |
| 357 | +- Announce drag operations to screen readers |
| 358 | + |
| 359 | +## TypeScript Support |
| 360 | + |
| 361 | +When using TypeScript, import the right types: |
| 362 | + |
| 363 | +```typescript |
| 364 | +import type { ListMoveEventDetail } from "@ui5/webcomponents/dist/List.js"; |
| 365 | + |
| 366 | +const handleMove = (event: CustomEvent<ListMoveEventDetail>) => { |
| 367 | + const { source, destination } = event.detail; |
| 368 | + // Type-safe access to event details |
| 369 | +}; |
| 370 | +``` |
| 371 | + |
| 372 | +## Browser Support |
| 373 | + |
| 374 | +Drag and drop works in all modern browsers that support the HTML5 Drag and Drop API. Older browsers fall back to basic mouse interactions. |
0 commit comments