Skip to content

fix(DnD): event targets are now correct in each document #12115

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 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 17 additions & 25 deletions packages/base/src/util/dragAndDrop/DragRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,17 @@ 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>();
let draggedElements: Array<EventTarget> = [];

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

if (!selfManagedDragAreas.has(e.target)) {
draggedElement = e.target;
}
draggedElements = e.composedPath();

handleMultipleDrag(e);
};
Expand All @@ -45,22 +43,29 @@ const handleMultipleDrag = async (e: DragEvent) => {
};

const ondragend = () => {
draggedElement = null;
draggedElements = [];
customDragElementPromise = null;
};

const ondrop = () => {
draggedElement = null;
draggedElements = [];
customDragElementPromise = null;
};

const setDraggedElement = (element: HTMLElement | null) => {
draggedElement = element;
};
type SetDraggedElementFunction = typeof setDraggedElement;
const getDraggedElement = (rootNode: Node) => {
// get only elements that are part of the current rootNode
const _draggedElements = draggedElements.filter((el): el is HTMLElement => el instanceof HTMLElement && el.getRootNode() === rootNode);

if (_draggedElements.length === 0) {
return null;
}

// special handling for TabContainer. Maybe add generic method to UI5Element and override it in TabContainer
if (_draggedElements[0].hasAttribute("ui5-tabcontainer")) {
return ((_draggedElements[0] as unknown) as { _getDraggedElement: () => HTMLElement | null })._getDraggedElement();
}

const getDraggedElement = () => {
return draggedElement;
return _draggedElements[0];
};

const createDefaultMultiDragElement = async (count: number): Promise<HTMLElement> => {
Expand Down Expand Up @@ -130,16 +135,6 @@ const unsubscribe = (subscriber: UI5Element) => {
}
};

const addSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
selfManagedDragAreas.add(area);

return setDraggedElement;
};

const removeSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
selfManagedDragAreas.delete(area);
};

type DragAndDropSettings = {
/**
* Allow cross-browser and file drag and drop.
Expand All @@ -165,8 +160,6 @@ type MoveEventDetail = {
const DragRegistry = {
subscribe,
unsubscribe,
addSelfManagedArea,
removeSelfManagedArea,
getDraggedElement,
startMultipleDrag,
};
Expand All @@ -176,7 +169,6 @@ export {
startMultipleDrag,
};
export type {
SetDraggedElementFunction,
DragAndDropSettings,
MoveEventDetail,
};
2 changes: 1 addition & 1 deletion packages/base/src/util/dragAndDrop/handleDragOver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type DragPosition = {
* Handles the dragover event.
*/
function handleDragOver<T extends UI5Element>(e: DragEvent, component: T, position: DragPosition, target: HTMLElement, settings: DragAndDropSettings = {}): DragOverResult {
const draggedElement = DragRegistry.getDraggedElement();
const draggedElement = DragRegistry.getDraggedElement(component.getRootNode());
const dragOverResult: DragOverResult = {
targetReference: null,
placement: null,
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/util/dragAndDrop/handleDrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import DragRegistry from "./DragRegistry.js";

function handleDrop<T extends UI5Element>(e: DragEvent, component: T, target: HTMLElement, placement: `${MovePlacement}`, settings: DragAndDropSettings = {}): void {
e.preventDefault();
const draggedElement = DragRegistry.getDraggedElement();
const draggedElement = DragRegistry.getDraggedElement(component.getRootNode());

if (!draggedElement && settings?.crossDnD) {
return;
Expand Down
23 changes: 10 additions & 13 deletions packages/main/src/TabContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import Orientation from "@ui5/webcomponents-base/dist/types/Orientation.js";
import DragRegistry from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js";
import handleDragOver from "@ui5/webcomponents-base/dist/util/dragAndDrop/handleDragOver.js";
import handleDrop from "@ui5/webcomponents-base/dist/util/dragAndDrop/handleDrop.js";
import type { SetDraggedElementFunction } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js";
import longDragOverHandler from "@ui5/webcomponents-base/dist/util/dragAndDrop/longDragOverHandler.js";
import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js";
import {
Expand Down Expand Up @@ -354,7 +353,6 @@ class TabContainer extends UI5Element {
responsivePopover?: ResponsivePopover;
_hasScheduledPopoverOpen = false;
_handleResizeBound: () => void;
_setDraggedElement?: SetDraggedElementFunction;

static registerTabStyles(styles: string) {
tabStyles.push(styles);
Expand Down Expand Up @@ -434,7 +432,7 @@ class TabContainer extends UI5Element {
onEnterDOM() {
ResizeHandler.register(this._getHeader(), this._handleResizeBound);
DragRegistry.subscribe(this);
this._setDraggedElement = DragRegistry.addSelfManagedArea(this);

if (isDesktop()) {
this.setAttribute("desktop", "");
}
Expand All @@ -443,8 +441,6 @@ class TabContainer extends UI5Element {
onExitDOM() {
ResizeHandler.deregister(this._getHeader(), this._handleResizeBound);
DragRegistry.unsubscribe(this);
DragRegistry.removeSelfManagedArea(this);
this._setDraggedElement = undefined;
}

_handleResize() {
Expand Down Expand Up @@ -495,15 +491,18 @@ class TabContainer extends UI5Element {
}
}

_getDraggedElement() {
return this.shadowRoot?.querySelector<TabInStrip>("[data-moving]")?.realTabReference
|| this.shadowRoot?.querySelector<TabInOverflow>(`#${this._id}-overflowMenu [data-moving]`);
}

_onDragStart(e: DragEvent) {
if (!e.dataTransfer || !(e.target instanceof HTMLElement)) {
return;
}

e.dataTransfer.dropEffect = "move";
e.dataTransfer.effectAllowed = "move";

this._setDraggedElement!((e.target as TabInStrip).realTabReference);
}

_onHeaderDragEnter(e: DragEvent) {
Expand All @@ -517,7 +516,7 @@ class TabContainer extends UI5Element {
return;
}

const draggedElement = DragRegistry.getDraggedElement();
const draggedElement = DragRegistry.getDraggedElement(this.getRootNode());
const closestPosition = findClosestPosition(
[...this._getTabStrip().querySelectorAll<HTMLElement>(`[role="tab"]:not([hidden])`)],
e.clientX,
Expand Down Expand Up @@ -625,7 +624,7 @@ class TabContainer extends UI5Element {

_onPopoverListMoveOver(e: CustomEvent<ListMoveEventDetail>) {
const { destination, source } = e.detail;
const draggedElement = DragRegistry.getDraggedElement()!;
const draggedElement = DragRegistry.getDraggedElement(this.getRootNode())!;
let destinationElement: HTMLElement = (destination.element as TabInStrip | TabSeparatorInStrip).realTabReference;

// workaround to simulate tree behavior
Expand Down Expand Up @@ -675,7 +674,7 @@ class TabContainer extends UI5Element {

_onPopoverListMove(e: CustomEvent<ListMoveEventDetail>) {
const { destination, source } = e.detail;
const draggedElement = DragRegistry.getDraggedElement()!;
const draggedElement = DragRegistry.getDraggedElement(this.getRootNode())!;
let destinationElement: HTMLElement = (destination.element as TabInStrip).realTabReference;

// Workaround to simulate tree behavior
Expand Down Expand Up @@ -715,9 +714,7 @@ class TabContainer extends UI5Element {
}

_onPopoverListKeyDown(e: KeyboardEvent) {
if (isCtrl(e)) {
this._setDraggedElement!((e.target as TabInOverflow).realTabReference);
}
// TODO
}

async _onTabStripClick(e: Event) {
Expand Down
4 changes: 2 additions & 2 deletions packages/main/src/delegate/DragAndDropHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class DragAndDropHandler {
return;
}

const draggedElement = DragRegistry.getDraggedElement()!;
const draggedElement = DragRegistry.getDraggedElement(this.component.getRootNode())!;
const dropIndicator = this.config.getDropIndicator()!;
const closestPosition = this._findClosestPosition(e);

Expand Down Expand Up @@ -98,7 +98,7 @@ class DragAndDropHandler {
return false;
}

const draggedElement = DragRegistry.getDraggedElement();
const draggedElement = DragRegistry.getDraggedElement(this.component.getRootNode());
const dropIndicator = this.config.getDropIndicator();

return !!(draggedElement && dropIndicator);
Expand Down
107 changes: 107 additions & 0 deletions packages/main/test/pages/DragAndDropInShadowDom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Drag and Drop in Shadow DOM</title>

<script src="%VITE_BUNDLE_PATH%" type="module"></script>
</head>

<body>
<section>
<div id="host1">
<template shadowrootmode="open">
<section>
<h2>#host1 - move-over</h2>
<div>
<ui5-label show-colon>source.element</ui5-label>
<ui5-text id="sourceElement"></ui5-text>
</div>
<div>
<ui5-label show-colon>destination.element</ui5-label>
<ui5-text id="destinationElement"></ui5-text>
</div>
</section>
<ui5-list id="listDnd1" header-text="List 1">
<ui5-li id="bg" movable>Bulgaria</ui5-li>
<ui5-li id="de" movable>Germany</ui5-li>
<ui5-li id="es" movable>Spain</ui5-li>
</ui5-list>
<div id="host2">
<template shadowrootmode="open">
<section>
<h2>#host2 - move-over</h2>
<ui5-label show-colon>source.element</ui5-label>
<ui5-text id="sourceElement"></ui5-text>
<ui5-label show-colon>destination.element</ui5-label>
<ui5-text id="destinationElement"></ui5-text>
</section>
<ui5-list id="listDnd2" header-text="List 2">
<ui5-li id="gr" movable>Greece</ui5-li>
<ui5-li id="ro" movable>Romania</ui5-li>
<ui5-li id="srb" movable>Serbia</ui5-li>
</ui5-list>
</template>
</div>
</template>
</div>
</section>

<script>
const host1 = document.querySelector("#host1");
const host2 = host1.shadowRoot.querySelector("#host2");

function applyStyles() {
const sheet = new CSSStyleSheet();

sheet.replaceSync(`
:host {
display: block;
padding: 0.5rem;
}

:host(#host1) {
border: 2px solid red;
}

:host(#host2) {
border: 2px solid green;
}

[ui5-text] {
display: inline-block;
}
`);

host1.shadowRoot.adoptedStyleSheets = [sheet];
host2.shadowRoot.adoptedStyleSheets = [sheet];
}

applyStyles();

document.getElementById("host1").addEventListener("ui5-move-over", (e) => {
const { destination, source } = e.detail;

document.getElementById("sourceElement").textContent = source.element.id;
document.getElementById("destinationElement").textContent = destination.element.id;
});

host1.shadowRoot.getElementById("listDnd1").addEventListener("ui5-move-over", (e) => {
const { destination, source } = e.detail;

host1.shadowRoot.getElementById("sourceElement").textContent = source.element.id;
host1.shadowRoot.getElementById("destinationElement").textContent = destination.element.id;
});

host2.shadowRoot.getElementById("listDnd2").addEventListener("ui5-move-over", (e) => {
const { destination, source } = e.detail;

host2.shadowRoot.getElementById("destinationElement").textContent = destination.element.id;
host2.shadowRoot.getElementById("sourceElement").textContent = source.element.id;
});
</script>
</body>

</html>
Loading