diff --git a/src/components/FlowEditor/editor/Automation2FlowEditor.module.css b/src/components/FlowEditor/editor/Automation2FlowEditor.module.css index 2178b60..bd66619 100644 --- a/src/components/FlowEditor/editor/Automation2FlowEditor.module.css +++ b/src/components/FlowEditor/editor/Automation2FlowEditor.module.css @@ -263,10 +263,10 @@ align-items: center; gap: 0.4rem; width: 100%; - padding: 0.35rem 0.5rem; + padding: 0; border-radius: 8px; - border: 1px solid var(--border-color, #e0e0e0); - background: var(--bg-secondary, #f8f9fa); + border: none; + background: none; box-sizing: border-box; } @@ -966,6 +966,8 @@ .handleWrapper:has(.handleOutput) { flex-direction: row; + /* Bottom handles: keep circle math aligned with wires even when a label grows row height. */ + align-items: flex-end; } .handleWrapper:has(.handleInput) { diff --git a/src/components/FlowEditor/editor/FlowCanvas.tsx b/src/components/FlowEditor/editor/FlowCanvas.tsx index 29bbbbb..2b16f63 100644 --- a/src/components/FlowEditor/editor/FlowCanvas.tsx +++ b/src/components/FlowEditor/editor/FlowCanvas.tsx @@ -115,6 +115,8 @@ export function getStickyNotePaletteEntry(colorId?: string) { const NODE_WIDTH = 200; const NODE_HEIGHT = 72; +/** Must match `.canvasNode { border: … solid }` — handles sit in the padding box. */ +const NODE_BORDER = 2; export const FLOW_CANVAS_MIN_ZOOM = 0.25; export const FLOW_CANVAS_MAX_ZOOM = 4; @@ -646,7 +648,7 @@ function feedbackConnectionPathD( const sx = src.x; const sy = src.y; const tx = tgt.x; - const tyIn = tgt.y - HANDLE_OFFSET; + const tyIn = tgt.y; const minNx = allNodes.length ? Math.min(...allNodes.map((n) => n.x)) @@ -687,10 +689,14 @@ function connectionPathD( tgtNode: CanvasNode, feedback: boolean, allNodes: CanvasNode[], + /** Trennt überlagernde Kanten in der Kurvenmitte — Endpunkt bleibt am Handle-Mittelpunkt. */ + lateralBias = 0, ): string { if (!feedback) { const dy = tgt.y - src.y; - return `M ${src.x} ${src.y} C ${src.x} ${src.y + Math.abs(dy) / 2}, ${tgt.x} ${tgt.y - Math.abs(dy) / 2}, ${tgt.x} ${tgt.y}`; + const mx = Math.abs(dy) / 2; + const b = lateralBias; + return `M ${src.x} ${src.y} C ${src.x + b} ${src.y + mx}, ${tgt.x + b} ${tgt.y - mx}, ${tgt.x} ${tgt.y}`; } return feedbackConnectionPathD(src, tgt, srcNode, tgtNode, allNodes); } @@ -1039,19 +1045,20 @@ export const FlowCanvas = forwardRef(function const ioIndex = isOutput ? handleIndex - node.inputs : handleIndex; const ioCount = isOutput ? node.outputs : node.inputs; - const w = NODE_WIDTH; - const h = NODE_HEIGHT; - const centerX = node.x + w / 2; + const innerLeft = node.x + NODE_BORDER; + const innerTop = node.y + NODE_BORDER; + const innerBottom = node.y + NODE_HEIGHT - NODE_BORDER; + const innerWidth = NODE_WIDTH - 2 * NODE_BORDER; + const centerX = innerLeft + innerWidth / 2; if (isOutput) { - if (ioCount === 1) return { x: centerX, y: node.y + h + HANDLE_OFFSET, side: 'bottom' }; - const step = w / (ioCount + 1); - return { x: node.x + step * (ioIndex + 1), y: node.y + h + HANDLE_OFFSET, side: 'bottom' }; - } else { - if (ioCount === 1) return { x: centerX, y: node.y, side: 'top' }; - const step = w / (ioCount + 1); - return { x: node.x + step * (ioIndex + 1), y: node.y, side: 'top' }; + if (ioCount === 1) return { x: centerX, y: innerBottom, side: 'bottom' }; + const step = innerWidth / (ioCount + 1); + return { x: innerLeft + step * (ioIndex + 1), y: innerBottom, side: 'bottom' }; } + if (ioCount === 1) return { x: centerX, y: innerTop, side: 'top' }; + const step = innerWidth / (ioCount + 1); + return { x: innerLeft + step * (ioIndex + 1), y: innerTop, side: 'top' }; }, [] ); @@ -1062,7 +1069,7 @@ export const FlowCanvas = forwardRef(function return used; }, [connections]); - /** Mehrere Kanten auf denselben Eingang: leicht versetzte Ziel-X für sichtbare, getrennte Enden. */ + /** Mehrere Kanten auf denselben Eingang: Kurven seitlich versetzen (Endpunkt = Handle-Mitte). */ const inboundStacksByTarget = useMemo(() => { const m = new Map(); for (const c of connections) { @@ -1746,9 +1753,10 @@ export const FlowCanvas = forwardRef(function @@ -1756,9 +1764,10 @@ export const FlowCanvas = forwardRef(function @@ -1766,9 +1775,10 @@ export const FlowCanvas = forwardRef(function @@ -1783,13 +1793,10 @@ export const FlowCanvas = forwardRef(function const tgtBase = getHandlePosition(tgtNode, c.targetHandle); const stack = inboundStacksByTarget.get(`${c.targetId}-${c.targetHandle}`) ?? [c]; const si = stack.findIndex((x) => x.id === c.id); - const spread = 12; - const tgt = - stack.length > 1 - ? { ...tgtBase, x: tgtBase.x + (si - (stack.length - 1) / 2) * spread } - : tgtBase; + const lateralBias = + stack.length > 1 ? (si - (stack.length - 1) / 2) * 14 : 0; const feedback = isLoopFeedbackEdge(c, srcNode, tgtNode); - const pathD = connectionPathD(src, tgt, srcNode, tgtNode, feedback, nodes); + const pathD = connectionPathD(src, tgtBase, srcNode, tgtNode, feedback, nodes, lateralBias); const isSelected = selectedConnectionId === c.id; const isWarning = connectionWarnings[c.id]; const strokeColor = isSelected @@ -1971,7 +1978,7 @@ export const FlowCanvas = forwardRef(function style={{ top: pos.side === 'top' ? -HANDLE_OFFSET : undefined, bottom: pos.side === 'bottom' ? -HANDLE_OFFSET : undefined, - left: pos.x - node.x - HANDLE_OFFSET, + left: pos.x - node.x - NODE_BORDER - HANDLE_OFFSET, }} > {outputLabel && pos.side === 'bottom' && isOutput ? (