Skip to content

Commit fb561aa

Browse files
fix(DnD): fix drag and drop in Shadow DOM (#11975)
* chore: add test page * fix(DnD): fix DnD in Shadow DOM * chore: fix lint error * chore: fix lint error * chore: change sample * chore: improve the examples * chore: skip tests * fix: fix clearDraggedElement usage * fix: fix lint errors * fix: test page and sample page * fix: resolve merge conflicts * fix: resolve merge conflicts * chore: update to lates DnD changes * chore: adapt code to latest multiple drag elements code * chore: add tab container tests * chore: move table logic to TableDragAndDrop file * chore: simplify "multiple drag" logic * chore: fix multiple drag example * chore: remove DnD outside elements tests and samples * chore: make DnD from link to list working * chore: fix failing test * chore: remove DnD from link * test: remove tests for links * test: skip mcb test * chore: resolve merge conflicts * chore: uncomment failing test --------- Co-authored-by: Petar Dimov <petar.dimov@sap.com> Co-authored-by: Petar Dimov <32839090+dimovpetar@users.noreply.github.com>
1 parent 2c97806 commit fb561aa

17 files changed

+1202
-222
lines changed
Lines changed: 16 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type UI5Element from "../../UI5Element.js";
21
import type MovePlacement from "../../types/MovePlacement.js";
32
import MultipleDragGhostCss from "../../generated/css/MultipleDragGhost.css.js";
43

@@ -10,55 +9,16 @@ import {
109

1110
const MIN_MULTI_DRAG_COUNT = 2;
1211

13-
let customDragElementPromise: Promise<HTMLElement> | null = null;
1412
let draggedElement: HTMLElement | null = null;
15-
let globalHandlersAttached = false;
16-
const subscribers = new Set<UI5Element>();
17-
const selfManagedDragAreas = new Set<HTMLElement | ShadowRoot>();
1813

19-
const ondragstart = (e: DragEvent) => {
20-
if (!e.dataTransfer || !(e.target instanceof HTMLElement)) {
21-
return;
22-
}
23-
24-
if (!selfManagedDragAreas.has(e.target)) {
25-
draggedElement = e.target;
26-
}
27-
28-
handleMultipleDrag(e);
29-
};
30-
31-
const handleMultipleDrag = async (e: DragEvent) => {
32-
if (!customDragElementPromise || !e.dataTransfer) {
33-
return;
34-
}
35-
const dragElement = await customDragElementPromise;
36-
// Add to document body temporarily
37-
document.body.appendChild(dragElement);
38-
39-
e.dataTransfer.setDragImage(dragElement, 0, 0);
40-
41-
// Clean up the temporary element after the drag operation starts
42-
requestAnimationFrame(() => {
43-
dragElement.remove();
44-
});
45-
};
46-
47-
const ondragend = () => {
48-
draggedElement = null;
49-
customDragElementPromise = null;
14+
const setDraggedElement = (element: HTMLElement | null) => {
15+
draggedElement = element;
5016
};
5117

52-
const ondrop = () => {
18+
const clearDraggedElement = () => {
5319
draggedElement = null;
54-
customDragElementPromise = null;
5520
};
5621

57-
const setDraggedElement = (element: HTMLElement | null) => {
58-
draggedElement = element;
59-
};
60-
type SetDraggedElementFunction = typeof setDraggedElement;
61-
6222
const getDraggedElement = () => {
6323
return draggedElement;
6424
};
@@ -86,58 +46,27 @@ const createDefaultMultiDragElement = async (count: number): Promise<HTMLElement
8646
* @param {DragEvent} e - The drag event that triggered the operation.
8747
* @public
8848
*/
89-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
90-
const startMultipleDrag = (count: number, e: DragEvent): void => {
49+
const startMultipleDrag = async (count: number, e: DragEvent) => {
9150
if (count < MIN_MULTI_DRAG_COUNT) {
9251
console.warn(`Cannot start multiple drag with count ${count}. Minimum is ${MIN_MULTI_DRAG_COUNT}.`); // eslint-disable-line
9352
return;
9453
}
9554

96-
customDragElementPromise = createDefaultMultiDragElement(count);
97-
};
98-
99-
const attachGlobalHandlers = () => {
100-
if (globalHandlersAttached) {
55+
if (!e.dataTransfer) {
10156
return;
10257
}
10358

104-
document.body.addEventListener("dragstart", ondragstart);
105-
document.body.addEventListener("dragend", ondragend);
106-
document.body.addEventListener("drop", ondrop);
107-
globalHandlersAttached = true;
108-
};
109-
110-
const detachGlobalHandlers = () => {
111-
document.body.removeEventListener("dragstart", ondragstart);
112-
document.body.removeEventListener("dragend", ondragend);
113-
document.body.removeEventListener("drop", ondrop);
114-
globalHandlersAttached = false;
115-
};
116-
117-
const subscribe = (subscriber: UI5Element) => {
118-
subscribers.add(subscriber);
119-
120-
if (!globalHandlersAttached) {
121-
attachGlobalHandlers();
122-
}
123-
};
59+
const customDragElement = await createDefaultMultiDragElement(count);
12460

125-
const unsubscribe = (subscriber: UI5Element) => {
126-
subscribers.delete(subscriber);
127-
128-
if (subscribers.size === 0 && globalHandlersAttached) {
129-
detachGlobalHandlers();
130-
}
131-
};
132-
133-
const addSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
134-
selfManagedDragAreas.add(area);
61+
// Add to document body temporarily
62+
document.body.appendChild(customDragElement);
13563

136-
return setDraggedElement;
137-
};
64+
e.dataTransfer.setDragImage(customDragElement, 0, 0);
13865

139-
const removeSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
140-
selfManagedDragAreas.delete(area);
66+
// Clean up the temporary element after the drag operation starts
67+
requestAnimationFrame(() => {
68+
customDragElement.remove();
69+
});
14170
};
14271

14372
type DragAndDropSettings = {
@@ -163,10 +92,8 @@ type MoveEventDetail = {
16392
};
16493

16594
const DragRegistry = {
166-
subscribe,
167-
unsubscribe,
168-
addSelfManagedArea,
169-
removeSelfManagedArea,
95+
setDraggedElement,
96+
clearDraggedElement,
17097
getDraggedElement,
17198
startMultipleDrag,
17299
};
@@ -175,8 +102,8 @@ export default DragRegistry;
175102
export {
176103
startMultipleDrag,
177104
};
105+
178106
export type {
179-
SetDraggedElementFunction,
180107
DragAndDropSettings,
181108
MoveEventDetail,
182109
};

packages/main/cypress/specs/List.cy.tsx

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,7 +1777,6 @@ describe("List - Drag and Drop", () => {
17771777
<div>
17781778
<CheckBox text="Movable items" checked />
17791779
<CheckBox text="Compact density" />
1780-
<a href="http://sap.com" draggable={true}>http://sap.com</a>
17811780

17821781
<section>
17831782
<h2>Drag and drop</h2>
@@ -1981,38 +1980,6 @@ describe("List - Drag and Drop", () => {
19811980
cy.get("[ui5-list]").first().find("[ui5-li]").should("have.length.at.least", 3);
19821981
cy.get("[ui5-list]").eq(1).find("[ui5-li]").should("have.length.at.least", 2);
19831982
});
1984-
1985-
it("Moving link to list that doesn't accept it", () => {
1986-
const dataTransfer = new DataTransfer();
1987-
1988-
cy.get("a[href='http://sap.com']")
1989-
.trigger("dragstart", { dataTransfer });
1990-
1991-
cy.get("[ui5-list]").first().find("[ui5-li]").first()
1992-
.trigger("dragover", { dataTransfer })
1993-
.trigger("drop", { dataTransfer });
1994-
1995-
cy.get("a[href='http://sap.com']")
1996-
.trigger("dragend", { dataTransfer });
1997-
1998-
cy.get("[ui5-list]").first().find("[ui5-li]").should("have.length", 3);
1999-
});
2000-
2001-
it("Moving link to list that accepts it", () => {
2002-
const dataTransfer = new DataTransfer();
2003-
2004-
cy.get("a[href='http://sap.com']")
2005-
.trigger("dragstart", { dataTransfer });
2006-
2007-
cy.get("[ui5-list]").eq(1).find("[ui5-li]").eq(1)
2008-
.trigger("dragover", { dataTransfer })
2009-
.trigger("drop", { dataTransfer });
2010-
2011-
cy.get("a[href='http://sap.com']")
2012-
.trigger("dragend", { dataTransfer });
2013-
2014-
cy.get("[ui5-list]").eq(1).find("[ui5-li]").should("have.length.at.least", 3);
2015-
});
20161983
});
20171984

20181985
describe("List keyboard drag and drop tests", () => {

packages/main/cypress/specs/ListItemGroup.cy.tsx

Lines changed: 2 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe("ListItemGroup Tests", () => {
77
cy.mount(<ListItemGroup headerText="New Items" />);
88

99
cy.get("[ui5-li-group]").should("exist");
10-
10+
1111
cy.get("[ui5-li-group]")
1212
.shadow()
1313
.find("ui5-li-group-header")
@@ -95,7 +95,7 @@ describe("List drag and drop tests", () => {
9595
destination: { element: $target[0], placement }
9696
}
9797
});
98-
98+
9999
const listElement = $target[0].closest("[ui5-li-group]");
100100
if (listElement) {
101101
listElement.dispatchEvent(moveEvent);
@@ -248,68 +248,6 @@ describe("List drag and drop tests", () => {
248248
cy.get("@list1").find("ui5-li").should("have.length", 4);
249249
cy.get("@list2").find("ui5-li").should("have.length", 2);
250250
});
251-
252-
it("Moving link to list that doesn't accept it", () => {
253-
cy.mount(
254-
<div>
255-
<a draggable={true} style={{ display: "block", marginBottom: "10px" }}>
256-
http://sap.com
257-
</a>
258-
<ListItemGroup headerText="List 1" />
259-
</div>
260-
);
261-
262-
cy.get("[ui5-li-group]").eq(0).as("list1").should("exist");
263-
setupDragAndDrop("@list1", false);
264-
265-
cy.get("@list1").then($list => {
266-
$list[0].innerHTML = `
267-
<ui5-li movable>1. Bulgaria</ui5-li>
268-
<ui5-li movable>1. Germany</ui5-li>
269-
<ui5-li movable>1. Spain</ui5-li>
270-
`;
271-
});
272-
273-
cy.get("@list1").find("ui5-li").should("have.length", 3);
274-
cy.get("a").as("link").should("contain.text", "http://sap.com");
275-
cy.get("@list1").find("ui5-li").eq(0).as("first").should("contain.text", "1. Bulgaria");
276-
277-
dispatchMoveEvent("@link", "@first", "After");
278-
279-
cy.get("@list1").find("ui5-li").should("have.length", 3);
280-
cy.get("a").should("exist").and("contain.text", "http://sap.com");
281-
});
282-
283-
it("Moving link to list that accepts it", () => {
284-
cy.mount(
285-
<div>
286-
<a draggable={true} style={{ display: "block", marginBottom: "10px" }}>
287-
http://sap.com
288-
</a>
289-
<ListItemGroup headerText="List 2" />
290-
</div>
291-
);
292-
293-
cy.get("[ui5-li-group]").eq(0).as("list2").should("exist");
294-
setupDragAndDrop("@list2", true);
295-
296-
cy.get("@list2").then($list => {
297-
$list[0].innerHTML = `
298-
<ui5-li movable>2. Bulgaria</ui5-li>
299-
<ui5-li movable data-allows-nesting>2. Germany (Allows nesting)</ui5-li>
300-
<ui5-li movable>2. Spain</ui5-li>
301-
`;
302-
});
303-
304-
cy.get("@list2").find("ui5-li").should("have.length", 3);
305-
cy.get("a").as("link").should("contain.text", "http://sap.com");
306-
cy.get("@list2").find("ui5-li").eq(1).as("second").should("contain.text", "2. Germany (Allows nesting)");
307-
308-
dispatchMoveEvent("@link", "@second", "Before");
309-
310-
cy.get("@list2").children().should("have.length", 4);
311-
cy.get("@list2").find("a").should("exist").and("contain.text", "http://sap.com");
312-
});
313251
});
314252

315253
describe("Focus", () => {

0 commit comments

Comments
 (0)