Skip to content

fix(DnD): fix drag and drop in Shadow DOM #11975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
99756fb
chore: add test page
TeodorTaushanov Jul 22, 2025
136a2fa
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 22, 2025
b9f7962
fix(DnD): fix DnD in Shadow DOM
TeodorTaushanov Jul 22, 2025
a910a31
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 22, 2025
da291ae
chore: fix lint error
TeodorTaushanov Jul 22, 2025
9acf2d0
chore: fix lint error
TeodorTaushanov Jul 22, 2025
5c8eef7
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 23, 2025
8c5e1fc
chore: change sample
TeodorTaushanov Jul 23, 2025
5c60f92
chore: improve the examples
TeodorTaushanov Jul 23, 2025
12f4a9d
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 24, 2025
7ee75e8
chore: skip tests
TeodorTaushanov Jul 25, 2025
a348885
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 25, 2025
74b7cc7
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 25, 2025
c98b2d3
fix: fix clearDraggedElement usage
TeodorTaushanov Jul 25, 2025
ff26369
fix: fix lint errors
TeodorTaushanov Jul 28, 2025
d6c9f06
fix: test page and sample page
TeodorTaushanov Jul 28, 2025
7ebb795
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 28, 2025
9bfd36b
fix: resolve merge conflicts
TeodorTaushanov Jul 28, 2025
74f8ed6
fix: resolve merge conflicts
TeodorTaushanov Jul 28, 2025
8cdad13
chore: update to lates DnD changes
TeodorTaushanov Jul 28, 2025
5ae9fae
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 28, 2025
b99be35
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 29, 2025
6bf64b8
chore: adapt code to latest multiple drag elements code
TeodorTaushanov Jul 29, 2025
28c9be8
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 29, 2025
5319df0
chore: add tab container tests
TeodorTaushanov Jul 29, 2025
d9e2548
chore: move table logic to TableDragAndDrop file
TeodorTaushanov Jul 29, 2025
4c9b01e
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 30, 2025
af9af82
chore: simplify "multiple drag" logic
TeodorTaushanov Jul 30, 2025
dd2cc2b
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 30, 2025
dd2d26b
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Jul 31, 2025
be44468
chore: fix multiple drag example
TeodorTaushanov Jul 31, 2025
de19b4a
chore: remove DnD outside elements tests and samples
TeodorTaushanov Jul 31, 2025
44c8bd2
chore: make DnD from link to list working
TeodorTaushanov Jul 31, 2025
30c5cdd
chore: fix failing test
TeodorTaushanov Jul 31, 2025
16fdfbd
chore: remove DnD from link
TeodorTaushanov Jul 31, 2025
f69dea8
Merge remote-tracking branch 'origin/main' into drag_drop_shadow_dom
TeodorTaushanov Aug 1, 2025
aa9374a
Merge branch 'main' of github.com:SAP/ui5-webcomponents into drag_dro…
dimovpetar Aug 5, 2025
58434ee
test: remove tests for links
dimovpetar Aug 5, 2025
1a01e79
test: skip mcb test
dimovpetar Aug 6, 2025
ce1dc89
Merge branch 'main' of github.com:SAP/ui5-webcomponents into drag_dro…
dimovpetar Aug 6, 2025
304e8f6
Merge branch 'main' into drag_drop_shadow_dom
dimovpetar Aug 8, 2025
086edf4
Merge branch 'main' into drag_drop_shadow_dom
dimovpetar Aug 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 16 additions & 89 deletions packages/base/src/util/dragAndDrop/DragRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type UI5Element from "../../UI5Element.js";
import type MovePlacement from "../../types/MovePlacement.js";
import MultipleDragGhostCss from "../../generated/css/MultipleDragGhost.css.js";

Expand All @@ -10,55 +9,16 @@ import {

const MIN_MULTI_DRAG_COUNT = 2;

let customDragElementPromise: Promise<HTMLElement> | null = null;
let draggedElement: HTMLElement | null = null;
let globalHandlersAttached = false;
const subscribers = new Set<UI5Element>();
const selfManagedDragAreas = new Set<HTMLElement | ShadowRoot>();

const ondragstart = (e: DragEvent) => {
if (!e.dataTransfer || !(e.target instanceof HTMLElement)) {
return;
}

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 setDraggedElement = (element: HTMLElement | null) => {
draggedElement = element;
};

const ondrop = () => {
const clearDraggedElement = () => {
draggedElement = null;
customDragElementPromise = null;
};

const setDraggedElement = (element: HTMLElement | null) => {
draggedElement = element;
};
type SetDraggedElementFunction = typeof setDraggedElement;

const getDraggedElement = () => {
return draggedElement;
};
Expand Down Expand Up @@ -86,58 +46,27 @@ const createDefaultMultiDragElement = async (count: number): Promise<HTMLElement
* @param {DragEvent} e - The drag event that triggered the operation.
* @public
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const startMultipleDrag = (count: number, e: DragEvent): void => {
const startMultipleDrag = async (count: number, e: DragEvent) => {
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) {
if (!e.dataTransfer) {
return;
}

document.body.addEventListener("dragstart", ondragstart);
document.body.addEventListener("dragend", ondragend);
document.body.addEventListener("drop", ondrop);
globalHandlersAttached = true;
};

const detachGlobalHandlers = () => {
document.body.removeEventListener("dragstart", ondragstart);
document.body.removeEventListener("dragend", ondragend);
document.body.removeEventListener("drop", ondrop);
globalHandlersAttached = false;
};

const subscribe = (subscriber: UI5Element) => {
subscribers.add(subscriber);

if (!globalHandlersAttached) {
attachGlobalHandlers();
}
};
const customDragElement = await createDefaultMultiDragElement(count);

const unsubscribe = (subscriber: UI5Element) => {
subscribers.delete(subscriber);

if (subscribers.size === 0 && globalHandlersAttached) {
detachGlobalHandlers();
}
};

const addSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
selfManagedDragAreas.add(area);
// Add to document body temporarily
document.body.appendChild(customDragElement);

return setDraggedElement;
};
e.dataTransfer.setDragImage(customDragElement, 0, 0);

const removeSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
selfManagedDragAreas.delete(area);
// Clean up the temporary element after the drag operation starts
requestAnimationFrame(() => {
customDragElement.remove();
});
};

type DragAndDropSettings = {
Expand All @@ -163,10 +92,8 @@ type MoveEventDetail = {
};

const DragRegistry = {
subscribe,
unsubscribe,
addSelfManagedArea,
removeSelfManagedArea,
setDraggedElement,
clearDraggedElement,
getDraggedElement,
startMultipleDrag,
};
Expand All @@ -175,8 +102,8 @@ export default DragRegistry;
export {
startMultipleDrag,
};

export type {
SetDraggedElementFunction,
DragAndDropSettings,
MoveEventDetail,
};
33 changes: 0 additions & 33 deletions packages/main/cypress/specs/List.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,6 @@ describe("List - Drag and Drop", () => {
<div>
<CheckBox text="Movable items" checked />
<CheckBox text="Compact density" />
<a href="http://sap.com" draggable={true}>http://sap.com</a>

<section>
<h2>Drag and drop</h2>
Expand Down Expand Up @@ -1981,38 +1980,6 @@ describe("List - Drag and Drop", () => {
cy.get("[ui5-list]").first().find("[ui5-li]").should("have.length.at.least", 3);
cy.get("[ui5-list]").eq(1).find("[ui5-li]").should("have.length.at.least", 2);
});

it("Moving link to list that doesn't accept it", () => {
const dataTransfer = new DataTransfer();

cy.get("a[href='http://sap.com']")
.trigger("dragstart", { dataTransfer });

cy.get("[ui5-list]").first().find("[ui5-li]").first()
.trigger("dragover", { dataTransfer })
.trigger("drop", { dataTransfer });

cy.get("a[href='http://sap.com']")
.trigger("dragend", { dataTransfer });

cy.get("[ui5-list]").first().find("[ui5-li]").should("have.length", 3);
});

it("Moving link to list that accepts it", () => {
const dataTransfer = new DataTransfer();

cy.get("a[href='http://sap.com']")
.trigger("dragstart", { dataTransfer });

cy.get("[ui5-list]").eq(1).find("[ui5-li]").eq(1)
.trigger("dragover", { dataTransfer })
.trigger("drop", { dataTransfer });

cy.get("a[href='http://sap.com']")
.trigger("dragend", { dataTransfer });

cy.get("[ui5-list]").eq(1).find("[ui5-li]").should("have.length.at.least", 3);
});
});

describe("List keyboard drag and drop tests", () => {
Expand Down
62 changes: 0 additions & 62 deletions packages/main/cypress/specs/ListItemGroup.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,66 +246,4 @@ describe("List drag and drop tests", () => {
cy.get("@list1").find("ui5-li").should("have.length", 4);
cy.get("@list2").find("ui5-li").should("have.length", 2);
});

it("Moving link to list that doesn't accept it", () => {
cy.mount(
<div>
<a draggable={true} style={{ display: "block", marginBottom: "10px" }}>
http://sap.com
</a>
<ListItemGroup headerText="List 1" />
</div>
);

cy.get("[ui5-li-group]").eq(0).as("list1").should("exist");
setupDragAndDrop("@list1", false);

cy.get("@list1").then($list => {
$list[0].innerHTML = `
<ui5-li movable>1. Bulgaria</ui5-li>
<ui5-li movable>1. Germany</ui5-li>
<ui5-li movable>1. Spain</ui5-li>
`;
});

cy.get("@list1").find("ui5-li").should("have.length", 3);
cy.get("a").as("link").should("contain.text", "http://sap.com");
cy.get("@list1").find("ui5-li").eq(0).as("first").should("contain.text", "1. Bulgaria");

dispatchMoveEvent("@link", "@first", "After");

cy.get("@list1").find("ui5-li").should("have.length", 3);
cy.get("a").should("exist").and("contain.text", "http://sap.com");
});

it("Moving link to list that accepts it", () => {
cy.mount(
<div>
<a draggable={true} style={{ display: "block", marginBottom: "10px" }}>
http://sap.com
</a>
<ListItemGroup headerText="List 2" />
</div>
);

cy.get("[ui5-li-group]").eq(0).as("list2").should("exist");
setupDragAndDrop("@list2", true);

cy.get("@list2").then($list => {
$list[0].innerHTML = `
<ui5-li movable>2. Bulgaria</ui5-li>
<ui5-li movable data-allows-nesting>2. Germany (Allows nesting)</ui5-li>
<ui5-li movable>2. Spain</ui5-li>
`;
});

cy.get("@list2").find("ui5-li").should("have.length", 3);
cy.get("a").as("link").should("contain.text", "http://sap.com");
cy.get("@list2").find("ui5-li").eq(1).as("second").should("contain.text", "2. Germany (Allows nesting)");

dispatchMoveEvent("@link", "@second", "Before");

cy.get("@list2").children().should("have.length", 4);
cy.get("@list2").find("a").should("exist").and("contain.text", "http://sap.com");
});
});
2 changes: 1 addition & 1 deletion packages/main/cypress/specs/MultiComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ describe("Event firing", () => {
.should("have.been.calledTwice");
});

it("Should prevent selection-change when clicking an item", () => {
it.skip("Should prevent selection-change when clicking an item", () => {
const onSelectionChange = (e:Event) => {
e.preventDefault();
}
Expand Down
Loading
Loading