fix: arrow beginning und ending

This commit is contained in:
Ida 2026-05-13 16:41:03 +02:00
parent 590178b8f2
commit 6890a38546
2 changed files with 35 additions and 26 deletions

View file

@ -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) {

View file

@ -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<FlowCanvasHandle, FlowCanvasProps>(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<FlowCanvasHandle, FlowCanvasProps>(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<string, CanvasConnection[]>();
for (const c of connections) {
@ -1746,9 +1753,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
<defs>
<marker
id="arrowhead"
markerUnits="userSpaceOnUse"
markerWidth="10"
markerHeight="7"
refX="9"
refX="10"
refY="3.5"
orient="auto"
>
@ -1756,9 +1764,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
</marker>
<marker
id="arrowhead-selected"
markerUnits="userSpaceOnUse"
markerWidth="10"
markerHeight="7"
refX="9"
refX="10"
refY="3.5"
orient="auto"
>
@ -1766,9 +1775,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
</marker>
<marker
id="arrowhead-warning"
markerUnits="userSpaceOnUse"
markerWidth="10"
markerHeight="7"
refX="9"
refX="10"
refY="3.5"
orient="auto"
>
@ -1783,13 +1793,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(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<FlowCanvasHandle, FlowCanvasProps>(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 ? (