fix: Drop-Position bei Zoom korrigiert und Canvas-Panning mit rechter Maustaste

- Drop- und Klickkoordinaten über das transformierte precanvas-Rect
  umrechnen (berücksichtigt transform-origin in der Canvas-Mitte)
- Greif-Offset in Canvas-Einheiten abziehen, damit der Knoten in jeder
  Zoomstufe unter dem Mauszeiger erscheint
- Rechtsklick auf freien Canvas verschiebt jetzt die gesamte Ansicht

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:46:36 +02:00
parent 5f85074653
commit 00f7b4f1ce

View File

@@ -284,14 +284,12 @@ function attach(host) {
const def = state.registry.get(typeId);
if (!def) return;
const rect = container.getBoundingClientRect();
const zoom = editor.zoom;
// Greif-Offset abziehen, damit der Knoten an seiner gezogenen Position
// liegt (linke obere Ecke wie während des Ziehens, nicht an der Maus).
const px = (e.clientX - rect.left - editor.canvas_x - dragGrabOffset.x) / zoom;
const py = (e.clientY - rect.top - editor.canvas_y - dragGrabOffset.y) / zoom;
addNodeInternal(host, def, px, py);
// Greif-Offset in Canvas-Einheiten abziehen (nicht in Bildschirm-
// Pixeln): So bleibt der Mauszeiger in jeder Zoomstufe an derselben
// relativen Stelle im erzeugten Knoten, statt dass der Knoten beim
// Herauszoomen um den hochgerechneten Offset neben der Maus landet.
const { x, y } = toCanvasPoint(state, e.clientX, e.clientY);
addNodeInternal(host, def, x - dragGrabOffset.x, y - dragGrabOffset.y);
});
editor.on('nodeSelected', id => {
@@ -376,26 +374,40 @@ function attach(host) {
selectConnection(state, path);
}, true);
// Verschieben von Knoten mit der RECHTEN Maustaste. Drawflow zieht Knoten
// nur mit links; das Ziehen mit rechts wird hier eigenständig umgesetzt.
// Rechte Maustaste: auf einem Knoten wird der Knoten verschoben (Drawflow
// zieht Knoten nur mit links), auf dem freien Canvas wird die gesamte
// Ansicht verschoben (Panning).
let rightDrag = null;
let rightPan = null;
let suppressContextMenu = false;
container.addEventListener('mousedown', e => {
if (e.button !== 2) return;
const nodeEl = e.target instanceof Element ? e.target.closest('.drawflow-node') : null;
if (!nodeEl) return;
e.preventDefault();
e.stopPropagation(); // Drawflows eigene (Links-)Drag-Logik nicht auslösen
rightDrag = {
nodeEl,
id: nodeEl.id.replace('node-', ''),
lastX: e.clientX,
lastY: e.clientY
};
const nodeEl = e.target instanceof Element ? e.target.closest('.drawflow-node') : null;
if (nodeEl) {
rightDrag = {
nodeEl,
id: nodeEl.id.replace('node-', ''),
lastX: e.clientX,
lastY: e.clientY
};
} else {
rightPan = { lastX: e.clientX, lastY: e.clientY };
}
suppressContextMenu = true;
}, true);
const onRightMove = e => {
if (rightPan) {
editor.canvas_x += e.clientX - rightPan.lastX;
editor.canvas_y += e.clientY - rightPan.lastY;
rightPan.lastX = e.clientX;
rightPan.lastY = e.clientY;
editor.precanvas.style.transform =
'translate(' + editor.canvas_x + 'px, ' + editor.canvas_y + 'px) scale(' + editor.zoom + ')';
return;
}
if (!rightDrag) return;
const zoom = editor.zoom || 1;
const newLeft = rightDrag.nodeEl.offsetLeft + (e.clientX - rightDrag.lastX) / zoom;
@@ -408,11 +420,11 @@ function attach(host) {
if (data) { data.pos_x = newLeft; data.pos_y = newTop; }
editor.updateConnectionNodes('node-' + rightDrag.id);
};
const onRightUp = () => { rightDrag = null; };
const onRightUp = () => { rightDrag = null; rightPan = null; };
document.addEventListener('mousemove', onRightMove);
document.addEventListener('mouseup', onRightUp);
// Browser-Kontextmenü nach einem Rechts-Ziehen auf einem Knoten unterdrücken.
// Browser-Kontextmenü nach Rechts-Ziehen (Knoten oder Canvas) unterdrücken.
container.addEventListener('contextmenu', e => {
if (suppressContextMenu) {
e.preventDefault();
@@ -466,14 +478,16 @@ function attach(host) {
}
// Wandelt Client-Koordinaten in das (unskalierte) Canvas-Koordinatensystem um,
// in dem auch die Verbindungspfade definiert sind.
// in dem auch die Verbindungspfade definiert sind. Das Rect des precanvas
// enthält bereits translate UND scale (inkl. transform-origin in der Mitte),
// daher genügt es, den skalierten Abstand zurückzurechnen.
function toCanvasPoint(state, clientX, clientY) {
const editor = state.editor;
const rect = state.container.getBoundingClientRect();
const rect = editor.precanvas.getBoundingClientRect();
const zoom = editor.zoom || 1;
return {
x: (clientX - rect.left - editor.canvas_x) / zoom,
y: (clientY - rect.top - editor.canvas_y) / zoom,
x: (clientX - rect.left) / zoom,
y: (clientY - rect.top) / zoom,
zoom
};
}