diff --git a/src/app/shared/rete-editor/rete-editor.spec.ts b/src/app/shared/rete-editor/rete-editor.spec.ts index 62f7c7d..98ed1c7 100644 --- a/src/app/shared/rete-editor/rete-editor.spec.ts +++ b/src/app/shared/rete-editor/rete-editor.spec.ts @@ -44,6 +44,7 @@ describe('ReteEditor', () => { deleteConnectionRequestTick: vi.fn(), loopLimitRequest: vi.fn().mockReturnValue(null), selectedConnectionId: vi.fn().mockReturnValue(null), + selectedConnection: vi.fn().mockReturnValue(null), clearConnectionSelection: vi.fn() } } @@ -70,6 +71,27 @@ describe('ReteEditor', () => { expect(component).toBeTruthy(); }); + it('brings both endpoints of a selected connection above unrelated nodes', async () => { + const source = document.createElement('div'); + const target = document.createElement('div'); + const reorder = vi.fn().mockResolvedValue(undefined); + const rete = { + area: { + nodeViews: new Map([ + ['source', { element: source }], + ['target', { element: target }] + ]), + area: { content: { reorder } } + } + } as any; + (component as any).rete = rete; + + await (component as any).bringConnectionEndpointsToFront(rete, 'source', 'target'); + + expect(reorder).toHaveBeenNthCalledWith(1, source, null); + expect(reorder).toHaveBeenNthCalledWith(2, target, null); + }); + it('does not render the dormant swimlane UI', () => { fixture.componentRef.setInput('flowData', { blocks: [ diff --git a/src/app/shared/rete-editor/rete-editor.ts b/src/app/shared/rete-editor/rete-editor.ts index b540ae9..59ea620 100644 --- a/src/app/shared/rete-editor/rete-editor.ts +++ b/src/app/shared/rete-editor/rete-editor.ts @@ -53,6 +53,12 @@ export class ReteEditor implements OnChanges, OnDestroy { if (!request || !rete || isReadonly) return; void this.applyLoopLimit(rete, request.connectionId, request.maxIterations); }); + effect(() => { + const selected = this.graphSelection.selectedConnection(); + const rete = untracked(() => this.rete); + if (!selected || !rete) return; + void this.bringConnectionEndpointsToFront(rete, selected.sourceId, selected.targetId); + }); } private async applyLoopLimit(rete: ReteEditorInstance, connectionId: string, maxIterations: number | null) { @@ -60,6 +66,16 @@ export class ReteEditor implements OnChanges, OnDestroy { this.flowState.updateData(exportGraph(rete.editor), { structural: true }); } + /** Keep both endpoint cards above unrelated nodes while their selected wire is in front. */ + private async bringConnectionEndpointsToFront(rete: ReteEditorInstance, sourceId: string, targetId: string) { + for (const nodeId of new Set([sourceId, targetId])) { + if (this.rete !== rete) return; + const view = rete.area.nodeViews.get(nodeId); + if (!view) continue; + await rete.area.area.content.reorder(view.element, null); + } + } + readonly container = viewChild.required('editor'); readonly shell = viewChild.required>('shell');