first commit
This commit is contained in:
+203
@@ -0,0 +1,203 @@
|
||||
import { Util } from "./Util.js";
|
||||
export const DD = {
|
||||
get isDragging() {
|
||||
let flag = false;
|
||||
DD._dragElements.forEach((elem) => {
|
||||
if (elem.dragStatus === 'dragging') {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
return flag;
|
||||
},
|
||||
get node() {
|
||||
let node;
|
||||
for (const elem of DD._dragElements.values()) {
|
||||
if (elem.dragStatus === 'dragging')
|
||||
return elem.node;
|
||||
node = elem.node;
|
||||
}
|
||||
// isDragReady() also uses this getter before movement starts.
|
||||
return node;
|
||||
},
|
||||
_dragElements: new Map(),
|
||||
// Konva is imported into one window, but a stage may be rendered in another
|
||||
// one: an iframe, or a window opened with `window.open`. Such a window sends
|
||||
// its pointer events to itself, so `Konva.Stage` asks for every window it is
|
||||
// rendered in. The handlers get the window they listen to, as only that
|
||||
// window has pointer positions the stages of that window can use
|
||||
_listenToWindow(win) {
|
||||
const endDragBefore = (evt) => DD._endDragBefore(evt, win);
|
||||
const drag = (evt) => DD._batchEvents(() => DD._drag(evt, win), win);
|
||||
const endDragAfter = (evt) => DD._batchEvents(() => DD._endDragAfter(evt));
|
||||
win.addEventListener('mouseup', endDragBefore, true);
|
||||
win.addEventListener('touchend', endDragBefore, true);
|
||||
// add touchcancel to fix this: https://github.com/konvajs/konva/issues/1843
|
||||
win.addEventListener('touchcancel', endDragBefore, true);
|
||||
win.addEventListener('mousemove', drag);
|
||||
win.addEventListener('touchmove', drag);
|
||||
win.addEventListener('mouseup', endDragAfter, false);
|
||||
win.addEventListener('touchend', endDragAfter, false);
|
||||
win.addEventListener('touchcancel', endDragAfter, false);
|
||||
},
|
||||
_batchEvents(callback, win) {
|
||||
if (!DD._dragElements.size)
|
||||
return;
|
||||
// One scope per integration, even when several stages use the same callback.
|
||||
// Keep all positions and handlers in the original order across those stages.
|
||||
const batches = new Set();
|
||||
for (const { node } of DD._dragElements.values()) {
|
||||
const stage = node.getStage();
|
||||
if (!stage || (win && stage._getOwnerWindow() !== win))
|
||||
continue;
|
||||
const batch = stage.eventBatchFunc();
|
||||
if (batch)
|
||||
batches.add(batch);
|
||||
}
|
||||
let run = callback;
|
||||
for (const batch of batches) {
|
||||
const next = run;
|
||||
run = () => Util._batchEvents(batch, next);
|
||||
}
|
||||
run();
|
||||
},
|
||||
// methods
|
||||
_drag(evt, win) {
|
||||
const nodesToFireEvents = [];
|
||||
// reading the positions forces a layout, so do it once per stage
|
||||
const positioned = new Set();
|
||||
DD._dragElements.forEach((elem, key) => {
|
||||
const { node } = elem;
|
||||
// we need to find pointer relative to that node
|
||||
const stage = node.getStage();
|
||||
// a pointer position of another window is relative to another viewport,
|
||||
// it would drag the node to a random place
|
||||
if (win && stage._getOwnerWindow() !== win) {
|
||||
return;
|
||||
}
|
||||
if (!positioned.has(stage)) {
|
||||
stage.setPointersPositions(evt);
|
||||
positioned.add(stage);
|
||||
}
|
||||
if (elem.pointerEventType &&
|
||||
elem.pointerEventType !== stage._pointerEventType) {
|
||||
return;
|
||||
}
|
||||
// it is possible that user call startDrag without any event
|
||||
// it that case we need to detect first movable pointer and attach it into the node
|
||||
if (elem.pointerId === undefined) {
|
||||
elem.pointerId = Util._getFirstPointerId(evt);
|
||||
elem.pointerEventType = stage._pointerEventType;
|
||||
if (elem.dragStatus === 'dragging') {
|
||||
stage._cancelClick(elem.pointerId, elem.pointerEventType);
|
||||
}
|
||||
}
|
||||
const pos = stage._changedPointerPositions.find((pos) => pos.id === elem.pointerId);
|
||||
// not related pointer
|
||||
if (!pos) {
|
||||
return;
|
||||
}
|
||||
if (elem.dragStatus !== 'dragging') {
|
||||
const dragDistance = node.dragDistance();
|
||||
const distance = Math.max(Math.abs(pos.x - elem.startPointerPos.x), Math.abs(pos.y - elem.startPointerPos.y));
|
||||
if (distance < dragDistance) {
|
||||
return;
|
||||
}
|
||||
node.startDrag({ evt });
|
||||
// a user can stop dragging inside `dragstart`
|
||||
if (!node.isDragging()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
node._setDragPosition(evt, elem);
|
||||
nodesToFireEvents.push(node);
|
||||
});
|
||||
// call dragmove only after ALL positions are changed
|
||||
nodesToFireEvents.forEach((node) => {
|
||||
// node may have been destroyed during a previous dragmove handler
|
||||
if (!node.getStage()) {
|
||||
return;
|
||||
}
|
||||
node.fire('dragmove', {
|
||||
type: 'dragmove',
|
||||
target: node,
|
||||
evt: evt,
|
||||
}, true);
|
||||
});
|
||||
},
|
||||
// dragBefore and dragAfter allows us to set correct order of events
|
||||
// setup all in dragbefore, and stop dragging only after pointerup triggered.
|
||||
_endDragBefore(evt, win, only) {
|
||||
const drawNodes = [];
|
||||
const positioned = new Set();
|
||||
DD._dragElements.forEach((elem, key) => {
|
||||
const { node } = elem;
|
||||
// node.stopDrag() ends only the drags of the same gesture: a Transformer
|
||||
// drags its other nodes with the same pointer, another pointer is its own
|
||||
if (only && elem !== only && elem.pointerId !== only.pointerId) {
|
||||
return;
|
||||
}
|
||||
// we need to find pointer relative to that node
|
||||
const stage = node.getStage();
|
||||
// a pointer released in another window ends the drag too - the pointer
|
||||
// is up everywhere - but its position is not used for this stage
|
||||
if (evt &&
|
||||
!positioned.has(stage) &&
|
||||
(!win || stage._getOwnerWindow() === win)) {
|
||||
stage.setPointersPositions(evt);
|
||||
positioned.add(stage);
|
||||
}
|
||||
if (!only) {
|
||||
if (evt &&
|
||||
elem.pointerEventType &&
|
||||
elem.pointerEventType !== Util._getEventType(evt.type)) {
|
||||
return;
|
||||
}
|
||||
// a drag started without an event (node.startDrag()) that has not
|
||||
// moved yet has no pointer: any release ends it
|
||||
const released = elem.pointerId === undefined ||
|
||||
stage._changedPointerPositions.some((pos) => pos.id === elem.pointerId);
|
||||
// that pointer is not related: a "ready" element of another pointer
|
||||
// waits for its own release
|
||||
if (!released) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (elem.dragStatus === 'ready') {
|
||||
DD._dragElements.delete(key);
|
||||
}
|
||||
else {
|
||||
// if a node is stopped manually we still need to reset events:
|
||||
stage._cancelClick(elem.pointerId, elem.pointerEventType);
|
||||
elem.dragStatus = 'stopped';
|
||||
// a node that has not moved needs no redraw
|
||||
const drawNode = node.getLayer() || stage;
|
||||
if (drawNode && drawNodes.indexOf(drawNode) === -1) {
|
||||
drawNodes.push(drawNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
// draw in a sync way
|
||||
// because mousemove event may trigger BEFORE batch draw is called
|
||||
// but as we have not hit canvas updated yet, it will trigger incorrect mouseover/mouseout events
|
||||
drawNodes.forEach((drawNode) => {
|
||||
drawNode.draw();
|
||||
});
|
||||
},
|
||||
_endDragAfter(evt, only) {
|
||||
DD._dragElements.forEach((elem, key) => {
|
||||
if (elem.dragStatus !== 'stopped' ||
|
||||
(only && elem !== only && elem.pointerId !== only.pointerId)) {
|
||||
return;
|
||||
}
|
||||
elem.node.fire('dragend', {
|
||||
type: 'dragend',
|
||||
target: elem.node,
|
||||
evt: evt,
|
||||
}, true);
|
||||
// a dragend handler may have started a new drag of the node
|
||||
if (elem.dragStatus !== 'dragging') {
|
||||
DD._dragElements.delete(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user