Skip to content

Commit c4383ea

Browse files
authored
feat(ui5-tabcontainer, ui5-list): add events for reordering items by mouse (#8265)
BGSOFUIRODOPI-3189
1 parent 25548a9 commit c4383ea

34 files changed

+1483
-39
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Placements of a moved element relative to a target element.
3+
*
4+
* @public
5+
*/
6+
enum MovePlacement {
7+
/**
8+
* @public
9+
*/
10+
On = "On",
11+
12+
/**
13+
* @public
14+
*/
15+
Before = "Before",
16+
17+
/**
18+
* @public
19+
*/
20+
After = "After",
21+
}
22+
23+
export default MovePlacement;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Determines the orientation of an operation.
3+
*
4+
* @private
5+
*/
6+
enum Orientation {
7+
/**
8+
* @private
9+
*/
10+
Vertical = "Vertical",
11+
12+
/**
13+
* @private
14+
*/
15+
Horizontal = "Horizontal",
16+
}
17+
18+
export default Orientation;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import type UI5Element from "../../UI5Element";
2+
3+
let draggedElement: HTMLElement | null = null;
4+
let globalHandlersAttached = false;
5+
const subscribers = new Set<UI5Element>();
6+
const selfManagedDragAreas = new Set<HTMLElement | ShadowRoot>();
7+
8+
const ondragstart = (e: DragEvent) => {
9+
if (!e.dataTransfer || !(e.target instanceof HTMLElement)) {
10+
return;
11+
}
12+
13+
e.dataTransfer.dropEffect = "move";
14+
e.dataTransfer.effectAllowed = "move";
15+
16+
if (!selfManagedDragAreas.has(e.target)) {
17+
draggedElement = e.target;
18+
}
19+
};
20+
21+
const ondragend = () => {
22+
draggedElement = null;
23+
};
24+
25+
const ondrop = () => {
26+
draggedElement = null;
27+
};
28+
29+
const setDraggedElement = (element: HTMLElement | null) => {
30+
draggedElement = element;
31+
};
32+
type SetDraggedElementFunction = typeof setDraggedElement;
33+
34+
const getDraggedElement = () => {
35+
return draggedElement;
36+
};
37+
38+
const attachGlobalHandlers = () => {
39+
if (globalHandlersAttached) {
40+
return;
41+
}
42+
43+
document.body.addEventListener("dragstart", ondragstart);
44+
document.body.addEventListener("dragend", ondragend);
45+
document.body.addEventListener("drop", ondrop);
46+
};
47+
48+
const detachGlobalHandlers = () => {
49+
document.body.removeEventListener("dragstart", ondragstart);
50+
document.body.removeEventListener("dragend", ondragend);
51+
document.body.removeEventListener("drop", ondrop);
52+
globalHandlersAttached = false;
53+
};
54+
55+
const subscribe = (subscriber: UI5Element) => {
56+
subscribers.add(subscriber);
57+
58+
if (!globalHandlersAttached) {
59+
attachGlobalHandlers();
60+
}
61+
};
62+
63+
const unsubscribe = (subscriber: UI5Element) => {
64+
subscribers.delete(subscriber);
65+
66+
if (subscribers.size === 0 && globalHandlersAttached) {
67+
detachGlobalHandlers();
68+
}
69+
};
70+
71+
const addSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
72+
selfManagedDragAreas.add(area);
73+
74+
return setDraggedElement;
75+
};
76+
77+
const removeSelfManagedArea = (area: HTMLElement | ShadowRoot) => {
78+
selfManagedDragAreas.delete(area);
79+
};
80+
81+
const DragRegistry = {
82+
subscribe,
83+
unsubscribe,
84+
addSelfManagedArea,
85+
removeSelfManagedArea,
86+
getDraggedElement,
87+
};
88+
89+
export default DragRegistry;
90+
export type {
91+
SetDraggedElementFunction,
92+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import MovePlacement from "../../types/MovePlacement.js";
2+
import Orientation from "../../types/Orientation.js";
3+
4+
const closestPlacement = (point: number, beforePoint: number, centerPoint: number, afterPoint: number) => {
5+
const distToBeforePoint = Math.abs(point - beforePoint);
6+
const distToCenterPoint = Math.abs(point - centerPoint);
7+
const distToAfterPoint = Math.abs(point - afterPoint);
8+
const closestPoint = Math.min(
9+
distToBeforePoint,
10+
distToCenterPoint,
11+
distToAfterPoint,
12+
);
13+
let placements: Array<MovePlacement> = [];
14+
15+
switch (closestPoint) {
16+
case distToBeforePoint:
17+
placements = [MovePlacement.Before];
18+
break;
19+
case distToCenterPoint:
20+
placements = [MovePlacement.On, distToBeforePoint < distToAfterPoint ? MovePlacement.Before : MovePlacement.After];
21+
break;
22+
case distToAfterPoint:
23+
placements = [MovePlacement.After];
24+
break;
25+
}
26+
27+
return placements;
28+
};
29+
30+
const findClosestPosition = (elements: Array<HTMLElement>, point: number, layoutOrientation: Orientation) => {
31+
let shortestDist = Number.POSITIVE_INFINITY;
32+
let closestElement: HTMLElement | null = null;
33+
34+
// determine which element is most closest to the point
35+
for (let i = 0; i < elements.length; i++) {
36+
const el = elements[i];
37+
const {
38+
left, width, top, height,
39+
} = el.getBoundingClientRect();
40+
41+
let elemCenter;
42+
if (layoutOrientation === Orientation.Vertical) {
43+
elemCenter = top + height / 2;
44+
} else { // Horizontal
45+
elemCenter = left + width / 2;
46+
}
47+
48+
const distanceToCenter = Math.abs(point - elemCenter);
49+
50+
if (distanceToCenter < shortestDist) {
51+
shortestDist = distanceToCenter;
52+
closestElement = el;
53+
}
54+
}
55+
56+
if (!closestElement) {
57+
return null;
58+
}
59+
60+
const {
61+
width, height, left, right, top, bottom,
62+
} = closestElement.getBoundingClientRect();
63+
let placements;
64+
65+
if (layoutOrientation === Orientation.Vertical) {
66+
placements = closestPlacement(point, top, top + height / 2, bottom);
67+
} else { // Horizontal
68+
placements = closestPlacement(point, left, left + width / 2, right);
69+
}
70+
71+
return {
72+
element: closestElement,
73+
placements,
74+
};
75+
};
76+
77+
export default findClosestPosition;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
let lastTarget: HTMLElement | null = null;
2+
let lastTargetDragOverStart = Date.now();
3+
const LONG_DRAG_OVER_THRESHOLD = 300;
4+
5+
const longDragOverHandler = (targetsSelector: string) => {
6+
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<(arg0: DragEvent, arg1: boolean) => any>) => {
7+
const origHandler = descriptor.value!;
8+
9+
descriptor.value = function handleDragOver(e: DragEvent) {
10+
let isLongDragOver = false;
11+
12+
if (e.target instanceof HTMLElement) {
13+
const currentTarget = e.target.closest<HTMLElement>(targetsSelector);
14+
15+
if (currentTarget === lastTarget && Date.now() - lastTargetDragOverStart >= LONG_DRAG_OVER_THRESHOLD) {
16+
isLongDragOver = true;
17+
} else if (currentTarget !== lastTarget) {
18+
lastTarget = currentTarget;
19+
lastTargetDragOverStart = Date.now();
20+
}
21+
}
22+
23+
origHandler.apply(this, [e, isLongDragOver]);
24+
};
25+
26+
return descriptor;
27+
};
28+
};
29+
30+
export default longDragOverHandler;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div class="{{classes.root}}"></div>

0 commit comments

Comments
 (0)