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