Edge relinking - #810
Merged
Merged
Edge relinking#810
Conversation
Conflict resolution: - deleteNodes keeps main's descendant cascade (#806) and feeds the cascaded set into partitionIncidentEdges for detach-on-delete. - copy-paste takes main's structure (#807: resolvePastedEnd, collectFreeEdgeEndpointPositions, calculateCenter, empty-paste early exit) and re-applies from the branch: manual-routing points shifted with a free pasted end, and the dangling-aware isInsideCopiedSet copy rule gated on danglingEdges.enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lukasz-jazwa
approved these changes
Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two opt-in features for existing edges. Both are off by default.
danglingEdgesconfig). An edge can have one or both ends free: emptysource/target, with the free end anchored atsourcePosition/targetPosition. Withenabled, a draw dropped on empty canvas keeps the edge (shouldKeepOnDropdecides per edge) and a relink drop detaches the end. WithdetachOnNodeDelete, deleting a node keeps its edges as dangling edges anchored where the port was (shouldDetachOnNodeDeletedecides per end; explicit deletes, hidden elements and edges losing both ends are still deleted). Dangling edges route between their anchors, are saved and restored with the model, copy and paste with their node, and box selection picks them by their anchors.edgeRelinkingconfig). A selected edge shows a handle at each end. Dragging a handle previews the new connection and commits it on drop. The drop is validated bylinking.validateConnectionwithreason: 'relink'. A drop on empty canvas detaches the end when dangling edges allow it. An invalid drop, a drop back on the same port, a click without a drag and Escape leave the edge unchanged. The handles have a hit area of about 12 px on screen at any zoom, so they work with touch.NgDiagramService.startLinkingFromPosition(position)starts a draw from a point on the canvas. RequiresdanglingEdges.enabled.New public API:
DanglingEdgesConfig,EdgeRelinkingConfig,ConnectionValidationContext,DanglingEndpoint,EdgeEnd,LinkingRelinkContext, eventsedgeRelinkStarted/edgeRelinkEnded(EdgeRelinkStartedEvent,EdgeRelinkEndedEvent,EdgeRelinkCancelReason),SelectionRemovedEvent.detachedEdges,NgDiagramModelService.detachEdge/attachEdge/getDanglingEndpoints/getNearestDanglingEndpointInRange, utilitiesisDanglingEdge,hasFreeEndpoint,getDanglingEndpoints,getNearestDanglingEndpointInRange,computeDetachAnchor,RelinkHandleDirectivefor custom edge templates, theme variables--ngd-relink-handle-size/-fill/-fill-hover/-stroke/-stroke-width(per edge:--edge-relink-handle-*), CSS hooksng-diagram-edge--danglingand.dangling.linking.validateConnectionis the single validator for drawing, relinking andattachEdge. It receives an optional fifth argument,ConnectionValidationContext, that says which operation asks (draw,relink,attach) and, for relink and attach, whichedgeandend.Internals: relinking reuses the linking action state (
linking.relink) and the temporary edge, so the existing middlewares,cancelLinkingand the Escape flow cover it.ModelLookupkeeps a cached list of dangling edges, so virtualization and the endpoint queries stay independent of the number of edges.Why
Users need to fix a wrong connection without deleting and redrawing the edge, and to keep connections when a node is removed or when the second node does not exist yet. Both were on the roadmap; the roadmap page moves Edge Relinking to the released features.
Backward compatibility
detachEdgeandstartLinkingFromPositionare no-ops with a console warning whiledanglingEdges.enabledis false.EdgeDrawEndedEvent.source: Node→source?: Node. Compile-time break only for apps withstrictNullChecksthat readevent.sourcewithout a check. Migration:event.source?.id. At runtimesourceisundefinedonly for draws started withstartLinkingFromPosition.edgeDrawEndedfor a kept canvas drop reportssuccess: truewith the creatededgeand notarget/targetPort. This happens only withdanglingEdges.enabled. Apps that created their own edge onreason === 'noTarget'must remove that code before enabling the flag, or edges are created twice. The deprecatededgeDrawnnever fires for kept dangling edges.linking.validateConnectiongets an optional fifth argument. Existing implementations keep working.sourcecan now benullat runtime: for draws started from a position and when the source end of a relinked edge is free. Implementations that assume a non-nullsourceneed a guard once they enable these features.SelectionRemovedEvent.detachedEdges: Edge[]is required and always present ([]when nothing was detached). Breaks only code that builds this event object by hand (tests, mocks).FlowConfig.danglingEdgesandFlowConfig.edgeRelinkingare required.NgDiagramConfigisDeepPartial<FlowConfig>, so app configs are not affected. Only code that builds a completeFlowConfigliteral must add both keys.ModelActionTypegains'startRelinking','finishRelinking'and'startLinkingFromPosition'. Breaks middlewares with an exhaustiveswitch(nevercheck) or aRecord<ModelActionType, …>.NgDiagramBaseEdgeComponentgets the public signalsdangling,relinkHandlesVisible,relinkSourceHandle,relinkTargetHandleandrelinkHandleHitRadius(all@since 1.4.0). Subclasses that declare members with these names conflict. The component injectsNgDiagramServiceandRendererServiceas optional, so TestBeds that mount it withoutprovideNgDiagram()keep working.updateEdgeorattachEdgechangingsource/targetnow recomputes the edge's z-index for its new nodes. Before, the edge kept the layering computed for its old nodes.source/targetare affected by the routing, box selection and paste changes: such edges now route between their stored positions, are selected by a box around their anchors, and paste moves their free ends. Edges with both ends connected behave as before.Docs
New guides
guides/edges/dangling-edgesandguides/edges/relinking. Updates in the edges overview (host class.dangling),intro/services,guides/flow-config,intro/roadmapand the CHANGELOG under Unreleased.