Skip to content

Commit 0c7025f

Browse files
authored
feat(DragRegistry): support for multiple drag (#11780)
## Add Drag and Drop Documentation and Multiple Drag Sample This PR adds comprehensive drag and drop documentation and a new sample showing multiple item dragging. ### What's New **Documentation** - New drag and drop guide at `/docs/2-advanced/17-drag-and-drop.md` - Clear examples for basic and advanced use cases **Website Sample** - New "Multiple Item Drag" sample for the List component page - Shows cross-list dragging between two task lists - Demonstrates multiple selection with real-time counters - Uses the `startMultipleDrag()` API for custom drag ghosts **DragRegistry Improvements** - Multiple drag handling ## How to Use Multiple Drag Follow these steps to add multiple drag to your lists: ### Step 1: Set up your list Make your list items draggable ```html <ui5-list selection-mode="Multiple"> <ui5-li movable>Item 1</ui5-li> <ui5-li movable>Item 2</ui5-li> <ui5-li movable>Item 3</ui5-li> </ui5-list> ``` ### Step 2: Import the multiple drag function ```js import { startMultipleDrag } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; ``` ### Step 3: Handle drag start Add this event listener to make the framework show the multiple drag ghost: ```js list.addEventListener("dragstart", (event) => { const selectedItems = list.getItems().filter(item => item.selected); // Show multiple drag ghost if more than one item is selected if (selectedItems.length > 1) { startMultipleDrag(selectedItems.length); } }); ``` ### Step 4: Handle the actual move Add move handlers to move all selected items together: ```javascript list.addEventListener("ui5-move", (event) => { const { source, destination } = event.detail; const sourceList = source.element.closest('ui5-list'); const selectedItems = sourceList.getItems().filter(item => item.selected); // Determine which items to move: all selected items or just the dragged item const itemsToMove = selectedItems.length > 1 && selectedItems.includes(source.element) ? selectedItems : [source.element]; // Move the items using spread operator switch (destination.placement) { case "Before": destination.element.before(...itemsToMove); break; case "After": destination.element.after(...itemsToMove); break; case "On": destination.element.prepend(...itemsToMove); break; } });
1 parent a9b3c69 commit 0c7025f

File tree

20 files changed

+868
-2
lines changed

20 files changed

+868
-2
lines changed

docs/2-advanced/17-drag-and-drop.md

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
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.

packages/base/bundle.esm.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { getFirstDayOfWeek, getLegacyDateCalendarCustomizing } from "./dist/conf
2929
import { _getRegisteredNames as getIconNames } from "./dist/asset-registries/Icons.js"
3030
import applyDirection from "./dist/locale/applyDirection.js";
3131
import { getCurrentRuntimeIndex } from "./dist/Runtimes.js";
32+
import { startMultipleDrag } from "./dist/DragAndDrop.js";
3233
import LegacyDateFormats from "./dist/features/LegacyDateFormats.js";
3334

3435
window["sap-ui-webcomponents-bundle"] = {
@@ -56,4 +57,5 @@ window["sap-ui-webcomponents-bundle"] = {
5657
renderFinished,
5758
applyDirection,
5859
EventProvider,
60+
startMultipleDrag,
5961
};

0 commit comments

Comments
 (0)