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; align-items: center;
gap: 0.4rem; gap: 0.4rem;
width: 100%; width: 100%;
padding: 0.35rem 0.5rem; padding: 0;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--border-color, #e0e0e0); border: none;
background: var(--bg-secondary, #f8f9fa); background: none;
box-sizing: border-box; box-sizing: border-box;
} }
@ -966,6 +966,8 @@
.handleWrapper:has(.handleOutput) { .handleWrapper:has(.handleOutput) {
flex-direction: row; 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) { .handleWrapper:has(.handleInput) {

View file

@ -115,6 +115,8 @@ export function getStickyNotePaletteEntry(colorId?: string) {
const NODE_WIDTH = 200; const NODE_WIDTH = 200;
const NODE_HEIGHT = 72; 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_MIN_ZOOM = 0.25;
export const FLOW_CANVAS_MAX_ZOOM = 4; export const FLOW_CANVAS_MAX_ZOOM = 4;
@ -646,7 +648,7 @@ function feedbackConnectionPathD(
const sx = src.x; const sx = src.x;
const sy = src.y; const sy = src.y;
const tx = tgt.x; const tx = tgt.x;
const tyIn = tgt.y - HANDLE_OFFSET; const tyIn = tgt.y;
const minNx = allNodes.length const minNx = allNodes.length
? Math.min(...allNodes.map((n) => n.x)) ? Math.min(...allNodes.map((n) => n.x))
@ -687,10 +689,14 @@ function connectionPathD(
tgtNode: CanvasNode, tgtNode: CanvasNode,
feedback: boolean, feedback: boolean,
allNodes: CanvasNode[], allNodes: CanvasNode[],
/** Trennt überlagernde Kanten in der Kurvenmitte — Endpunkt bleibt am Handle-Mittelpunkt. */
lateralBias = 0,
): string { ): string {
if (!feedback) { if (!feedback) {
const dy = tgt.y - src.y; 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); 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 ioIndex = isOutput ? handleIndex - node.inputs : handleIndex;
const ioCount = isOutput ? node.outputs : node.inputs; const ioCount = isOutput ? node.outputs : node.inputs;
const w = NODE_WIDTH; const innerLeft = node.x + NODE_BORDER;
const h = NODE_HEIGHT; const innerTop = node.y + NODE_BORDER;
const centerX = node.x + w / 2; const innerBottom = node.y + NODE_HEIGHT - NODE_BORDER;
const innerWidth = NODE_WIDTH - 2 * NODE_BORDER;
const centerX = innerLeft + innerWidth / 2;
if (isOutput) { if (isOutput) {
if (ioCount === 1) return { x: centerX, y: node.y + h + HANDLE_OFFSET, side: 'bottom' }; if (ioCount === 1) return { x: centerX, y: innerBottom, side: 'bottom' };
const step = w / (ioCount + 1); const step = innerWidth / (ioCount + 1);
return { x: node.x + step * (ioIndex + 1), y: node.y + h + HANDLE_OFFSET, side: 'bottom' }; return { x: innerLeft + step * (ioIndex + 1), y: innerBottom, 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: 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; return used;
}, [connections]); }, [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 inboundStacksByTarget = useMemo(() => {
const m = new Map<string, CanvasConnection[]>(); const m = new Map<string, CanvasConnection[]>();
for (const c of connections) { for (const c of connections) {
@ -1746,9 +1753,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
<defs> <defs>
<marker <marker
id="arrowhead" id="arrowhead"
markerUnits="userSpaceOnUse"
markerWidth="10" markerWidth="10"
markerHeight="7" markerHeight="7"
refX="9" refX="10"
refY="3.5" refY="3.5"
orient="auto" orient="auto"
> >
@ -1756,9 +1764,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
</marker> </marker>
<marker <marker
id="arrowhead-selected" id="arrowhead-selected"
markerUnits="userSpaceOnUse"
markerWidth="10" markerWidth="10"
markerHeight="7" markerHeight="7"
refX="9" refX="10"
refY="3.5" refY="3.5"
orient="auto" orient="auto"
> >
@ -1766,9 +1775,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
</marker> </marker>
<marker <marker
id="arrowhead-warning" id="arrowhead-warning"
markerUnits="userSpaceOnUse"
markerWidth="10" markerWidth="10"
markerHeight="7" markerHeight="7"
refX="9" refX="10"
refY="3.5" refY="3.5"
orient="auto" orient="auto"
> >
@ -1783,13 +1793,10 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
const tgtBase = getHandlePosition(tgtNode, c.targetHandle); const tgtBase = getHandlePosition(tgtNode, c.targetHandle);
const stack = inboundStacksByTarget.get(`${c.targetId}-${c.targetHandle}`) ?? [c]; const stack = inboundStacksByTarget.get(`${c.targetId}-${c.targetHandle}`) ?? [c];
const si = stack.findIndex((x) => x.id === c.id); const si = stack.findIndex((x) => x.id === c.id);
const spread = 12; const lateralBias =
const tgt = stack.length > 1 ? (si - (stack.length - 1) / 2) * 14 : 0;
stack.length > 1
? { ...tgtBase, x: tgtBase.x + (si - (stack.length - 1) / 2) * spread }
: tgtBase;
const feedback = isLoopFeedbackEdge(c, srcNode, tgtNode); 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 isSelected = selectedConnectionId === c.id;
const isWarning = connectionWarnings[c.id]; const isWarning = connectionWarnings[c.id];
const strokeColor = isSelected const strokeColor = isSelected
@ -1971,7 +1978,7 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
style={{ style={{
top: pos.side === 'top' ? -HANDLE_OFFSET : undefined, top: pos.side === 'top' ? -HANDLE_OFFSET : undefined,
bottom: pos.side === 'bottom' ? -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 ? ( {outputLabel && pos.side === 'bottom' && isOutput ? (