Skip to content

Commit

Permalink
feat(ui5-tabcontainer, ui5-list): add events for reordering items by …
Browse files Browse the repository at this point in the history
…mouse (#8265)

BGSOFUIRODOPI-3189
  • Loading branch information
dimovpetar committed Mar 14, 2024
1 parent 25548a9 commit c4383ea
Show file tree
Hide file tree
Showing 34 changed files with 1,483 additions and 39 deletions.
23 changes: 23 additions & 0 deletions packages/base/src/types/MovePlacement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Placements of a moved element relative to a target element.
*
* @public
*/
enum MovePlacement {
/**
* @public
*/
On = "On",

/**
* @public
*/
Before = "Before",

/**
* @public
*/
After = "After",
}

export default MovePlacement;
18 changes: 18 additions & 0 deletions packages/base/src/types/Orientation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Determines the orientation of an operation.
*
* @private
*/
enum Orientation {
/**
* @private
*/
Vertical = "Vertical",

/**
* @private
*/
Horizontal = "Horizontal",
}

export default Orientation;
92 changes: 92 additions & 0 deletions packages/base/src/util/dragAndDrop/DragRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type UI5Element from "../../UI5Element";

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;
}

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

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

const ondragend = () => {
draggedElement = null;
};

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

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

const getDraggedElement = () => {
return draggedElement;
};

const attachGlobalHandlers = () => {
if (globalHandlersAttached) {
return;
}

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

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 unsubscribe = (subscriber: UI5Element) => {
subscribers.delete(subscriber);

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

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

return setDraggedElement;
};

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

const DragRegistry = {
subscribe,
unsubscribe,
addSelfManagedArea,
removeSelfManagedArea,
getDraggedElement,
};

export default DragRegistry;
export type {
SetDraggedElementFunction,
};
77 changes: 77 additions & 0 deletions packages/base/src/util/dragAndDrop/findClosestPosition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import MovePlacement from "../../types/MovePlacement.js";
import Orientation from "../../types/Orientation.js";

const closestPlacement = (point: number, beforePoint: number, centerPoint: number, afterPoint: number) => {
const distToBeforePoint = Math.abs(point - beforePoint);
const distToCenterPoint = Math.abs(point - centerPoint);
const distToAfterPoint = Math.abs(point - afterPoint);
const closestPoint = Math.min(
distToBeforePoint,
distToCenterPoint,
distToAfterPoint,
);
let placements: Array<MovePlacement> = [];

switch (closestPoint) {
case distToBeforePoint:
placements = [MovePlacement.Before];
break;
case distToCenterPoint:
placements = [MovePlacement.On, distToBeforePoint < distToAfterPoint ? MovePlacement.Before : MovePlacement.After];
break;
case distToAfterPoint:
placements = [MovePlacement.After];
break;
}

return placements;
};

const findClosestPosition = (elements: Array<HTMLElement>, point: number, layoutOrientation: Orientation) => {
let shortestDist = Number.POSITIVE_INFINITY;
let closestElement: HTMLElement | null = null;

// determine which element is most closest to the point
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
const {
left, width, top, height,
} = el.getBoundingClientRect();

let elemCenter;
if (layoutOrientation === Orientation.Vertical) {
elemCenter = top + height / 2;
} else { // Horizontal
elemCenter = left + width / 2;
}

const distanceToCenter = Math.abs(point - elemCenter);

if (distanceToCenter < shortestDist) {
shortestDist = distanceToCenter;
closestElement = el;
}
}

if (!closestElement) {
return null;
}

const {
width, height, left, right, top, bottom,
} = closestElement.getBoundingClientRect();
let placements;

if (layoutOrientation === Orientation.Vertical) {
placements = closestPlacement(point, top, top + height / 2, bottom);
} else { // Horizontal
placements = closestPlacement(point, left, left + width / 2, right);
}

return {
element: closestElement,
placements,
};
};

export default findClosestPosition;
30 changes: 30 additions & 0 deletions packages/base/src/util/dragAndDrop/longDragOverHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
let lastTarget: HTMLElement | null = null;
let lastTargetDragOverStart = Date.now();
const LONG_DRAG_OVER_THRESHOLD = 300;

const longDragOverHandler = (targetsSelector: string) => {
return (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<(arg0: DragEvent, arg1: boolean) => any>) => {
const origHandler = descriptor.value!;

descriptor.value = function handleDragOver(e: DragEvent) {
let isLongDragOver = false;

if (e.target instanceof HTMLElement) {
const currentTarget = e.target.closest<HTMLElement>(targetsSelector);

if (currentTarget === lastTarget && Date.now() - lastTargetDragOverStart >= LONG_DRAG_OVER_THRESHOLD) {
isLongDragOver = true;
} else if (currentTarget !== lastTarget) {
lastTarget = currentTarget;
lastTargetDragOverStart = Date.now();
}
}

origHandler.apply(this, [e, isLongDragOver]);
};

return descriptor;
};
};

export default longDragOverHandler;
1 change: 1 addition & 0 deletions packages/main/src/DropIndicator.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="{{classes.root}}"></div>

0 comments on commit c4383ea

Please sign in to comment.