fix(editor): keep loop routes at a fixed height

This commit is contained in:
Lucio Lelii 2026-09-30 08:44:34 +02:00
parent f33712a250
commit 09abd7e3f6
3 changed files with 21 additions and 18 deletions

View File

@ -65,4 +65,16 @@ describe('CustomConnectionComponent bias routing highlight', () => {
comparisonViewState.show({ report: REPORT });
expect(component.strokeColor).toBe('#4682b4');
});
it('keeps a loop at its assigned height while a connected node moves above it', () => {
component.data = {
...component.data,
__loopBack: true,
__loopTop: 120
} as any;
component.start = { x: 0, y: 40 };
component.end = { x: 100, y: 80 };
expect(component.drawnPath).toContain('L 100 120');
});
});

View File

@ -267,7 +267,7 @@ export class CustomConnectionComponent {
private get loopTop(): number {
const ports = Math.min(this.start?.y ?? 0, this.end?.y ?? 0) - 90;
const nodes = this.loopData?.__loopTop;
return typeof nodes === 'number' ? Math.min(nodes, ports) : ports;
return typeof nodes === 'number' ? nodes : ports;
}
get loopLabelX(): number {

View File

@ -222,20 +222,6 @@ export async function createEditor(
return context;
});
area.addPipe((context: any) => {
if (context?.type === 'nodetranslated') {
// Any node moved can be one the way back has to clear, not only one of its two ends.
for (const connection of editor.getConnections() as LoopAwareConnection[]) {
if (!connection.__loopBack) continue;
const top = loopTop(area, connection);
if (top === connection.__loopTop) continue;
connection.__loopTop = top;
void area.update('connection', connection.id);
}
}
return context;
});
connection.addPreset(ConnectionPresets.classic.setup());
AreaExtensions.simpleNodesOrder(area);
@ -975,7 +961,13 @@ export async function refreshLoopMarkers(
for (const connection of connections) {
const loopBack = backEdges.has(connection.id);
const readonly = resolvedRuntime?.readonly === true;
const top = loopBack ? loopTop(area, connection) : undefined;
// A loop gets its route when it becomes a loop. It then keeps that canvas height while cards
// move, so a person can drag a card across the wire instead of the wire perpetually fleeing up.
const top = loopBack
? connection.__loopBack === true && typeof connection.__loopTop === 'number'
? connection.__loopTop
: loopTop(area, connection)
: undefined;
if (connection.__loopBack === loopBack && connection.__readonly === readonly && connection.__loopTop === top) continue;
connection.__loopBack = loopBack;
connection.__readonly = readonly;
@ -985,8 +977,7 @@ export async function refreshLoopMarkers(
}
/**
* Above the tops of the nodes at both ends. Their tops, not their bottoms: a node's height changes
* as it is expanded or edited, its position only when it is moved.
* The initial route of a loop: above the nodes it has to avoid when the loop is first established.
*/
function loopTop(area: AreaPlugin<HFSchemes, AreaExtra>, connection: LoopAwareConnection): number | undefined {
const ends = [connection.source, connection.target]