diff --git a/docs/2-advanced/17-drag-and-drop.md b/docs/2-advanced/17-drag-and-drop.md new file mode 100644 index 000000000000..492db06ddb25 --- /dev/null +++ b/docs/2-advanced/17-drag-and-drop.md @@ -0,0 +1,399 @@ +--- +title: Drag and Drop +--- + +# Drag and Drop + +*UI5 Web Components has built-in drag and drop for lists, trees, tables, and tabs. This guide shows you how to use it.* + +You can drag items to reorder them or move them between components. The framework handles visual feedback and multiple selections for you. + +## Supported Components + +These components support drag and drop: + +### Lists and Trees +- `ui5-list` - Lists with movable items +- `ui5-li` - List items you can drag +- `ui5-tree` - Trees with movable items +- `ui5-tree-item` - Tree items you can drag + +### Tables +- `ui5-table` - Tables with movable rows +- `ui5-table-row` - Table rows you can drag + +### Tab Containers +- `ui5-tab-container` - Tab containers with movable tabs +- `ui5-tab` - Tabs you can drag + +### Other Components +- `ui5-li-group` - List groups that support drag events + +## Basic Setup + +### Make List Items Draggable + +Set the `movable` property to `true`: + +```html + + Item 1 + Item 2 + Item 3 + +``` + +### Handle Drag Events + +Lists fire two events for drag operations: + +#### `move-over` Event + +This fires when you drag an item over a drop target. Use it to validate if the drop is allowed: + +```javascript +list.addEventListener("ui5-move-over", (event) => { + const { source, destination } = event.detail; + + // Allow drop before or after items + if (destination.placement === "Before" || destination.placement === "After") { + event.preventDefault(); // Allow the drop + } + + // Conditionally allow nesting + if (destination.placement === "On" && destination.element.dataset.allowsNesting) { + event.preventDefault(); + } +}); +``` + +#### `move` Event + +This fires when you drop an item. It only fires if you prevented the default action in the `move-over` event: + +```javascript +list.addEventListener("ui5-move", (event) => { + const { source, destination } = event.detail; + + switch (destination.placement) { + case "Before": + destination.element.before(source.element); + break; + case "After": + destination.element.after(source.element); + break; + case "On": + destination.element.prepend(source.element); + break; + } +}); +``` + +## Event Details + +Both events give you this information: + +```typescript +{ + source: { + element: HTMLElement // The element being dragged + }, + destination: { + element: HTMLElement, // The target element + placement: "Before" | "After" | "On" // Where the item should be placed + } +} +``` + +### Where Items Can Go + +- **`Before`**: Place the item before the target +- **`After`**: Place the item after the target +- **`On`**: Place the item inside the target (for nesting) + +## Multiple Item Drag + +You can drag multiple selected items at once when using lists with multiple selection. + +### Enable Multiple Selection + +```html + + Item 1 + Item 2 + Item 3 + +``` + +### Handle Multiple Item Drag + +When you select multiple items and drag one of them, all selected items move together: + +```javascript +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; + +list.addEventListener("dragstart", (event) => { + const selectedItems = list.getItems().filter(item => item.selected); + const draggedItem = event.target; + + // If dragged item is not selected, select only it + if (!draggedItem.selected) { + selectedItems.forEach(item => item.selected = false); + draggedItem.selected = true; + } + + const currentSelected = list.getItems().filter(item => item.selected); + + // Start multiple drag if more than one item is selected + if (currentSelected.length > 1) { + startMultipleDrag(currentSelected.length); + } +}); +``` + +## Advanced Features + +### Custom Drag Images + +You can create custom drag images for better visual feedback: + +```javascript +function createCustomDragImage(count) { + const element = document.createElement("div"); + element.innerHTML = ` +
+ ${count} Items +
+ `; + element.style.cssText = "position: absolute; top: -1000px; left: -1000px;"; + document.body.appendChild(element); + return element; +} + +list.addEventListener("dragstart", (event) => { + const selectedItems = getSelectedItems(); + if (selectedItems.length > 1) { + const dragElement = createCustomDragImage(selectedItems.length); + event.dataTransfer.setDragImage(dragElement, 30, 15); + + // Clean up after drag starts + requestAnimationFrame(() => { + if (dragElement.parentNode) { + dragElement.parentNode.removeChild(dragElement); + } + }); + } +}); +``` + +### Conditional Drag and Drop + +You can control which items accept drops based on their properties: + +```javascript +// Mark certain items as fixed (non-movable) +list.addEventListener("ui5-move-over", (event) => { + const { destination } = event.detail; + + // Prevent dropping on fixed items + if (destination.element.dataset.fixed) { + return; // Don't prevent default, disallow drop + } + + event.preventDefault(); // Allow drop +}); +``` + +### Drag Between Lists + +You can drag items between different lists: + +```javascript +const list1 = document.getElementById("list1"); +const list2 = document.getElementById("list2"); + +function handleCrossListMove(event) { + const { source, destination } = event.detail; + + // Allow drops from other lists + if (!event.currentTarget.contains(source.element)) { + event.preventDefault(); + } +} + +list1.addEventListener("ui5-move-over", handleCrossListMove); +list2.addEventListener("ui5-move-over", handleCrossListMove); + +// Handle the actual move +[list1, list2].forEach(list => { + list.addEventListener("ui5-move", (event) => { + const { source, destination } = event.detail; + + switch (destination.placement) { + case "Before": + destination.element.before(source.element); + break; + case "After": + destination.element.after(source.element); + break; + } + }); +}); +``` + +## DragRegistry API + +For advanced cases, use the DragRegistry API to control multiple drag operations programmatically: + +```javascript +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; + +// Start a multiple drag operation +startMultipleDrag(itemCount); +``` + +### DragRegistry Methods + +- **`startMultipleDrag(count: number)`**: Starts a multiple drag operation with a custom ghost showing the item count + +## Best Practices + +### 1. Validate Drop Operations + +Always check if drops are valid in the `move-over` event: + +```javascript +list.addEventListener("ui5-move-over", (event) => { + const { source, destination } = event.detail; + + // Example: Prevent dropping item on itself + if (source.element === destination.element) { + return; + } + + // Example: Check business logic + if (isValidDrop(source.element, destination.element)) { + event.preventDefault(); + } +}); +``` + +### 2. Handle Edge Cases + +Think about fixed items, disabled states, and loading states: + +```javascript +list.addEventListener("ui5-move-over", (event) => { + const { destination, source } = event.detail; + + // Don't allow drops during loading + if (list.loading) { + return; + } + + // Don't allow drops on disabled items + if (destination.element.disabled) { + return; + } + + event.preventDefault(); +}); +``` + +### 3. Make It Accessible + +Make sure drag and drop works for everyone: + +- Add keyboard alternatives for drag and drop operations +- Use proper ARIA labels and descriptions +- Announce drag operations to screen readers + +## Complete Example + +Here's a working example with multiple selection: + +```html + + + + + + + + Task 1 + Task 2 + Task 3 + Task 4 + + + + + +``` + +## TypeScript Support + +When using TypeScript, import the right types: + +```typescript +import type { ListMoveEventDetail } from "@ui5/webcomponents/dist/List.js"; +import type { MoveEventDetail } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; + +const handleMove = (event: CustomEvent) => { + const { source, destination } = event.detail; + // Type-safe access to event details +}; +``` + +## Browser Support + +Drag and drop works in all modern browsers that support the HTML5 Drag and Drop API. Older browsers fall back to basic mouse interactions. diff --git a/packages/base/bundle.esm.js b/packages/base/bundle.esm.js index 01a624ddf0f6..ae4b89c3c3da 100644 --- a/packages/base/bundle.esm.js +++ b/packages/base/bundle.esm.js @@ -29,6 +29,7 @@ import { getFirstDayOfWeek, getLegacyDateCalendarCustomizing } from "./dist/conf import { _getRegisteredNames as getIconNames } from "./dist/asset-registries/Icons.js" import applyDirection from "./dist/locale/applyDirection.js"; import { getCurrentRuntimeIndex } from "./dist/Runtimes.js"; +import { startMultipleDrag } from "./dist/util/dragAndDrop/DragRegistry.js"; import LegacyDateFormats from "./dist/features/LegacyDateFormats.js"; window["sap-ui-webcomponents-bundle"] = { @@ -56,4 +57,5 @@ window["sap-ui-webcomponents-bundle"] = { renderFinished, applyDirection, EventProvider, + startMultipleDrag, }; diff --git a/packages/base/package-scripts.cjs b/packages/base/package-scripts.cjs index 85fe857d4084..ce747c91ac9b 100644 --- a/packages/base/package-scripts.cjs +++ b/packages/base/package-scripts.cjs @@ -15,8 +15,8 @@ const viteConfig = `-c "${require.resolve("@ui5/webcomponents-tools/components-p const scripts = { clean: "rimraf src/generated && rimraf dist && rimraf .port", lint: `eslint .`, - generate: "cross-env UI5_TS=true nps clean integrate copy generateAssetParameters generateVersionInfo generateStyles generateTemplates", - prepare: "cross-env UI5_TS=true nps clean integrate copy generateAssetParameters generateVersionInfo generateStyles generateTemplates typescript integrate.no-remaining-require", + generate: "cross-env UI5_TS=true nps clean build.i18n integrate copy generateAssetParameters generateVersionInfo generateStyles generateTemplates build.jsonImports", + prepare: "cross-env UI5_TS=true nps clean build.i18n integrate copy generateAssetParameters generateVersionInfo generateStyles generateTemplates typescript integrate.no-remaining-require build.jsonImports", typescript: "tsc -b", integrate: { default: "nps integrate.copy-used-modules integrate.amd-to-es6 integrate.third-party", @@ -32,6 +32,15 @@ const scripts = { build: { default: `nps prepare`, bundle: `vite build ${viteConfig}`, + i18n: { + default: "nps build.i18n.defaultsjs build.i18n.json", + defaultsjs: `node "${LIB}/i18n/defaults.js" src/i18n src/generated/i18n`, + json: `node "${LIB}/i18n/toJSON.js" src/i18n dist/generated/assets/i18n`, + }, + jsonImports: { + default: "mkdirp src/generated/json-imports && nps build.jsonImports.i18n", + i18n: `node "${LIB}/generate-json-imports/i18n.js" dist/generated/assets/i18n src/generated/json-imports`, + }, }, copy: { default: "nps copy.src", diff --git a/packages/base/src/Assets-fetch.ts b/packages/base/src/Assets-fetch.ts new file mode 100644 index 000000000000..0ec49a1b0c60 --- /dev/null +++ b/packages/base/src/Assets-fetch.ts @@ -0,0 +1,2 @@ +// own base package assets +import "./generated/json-imports/i18n-fetch.js"; diff --git a/packages/base/src/Assets-node.ts b/packages/base/src/Assets-node.ts new file mode 100644 index 000000000000..5527ba3234b0 --- /dev/null +++ b/packages/base/src/Assets-node.ts @@ -0,0 +1,14 @@ +/** + * The `Assets-node` entry point is used to import all CLDR, theming, and i18n assets. + * + * It serves as an alternative to the `Assets` and `Assets-fetch` modules and supports the + * `with: { type: 'json' }` import attribute for loading JSON files. + * + * This import attribute is required in some environments, such as Node.js with server-side rendering (SSR). + * + * Example usage: + * await import("../assets/i18n/messagebundle_bg.json", { with: { type: 'json' } }) + */ + +// own base package assets +import "./generated/json-imports/i18n-node.js"; diff --git a/packages/base/src/Assets.ts b/packages/base/src/Assets.ts new file mode 100644 index 000000000000..fb3b965a3be5 --- /dev/null +++ b/packages/base/src/Assets.ts @@ -0,0 +1,2 @@ +// own base package assets +import "./generated/json-imports/i18n.js"; diff --git a/packages/base/src/css/MultipleDragGhost.css b/packages/base/src/css/MultipleDragGhost.css new file mode 100644 index 000000000000..7c880bad2f64 --- /dev/null +++ b/packages/base/src/css/MultipleDragGhost.css @@ -0,0 +1,17 @@ +:host { + /* copy list item design */ + justify-content: center; + align-items: center; + display: flex; + color: var(--sapList_TextColor); + background-color: var(--sapList_Background); + font-family: "72override", var(--sapFontFamily); + height: var(--sapElement_LineHeight); + border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); + /* drag and drop related */ + opacity: 0.8; + position: absolute; + padding: 0 1rem; + top: -1000px; + left: -1000px; +} \ No newline at end of file diff --git a/packages/base/src/i18n/messagebundle.properties b/packages/base/src/i18n/messagebundle.properties new file mode 100644 index 000000000000..4e26a7bba71d --- /dev/null +++ b/packages/base/src/i18n/messagebundle.properties @@ -0,0 +1,5 @@ +#This is the resource bundle for the UI5 Web Components +#__ldi.translation.uuid=96bea51a-d5e3-46f0-b1d1-514d97be02ec + +#XTXT: Text for the Multiple Drag & Drop action +DRAG_DROP_MULTIPLE_TEXT={0} items diff --git a/packages/base/src/util/dragAndDrop/DragRegistry.ts b/packages/base/src/util/dragAndDrop/DragRegistry.ts index 8c960411b9d2..e635e4cdb6ae 100644 --- a/packages/base/src/util/dragAndDrop/DragRegistry.ts +++ b/packages/base/src/util/dragAndDrop/DragRegistry.ts @@ -1,6 +1,16 @@ import type UI5Element from "../../UI5Element.js"; import type MovePlacement from "../../types/MovePlacement.js"; +import MultipleDragGhostCss from "../../generated/css/MultipleDragGhost.css.js"; +import { getI18nBundle } from "../../i18nBundle.js"; + +import { + DRAG_DROP_MULTIPLE_TEXT, +} from "../../generated/i18n/i18n-defaults.js"; + +const MIN_MULTI_DRAG_COUNT = 2; + +let customDragElementPromise: Promise | null = null; let draggedElement: HTMLElement | null = null; let globalHandlersAttached = false; const subscribers = new Set(); @@ -14,14 +24,34 @@ const ondragstart = (e: DragEvent) => { if (!selfManagedDragAreas.has(e.target)) { draggedElement = e.target; } + + handleMultipleDrag(e); +}; + +const handleMultipleDrag = async (e: DragEvent) => { + if (!customDragElementPromise || !e.dataTransfer) { + return; + } + const dragElement = await customDragElementPromise; + // Add to document body temporarily + document.body.appendChild(dragElement); + + e.dataTransfer.setDragImage(dragElement, 0, 0); + + // Clean up the temporary element after the drag operation starts + requestAnimationFrame(() => { + dragElement.remove(); + }); }; const ondragend = () => { draggedElement = null; + customDragElementPromise = null; }; const ondrop = () => { draggedElement = null; + customDragElementPromise = null; }; const setDraggedElement = (element: HTMLElement | null) => { @@ -33,6 +63,37 @@ const getDraggedElement = () => { return draggedElement; }; +const createDefaultMultiDragElement = async (count: number): Promise => { + const dragElement = document.createElement("div"); + const i18nBundle = await getI18nBundle("@ui5/webcomponents-base"); + + const dragElementShadow = dragElement.attachShadow({ mode: "open" }); + + const styles = new CSSStyleSheet(); + styles.replaceSync(MultipleDragGhostCss); + + dragElementShadow.adoptedStyleSheets = [styles]; + dragElementShadow.innerHTML = i18nBundle.getText(DRAG_DROP_MULTIPLE_TEXT, count); + + return dragElement; +}; + +/** + * Starts a multiple drag operation by creating a drag ghost element. + * The drag ghost will be displayed when dragging multiple items. + * + * @param {number} count - The number of items being dragged. + * @public + */ +const startMultipleDrag = (count: number): void => { + if (count < MIN_MULTI_DRAG_COUNT) { + console.warn(`Cannot start multiple drag with count ${count}. Minimum is ${MIN_MULTI_DRAG_COUNT}.`); // eslint-disable-line + return; + } + + customDragElementPromise = createDefaultMultiDragElement(count); +}; + const attachGlobalHandlers = () => { if (globalHandlersAttached) { return; @@ -41,6 +102,7 @@ const attachGlobalHandlers = () => { document.body.addEventListener("dragstart", ondragstart); document.body.addEventListener("dragend", ondragend); document.body.addEventListener("drop", ondrop); + globalHandlersAttached = true; }; const detachGlobalHandlers = () => { @@ -104,9 +166,13 @@ const DragRegistry = { addSelfManagedArea, removeSelfManagedArea, getDraggedElement, + startMultipleDrag, }; export default DragRegistry; +export { + startMultipleDrag, +}; export type { SetDraggedElementFunction, DragAndDropSettings, diff --git a/packages/main/src/Assets-fetch.ts b/packages/main/src/Assets-fetch.ts index 059bc99d3c42..04ec2991524f 100644 --- a/packages/main/src/Assets-fetch.ts +++ b/packages/main/src/Assets-fetch.ts @@ -1,3 +1,4 @@ +import "@ui5/webcomponents-base/dist/Assets-fetch.js"; // Base i18n import "@ui5/webcomponents-localization/dist/Assets-fetch.js"; // CLDR import "@ui5/webcomponents-theming/dist/Assets-fetch.js"; // Theming import "@ui5/webcomponents-icons/dist/Assets-fetch.js"; // Icons texts diff --git a/packages/main/src/Assets-node.ts b/packages/main/src/Assets-node.ts index b1c5ee9ab110..0772a94b7980 100644 --- a/packages/main/src/Assets-node.ts +++ b/packages/main/src/Assets-node.ts @@ -11,6 +11,7 @@ */ // common assets +import "@ui5/webcomponents-base/dist/Assets-node.js"; // Base i18n import "@ui5/webcomponents-localization/dist/Assets-node.js"; // CLDR import "@ui5/webcomponents-theming/dist/Assets-node.js"; // Theming import "@ui5/webcomponents-icons/dist/Assets-node.js"; // Icons texts diff --git a/packages/main/src/Assets.ts b/packages/main/src/Assets.ts index 32cbb53d93ff..931577a05196 100644 --- a/packages/main/src/Assets.ts +++ b/packages/main/src/Assets.ts @@ -1,3 +1,4 @@ +import "@ui5/webcomponents-base/dist/Assets.js"; // Base i18n import "@ui5/webcomponents-localization/dist/Assets.js"; // CLDR import "@ui5/webcomponents-theming/dist/Assets.js"; // Theming import "@ui5/webcomponents-icons/dist/Assets.js"; // Icons texts diff --git a/packages/main/src/bundle.common.bootstrap.ts b/packages/main/src/bundle.common.bootstrap.ts index 810655cf1d79..6157fdd74b6a 100644 --- a/packages/main/src/bundle.common.bootstrap.ts +++ b/packages/main/src/bundle.common.bootstrap.ts @@ -73,6 +73,7 @@ import { attachDirectionChange } from "@ui5/webcomponents-base/dist/locale/direc import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; import { ignoreCustomElements, shouldIgnoreCustomElement } from "@ui5/webcomponents-base/dist/IgnoreCustomElements.js"; +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; import getElementSelection from "@ui5/webcomponents-base/dist/util/SelectionAssistant.js"; import * as defaultTexts from "./generated/i18n/i18n-defaults.js"; @@ -122,6 +123,7 @@ const testAssets = { getEffectiveIconCollection, ignoreCustomElements, shouldIgnoreCustomElement, + startMultipleDrag, }; // @ts-ignore diff --git a/packages/main/test/pages/MultipleDragDemo.html b/packages/main/test/pages/MultipleDragDemo.html new file mode 100644 index 000000000000..d42e01c935c8 --- /dev/null +++ b/packages/main/test/pages/MultipleDragDemo.html @@ -0,0 +1,160 @@ + + + + + + Multiple Drag Demo + + + + +
+ +
+

ui5-li (DragRegistry)

+
+ Selected: 0
+ Multiple selection mode, drag for multiple +
+ + + Item 1 + Item 2 + Item 3 + Item 4 + +
+ + +
+

ui5-li (Direct)

+
+ Selected: 0
+ Multiple selection, purple drag image +
+ + + Direct 1 + Direct 2 + Direct 3 + Direct 4 + +
+
+ + + + \ No newline at end of file diff --git a/packages/website/docs/_components_pages/main/List/List.mdx b/packages/website/docs/_components_pages/main/List/List.mdx index 1526a54703ef..2f99acb8d37f 100644 --- a/packages/website/docs/_components_pages/main/List/List.mdx +++ b/packages/website/docs/_components_pages/main/List/List.mdx @@ -10,6 +10,7 @@ import NoData from "../../../_samples/main/List/NoData/NoData.md"; import GroupHeaders from "../../../_samples/main/List/GroupHeaders/GroupHeaders.md"; import SeparationTypes from "../../../_samples/main/List/SeparationTypes/SeparationTypes.md"; import DragAndDrop from "../../../_samples/main/List/DragAndDrop/DragAndDrop.md"; +import MultipleDrag from "../../../_samples/main/List/MultipleDrag/MultipleDrag.md"; import WrappingBehavior from "../../../_samples/main/List/WrappingBehavior/WrappingBehavior.md"; <%COMPONENT_OVERVIEW%> @@ -57,6 +58,11 @@ The list items are draggable through the use of the movable property on < +### Multiple Item Drag +You can select and drag multiple items at once when using selection-mode="Multiple". This sample demonstrates cross-list dragging and the multiple drag ghost feature. + + + ### Wrapping Behavior The standard list item `` supports text wrapping through the wrappingType property. When set to "Normal", long text content (title and description) will wrap to multiple lines instead of truncating with an ellipsis. For very long content, the text is displayed with a "Show More/Show Less" mechanism. diff --git a/packages/website/docs/_samples/main/List/MultipleDrag/MultipleDrag.md b/packages/website/docs/_samples/main/List/MultipleDrag/MultipleDrag.md new file mode 100644 index 000000000000..17798ecc59ab --- /dev/null +++ b/packages/website/docs/_samples/main/List/MultipleDrag/MultipleDrag.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + diff --git a/packages/website/docs/_samples/main/List/MultipleDrag/main.js b/packages/website/docs/_samples/main/List/MultipleDrag/main.js new file mode 100644 index 000000000000..790ad0a148cc --- /dev/null +++ b/packages/website/docs/_samples/main/List/MultipleDrag/main.js @@ -0,0 +1,110 @@ +import "@ui5/webcomponents/dist/List.js"; +import "@ui5/webcomponents/dist/ListItemStandard.js"; +import "@ui5/webcomponents-icons/dist/task.js"; +import "@ui5/webcomponents-icons/dist/accept.js"; +import { startMultipleDrag } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; +import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; + +const lists = [ + document.getElementById("list1"), + document.getElementById("list2"), +]; +const counters = [ + document.getElementById("count1"), + document.getElementById("count2"), +]; + +function getSelectedItems(list) { + return list.getItems().filter(item => item.selected); +} + +function updateSelectionCount(listIndex) { + const selectedItems = getSelectedItems(lists[listIndex]); + counters[listIndex].textContent = selectedItems.length; +} + +function setupSelectionChangeListener(listIndex) { + lists[listIndex].addEventListener("ui5-selection-change", () => { + updateSelectionCount(listIndex); + }); +} + +function handleDragStart(listIndex) { + return function(event) { + const list = lists[listIndex]; + const selectedItems = getSelectedItems(list); + const draggedItem = event.target; + + // If dragged item is not selected, select only it + if (!draggedItem.selected) { + selectedItems.forEach(item => item.selected = false); + draggedItem.selected = true; + updateSelectionCount(listIndex); + } + + const currentSelected = getSelectedItems(list); + + // Start multiple drag if more than one item is selected + if (currentSelected.length > 1) { + startMultipleDrag(currentSelected.length); + } + }; +} + +function handleMoveOver(event) { + const { source, destination } = event.detail; + + // Allow drops from both lists + const sourceList = source.element.closest('ui5-list'); + if (lists.includes(sourceList)) { + // Allow reordering within lists + if (destination.placement === MovePlacement.Before || + destination.placement === MovePlacement.After) { + event.preventDefault(); + } + } +} + +function handleMove(event) { + const { source, destination } = event.detail; + + // Get the source list to find all selected items + const sourceList = source.element.closest('ui5-list'); + const selectedItems = getSelectedItems(sourceList); + + // 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 MovePlacement.Before: + destination.element.before(...itemsToMove); + break; + case MovePlacement.After: + destination.element.after(...itemsToMove); + break; + case MovePlacement.On: + destination.element.prepend(...itemsToMove); + break; + } + + // Update selection counts after move + setTimeout(() => { + updateSelectionCount(0); + updateSelectionCount(1); + }, 0); +} + +// Setup both lists +lists.forEach((list, index) => { + setupSelectionChangeListener(index); + list.addEventListener("dragstart", handleDragStart(index)); + list.addEventListener("ui5-move-over", handleMoveOver); + list.addEventListener("ui5-move", handleMove); +}); + +// Initialize selection counts +updateSelectionCount(0); +updateSelectionCount(1); diff --git a/packages/website/docs/_samples/main/List/MultipleDrag/sample.html b/packages/website/docs/_samples/main/List/MultipleDrag/sample.html new file mode 100644 index 000000000000..f1cdd135a004 --- /dev/null +++ b/packages/website/docs/_samples/main/List/MultipleDrag/sample.html @@ -0,0 +1,76 @@ + + + + + + + + Multiple Drag Sample + + + + + + +
+ +
+
+ Selected: 0 items
+ Select multiple items and drag to move them together +
+ + + Review design mockups + Update documentation + Fix bug #123 + Prepare demo + Test new feature + +
+ + +
+
+ Selected: 0 items
+ Drag items from the left list here +
+ + + Write unit tests + Code review + +
+
+ + + + + + +