forked from galaxyproject/galaxy
-
Notifications
You must be signed in to change notification settings - Fork 1
/
useDraggable.js
127 lines (118 loc) · 3.92 KB
/
useDraggable.js
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
127
import { computed, ref } from "vue-demi";
import { isClient, resolveUnref, toRefs } from "@vueuse/shared";
import { useEventListener } from "@vueuse/core";
/**
* Make elements draggable.
*
* @see https://vueuse.org/useDraggable
* @param target
* @param options
*/
export function useDraggable(target, options = {}) {
const draggingElement = options.draggingElement ?? window;
const draggingHandle = options.handle ?? target;
const position = ref(resolveUnref(options.initialValue) ?? { x: 0, y: 0 });
const pressedDelta = ref();
const cleanUpPointerMove = ref();
const cleanUpDragMove = ref();
const filterEvent = (e) => {
if (options.pointerTypes) {
return options.pointerTypes.includes(e.pointerType);
}
return true;
};
const handleEvent = (e) => {
if (resolveUnref(options.preventDefault)) {
e.preventDefault();
}
if (resolveUnref(options.stopPropagation)) {
e.stopPropagation();
}
};
const start = (e) => {
if (!filterEvent(e)) {
return;
}
if (resolveUnref(options.exact) && e.target !== resolveUnref(target)) {
return;
}
const rect = resolveUnref(target)?.getBoundingClientRect();
const pos = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
if (options.onStart?.(pos, e) === false) {
return;
}
pressedDelta.value = pos;
// define move inline because the event lister scope never receives updates to the initial pressedDelta value
// (interestingly not a problem if using move directly)
const move = (e) => {
if (!filterEvent(e)) {
return;
}
if (!pressedDelta.value) {
return;
}
position.value = {
x: e.clientX - pressedDelta.value.x,
y: e.clientY - pressedDelta.value.y,
};
options.onMove?.(position.value, e);
handleEvent(e);
};
function throttleMove(e) {
return throttledWrite(() => move(e));
}
cleanUpPointerMove.value && cleanUpPointerMove.value();
cleanUpDragMove.value && cleanUpDragMove.value();
cleanUpDragMove.value = useEventListener(window, "dragover", throttleMove, options.useCapture ?? true);
cleanUpPointerMove.value = useEventListener(
draggingElement,
"pointermove",
throttleMove,
options.useCapture ?? true
);
handleEvent(e);
};
const end = (e) => {
if (!filterEvent(e)) {
return;
}
if (!pressedDelta.value) {
return;
}
pressedDelta.value = undefined;
options.onEnd?.(position.value, e);
handleEvent(e);
cleanUpPointerMove.value && cleanUpPointerMove.value();
cleanUpDragMove.value && cleanUpDragMove.value();
};
if (isClient) {
const useCapture = options.useCapture ?? true;
useEventListener(draggingHandle, "dragstart", start, useCapture);
useEventListener(draggingHandle, "pointerdown", start, useCapture);
useEventListener(draggingElement, "dragend", end, useCapture);
useEventListener(draggingElement, "pointerup", end, useCapture);
}
return {
...toRefs(position),
position,
isDragging: computed(() => !!pressedDelta.value),
style: computed(() => `left:${position.value.x}px;top:${position.value.y}px;`),
};
}
function throttle(timer) {
let queuedCallback;
return (callback) => {
if (!queuedCallback) {
timer(() => {
const cb = queuedCallback;
queuedCallback = null;
cb();
});
}
queuedCallback = callback;
};
}
const throttledWrite = throttle(requestAnimationFrame);