1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
import {Opt} from "../fields/Field";
import {DocumentView} from "../views/nodes/DocumentView";
import {DocumentDecorations} from "../DocumentDecorations";
import {SelectionManager} from "./SelectionManager";
export namespace DragManager {
export let rootId = "root";
let dragDiv: HTMLDivElement;
export enum DragButtons {
Left = 1, Right = 2, Both = Left | Right
}
interface DragOptions {
handlers: DragHandlers;
hideSource: boolean | (() => boolean);
}
export interface DragDropDisposer {
(): void;
}
export class DragCompleteEvent {
}
export interface DragHandlers {
dragComplete: (e: DragCompleteEvent) => void;
}
export interface DropOptions {
handlers: DropHandlers;
}
export class DropEvent {
constructor(readonly x: number, readonly y: number, readonly data: {[ id: string ]: any}) {}
}
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
if ("canDrop" in element.dataset) {
throw new Error("Element is already droppable, can't make it droppable again");
}
element.dataset[ "canDrop" ] = "true";
const handler = (e: Event) => {
const ce = e as CustomEvent<DropEvent>;
options.handlers.drop(e, ce.detail);
};
element.addEventListener("dashOnDrop", handler);
return () => {
element.removeEventListener("dashOnDrop", handler);
delete element.dataset[ "canDrop" ]
};
}
export function StartDrag(ele: HTMLElement, dragData: {[ id: string ]: any}, options: DragOptions) {
if (!dragDiv) {
const root = document.getElementById(rootId);
if (!root) {
throw new Error("No root element found");
}
dragDiv = document.createElement("div");
root.appendChild(dragDiv);
}
const w = ele.offsetWidth, h = ele.offsetHeight;
const rect = ele.getBoundingClientRect();
const scaleX = rect.width / w, scaleY = rect.height / h;
let x = rect.left, y = rect.top;
// const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
let dragElement = ele.cloneNode(true) as HTMLElement;
dragElement.style.opacity = "0.7";
dragElement.style.position = "absolute";
dragElement.style.transformOrigin = "0 0";
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
dragDiv.appendChild(dragElement);
let hideSource = false;
if (typeof options.hideSource === "boolean") {
hideSource = options.hideSource;
} else {
hideSource = options.hideSource();
}
const wasHidden = ele.hidden;
if (hideSource) {
ele.hidden = true;
}
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
x += e.movementX;
y += e.movementY;
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
};
const upHandler = (e: PointerEvent) => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
FinishDrag(dragElement, e, options, dragData);
if (hideSource && !wasHidden) {
ele.hidden = false;
}
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: {[ index: string ]: any}) {
dragDiv.removeChild(dragEle);
const target = document.elementFromPoint(e.x, e.y);
if (!target) {
return;
}
target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
bubbles: true,
detail: {
x: e.x,
y: e.y,
data: dragData
}
}));
options.handlers.dragComplete({});
}
}
|