Skip to content

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

  • NgDiagramPaletteItemPreviewComponent.scaleTransform — the drag image is scaled internally, so this value is no longer used. It will be removed in the next major version (#801)
  • NgDiagramPaletteItemPreviewComponent protected members isChrome, isSafari, isVisible and scale — no longer used by the preview. They keep working for subclasses and will be removed in the next major version (#801)
  • EdgeDrawEndedEvent.source is now optional — a draw can start from a position instead of a node (startLinkingFromPosition). Read it as event.source?.id. Breaking only for apps compiled with strictNullChecks that read event.source without a check (#810)
  • edgeDrawEnded reports success for a canvas drop kept as a dangling edge — with danglingEdges.enabled, such a drop fires with success: true and the created edge, and without target/targetPort (before, every canvas drop cancelled with noTarget). If your app created its own edge on reason === 'noTarget', remove that code before enabling the flag, or edges are created twice. The deprecated edgeDrawn never fires for kept dangling edges (#810)
  • linking.validateConnection receives an optional fifth argument — a ConnectionValidationContext with reason: 'draw' | 'relink' | 'attach' and, for relink and attach, the edge and end being reconnected, so one validator can treat the three cases differently. The source node argument can now be null: for draws started with startLinkingFromPosition and when the other end of a relinked edge is free (#810)
  • SelectionRemovedEvent has a new required detachedEdges field — the edges kept as dangling edges instead of deleted (danglingEdges.detachOnNodeDelete), [] when none. Breaking only for code that builds this event object by hand (#810)
  • ModelActionType gained 'startRelinking', 'finishRelinking' and 'startLinkingFromPosition' — breaking for middlewares with exhaustive switches over the action type (#810)
  • Box selection counts dangling edges by their free-endpoint anchors — a box drawn around a free endpoint’s sourcePosition/targetPosition selects the edge (#810)
  • Paste moves free endpoints — pasting moves the free endpoints of dangling edges together with the rest of the content (for manual-routing edges also their points), and paste at a position centers on nodes and free endpoints alike (#810)
  • Z-index recomputes when an edge’s source/target changes programmatically — after updateEdge or attachEdge changes an endpoint, the edge is layered relative to its new nodes (#810)
  • ng-diagram-palette-item-preview takes no space in the layout — the preview renders nothing on the page, whatever content it holds and however the palette is laid out. It is a block, so inline content placed next to it inside a palette item now starts on a new line (#801)
  • NgDiagramPaletteItemPreviewComponent.preview is for reading content and size — the element behind this signal is not rendered in the page flow. Read the preview’s natural size or content from it; its position is meaningless (#801)
  • The drag image zoom is capped at 3x — at higher zoom levels the image under the cursor stays at 3x, so starting a drag stays smooth; the dropped node still lands at the current zoom (#801)
  • Copying a group now includes its content — copy and cut also take all descendants of the copied nodes and the edges between the copied nodes. Pasting a group now recreates its children and their edges (before, a pasted group was empty). Cutting a collapsed group keeps its hidden children: they are pasted back together with the group. Selected elements that are effectively hidden are skipped by copy, the same as in deleteSelection. Pasted hidden content is not selected, so a paste never creates a selection that you cannot see. The paste position at the cursor is computed from the visible pasted content only (#799)
  • Dangling edges — edges with one or both ends not connected to a node. A free end has source/target set to the empty string '' (never null or undefined), no sourcePort/targetPort (undefined), and its position in sourcePosition/targetPosition. Opt in with the new danglingEdges.enabled config: a draw dropped on empty canvas then keeps the edge (shouldKeepOnDrop decides per edge) and a relink drop detaches the endpoint. Dangling edges route between their anchors, are saved and restored with the model, copy and paste together with their node, and box selection picks them by their anchors. New utilities isDanglingEdge, hasFreeEndpoint, getDanglingEndpoints, getNearestDanglingEndpointInRange and computeDetachAnchor; new NgDiagramModelService.detachEdge/attachEdge (attach validates through linking.validateConnection with reason: 'attach'); new CSS hooks ng-diagram-edge--dangling on the edge wrapper and dangling on ng-diagram-base-edge and its path (#810)
  • Keep edges when deleting a node — with danglingEdges.detachOnNodeDelete, deleting a node keeps its edges as dangling edges anchored where their port was, and selectionRemoved.detachedEdges lists them. shouldDetachOnNodeDelete decides per endpoint. An edge is still deleted when it is deleted explicitly, when it is hidden only because of the node it loses (an edge that stays hidden on its own is detached like any other), or when it loses both ends at once (unless the callback keeps both) (#810)
  • Edge relinking — reconnect an edge by dragging one of its ends. Set linking.defaultRelinkable to true and a selected edge shows a handle at each end; set it to 'source' or 'target' to allow only that end. An edge overrides the default with its own relinkable property, so you can lock single edges or open only some; isEdgeEndRelinkable(edge, end, defaultRelinkable) resolves the two for custom templates. Dragging a handle previews the new connection and commits it on drop. The drop is validated by linking.validateConnection with reason: 'relink'. A drop on empty canvas detaches the end when danglingEdges.enabled allows it; an invalid drop, a drop back on the same port and Escape leave the edge unchanged. New edgeRelinkStarted/edgeRelinkEnded events report the gesture and its result (noTarget, invalidConnection or cancelled on failure). The handles show a halo on hover and stay visible while an end is dragged. Style them with the --ngd-relink-handle-* theme variables (size, stroke width, halo size, and colors for the default, hover and drag states) or per edge with --edge-relink-handle-* (#630, #810, #814) — thanks @axhar-ibraheem for the suggestion! 🙏
  • NgDiagramService.startLinkingFromPosition(position) — starts drawing an edge from a point on the canvas (flow coordinates) instead of from a node; the free end follows the pointer until a click connects it to a port or leaves a dual dangling edge on empty canvas. Requires danglingEdges.enabled (ignored with a console warning otherwise) (#810)
  • hidden flag on nodes and edges — the new optional hidden property on SimpleNode and Edge hides an element without removing it from the model. Hidden elements stay in the DOM with display: none, keep their size and position, never block initialization or waitForMeasurements, and are measured again automatically when they become visible. Hiding a group hides all of its descendants, and an edge is hidden when either of its endpoint nodes is hidden. The resulting effective visibility is available as the read-only computedHidden property. Visible nodes, edges and labels never get an inline display style, so existing CSS-based workarounds keep working. Ports manage their own inline display as before: hide them with the port’s hidden input, not with CSS. Upgrade note: hidden is now a library property on nodes and edges. If your app already stores hidden: true on nodes or edges, those elements become hidden after the upgrade (#799)
  • Hidden elements are ignored by user interactions — effectively hidden nodes and edges cannot be clicked, and they are left out of selectAll, keyboard move, drag, box selection, linking (starting a link from a hidden node does nothing and logs a console warning), zoomToFit and computePartsBounds bounds, virtualization, edge routing, and the resize limits of their group. A collapsed group can be resized freely, and its hidden children reappear at their old positions on expand. The minimap follows the canvas: hidden nodes are not drawn (neither as default rectangles nor as custom minimap templates) and do not enlarge the minimap bounds, in both rendering modes. Programmatic APIs (select, centerOnNode, z-order and group commands) intentionally do not skip hidden elements. Hiding does not deselect: a hidden element keeps its selected flag but cannot be moved, and deleteSelection skips hidden selected elements. Descendants of a deleted visible group and edges of deleted nodes are still removed (#799)
  • hidden inputs and ngDiagramHidden directive in templates — ng-diagram-port and ng-diagram-base-edge-label accept a hidden input that hides a port or label independently of its node or edge (for example, ports visible only in an edit mode). A hidden port cannot receive a link, and the linking preview does not snap to it. Edges connected to a hidden port stay visible and keep using its last measured position. The new NgDiagramHiddenDirective ([ngDiagramHidden]) hides a node or edge from inside its template. The model flag, the template binding and a hidden parent all feed the same effective visibility: any of them hides the element. Bindings that change in the same change-detection cycle are processed together in one pass, but hiding many elements through the model hidden flag is still the cheapest option. The directive does not work with virtualization, because hiding the element would remove the template that holds the binding. In that case the binding is ignored and a console warning is logged. Use the model hidden flag instead (#799)
  • tabbable input on <ng-diagram> — set [tabbable]="false" to remove the diagram’s own Tab stops (the container element and the watermark link) from the page’s Tab order, for example when your application manages Tab order itself with a roving tabindex on the nodes. Both elements stay clickable and keep working with screen readers, and keyboard shortcuts keep working once the diagram is focused, for example by a click. Focusable content in your own node and edge templates keeps its own Tab stops. Defaults to true. If you set a tabindex attribute on <ng-diagram> in your template, it no longer takes effect — the diagram now controls that attribute; use [tabbable]="false" instead (#790, #795) — thanks @jimmeryn for the issue submission! 🙏
  • Ports are measured when <ng-diagram> is rendered outside the component that declares provideNgDiagram() — when the diagram was outside the DOM of that component (for example with ngTemplateOutlet, a portal or an overlay), changing a node’s size or rotation logged Node measurement failed: Node element not found and left the ports at their old positions. Port positions now update in this layout too (#811, #815) — thanks @montella1507 for the issue submission! 🙏
  • The cursor no longer flickers while an edge is drawn or relinked — the default edge and edge labels do not react to the pointer while they preview a connection, and the diagram shows the grabbing hand over the canvas for the whole gesture. The preview’s edge wrapper carries an ng-diagram-edge--temporary class; use it to keep interactive content in your own edge template out of the pointer’s way during a draw (#812)
  • A draw always ends cleanly — when the system takes the pointer away, for example a palm-rejected touch or a system gesture, the edge being drawn is cancelled instead of leaving the diagram in drawing mode. A diagram destroyed in the middle of a draw removes its listeners (#812)
  • Arrow keys pan the viewport when the selection cannot move — with nodeDraggingEnabled: false and a node selected, the arrow keys used to do nothing. The same happened when only draggable: false nodes were selected. Now the arrow keys pan the viewport whenever the selection cannot move. A selection that can move still moves as before (#809)
  • zoomToFit({ nodeIds }) fits only the requested nodes — without edgeIds, the fit used to include every edge of the diagram. Fitting a few nodes in a connected diagram therefore zoomed out to the whole diagram, and fitting only hidden nodes showed the whole diagram instead of doing nothing. Now only edges with both ends on the requested nodes are included. Passing edgeIds works as before (#808)
  • Pasted manual-routing edges keep their shape at the new location — an edge with routingMode: 'manual' pasted together with its nodes used to be drawn at the original location; its points now move with the pasted nodes (#810)
  • Pasting an edge without its nodes no longer duplicates the original connection — an edge copied without one or both of its endpoint nodes used to be pasted between the original nodes. Every paste added one more identical connection. Now an end whose node was not copied is pasted as a free end. It moves with the rest of the pasted content, and you can move it or reconnect it. Cutting a single edge and pasting it therefore gives a free edge, not a copy of the original connection. Edges copied together with their nodes are still pasted between the new nodes (#807)
  • deleteNodes on a group deletes its whole subtree — NgDiagramModelService.deleteNodes now also deletes all descendants of the deleted nodes and their edges, the same as deleteSelection. Before, the children stayed in the model with a groupId that pointed at a deleted node, and the children of a hidden group became visible again. To delete a group but keep its children, call NgDiagramGroupsService.removeFromGroup first (#806)
  • Palette preview no longer bleeds over the palette or stretches the page — at high zoom levels ng-diagram-palette-item-preview no longer paints over the palette items and no longer adds scrollbars to the page, whatever the layout around the palette (#801)
  • Palette drag image matches the preview it shows — the image under the cursor while dragging a palette item is now exactly the scaled preview, at every zoom level and in every browser. The preview can render any element, of any size, and the drag image will match it (#801)
  • Zero-size measurements no longer break geometry — when a rendered element was hidden with CSS display: none, the browser reported a size of 0×0, and this value overwrote the node’s size and the measured geometry of its ports and labels in the model. Measurements of 0×0 are now ignored (a measurement like 200×0 is still applied). Invalid measurements collected during initialization are no longer written over existing geometry either (#799)
  • computePartsBounds no longer pulls the result to the origin — when one of the lists was empty (most often computePartsBounds(nodes, [])) or had nothing measured in it, the result was extended to include the point (0, 0). The bounds now cover exactly the measured parts. When there is nothing to measure at all, a zero-size rectangle at the origin is returned, as before. The minimap now frames diagrams placed away from the origin correctly, and zoomToFit({ nodeIds }) fits the requested nodes instead of the origin. zoomToFit on an empty or unmeasured diagram does nothing (#793)
  • NgDiagramService.invalidateMeasurements is awaitable — it returns a Promise<void> that resolves once the triggered re-measurements have settled, so the invalidated elements’ size, measuredPorts and measuredLabels read fresh on the next line. An element that delivers no new measurement (unmounted, zero-size, or unchanged) does not stall the promise. Existing call sites keep working (void → Promise<void>); ignoring the promise is fine (#778)
  • Awaitable service methods — every mutating method on the public services (NgDiagramModelService, NgDiagramNodeService, NgDiagramSelectionService, NgDiagramClipboardService, NgDiagramGroupsService, NgDiagramViewportService) now returns a Promise<void> that resolves once the change has been applied to the model, so the next line of code reads the updated state. Read it through getModel() or the getter methods (getNodeById, getEdgeById, getConnectedEdges, …) — these are synchronous with the model. The nodes(), edges() and metadata() signals refresh with Angular’s change detection, so right after the await (and inside diagram event handlers) they can still show the previous state. Existing call sites keep working (void → Promise<void>). Do not await these promises from inside a middleware — the update pipeline is not re-entrant and the await would deadlock; fire-and-forget calls are safe there (#769)
  • NgDiagramDefaultEdgeLabelComponent is now public — the component rendering the default edge label chip (ng-diagram-default-edge-label) is exported from the package, so custom edge templates can compose it inside ng-diagram-base-edge-label and get the default label look (theme-aware chip, hover and selected border highlights) without copying its styles. The hover and selected border highlights now work in any edge template, and the new --edge-label-border-color-hover and --edge-label-border-color-selected variables override their colors (each falling back to --edge-label-border-color, then the matching --ngd-default-edge-stroke-hover/-selected token). (#777)
  • Configurable resize sides — ng-diagram-node-resize-adornment accepts an activeSides input that limits which sides of the node can be grabbed, for example [activeSides]="['right', 'bottom']" for a node anchored at its top-left corner. All four lines keep rendering (they double as the selection frame), but the ones left out are inert and show no resize cursor, and a corner handle renders only when both of its sides are listed. Omitting the input keeps all four sides and corners active, so existing templates are unaffected. The new Side type names one of the four sides of a rectangular area; PortSide is now an alias of it (#775)
  • Remove ports from default nodes – this is now possible to remove ports from default nodes (#759)
  • waitForMeasurements option on service methods — addNodes, addEdges, updateNode, updateNodes, updateNodeData, updateEdge, updateEdges, updateEdgeData on NgDiagramModelService, resizeNode on NgDiagramNodeService and paste on NgDiagramClipboardService accept options?: { waitForMeasurements?: boolean }; when set, the returned promise resolves only after the elements affected by the change have been measured — useful whenever the next step depends on real dimensions (for example zoomToFit() or centerOnNode()). The option exists only on methods whose changes can trigger measurements; deletions and other model-only operations have nothing to measure, so awaiting the method itself is already enough there. Inside an already active transaction the option is ignored with a console warning — pass { waitForMeasurements: true } to the transaction itself instead (#769)
  • Customizable runtime-property stripping – initializeModel and initializeModelAdapter accept an optional InitializeModelOptions parameter to control which properties are stripped on initialization and toJSON(). Overriding the defaults can break the diagram — use at your own risk (#760)
  • Resize snap offset — new computeSnapOffsetForNodeSize and defaultResizeSnapOffset options on SnappingConfig. Snapped node sizes now follow the sequence offset + n * snap per axis, so a node with a 60px header and a 50px vertical resize snap can snap to 60, 110, 160, … instead of 50, 100, 150, …. Defaults to { width: 0, height: 0 } (#765, #770) — thanks @logan-brd for the suggestion! 🙏
  • NgDiagramService.transaction always returns the commit promise — the synchronous-callback overload used to return void, and for some async callbacks the commit promise was silently discarded; all overloads now return Promise<TransactionResult>, so awaiting a transaction reliably waits for its commit (#769)
  • Cancel in-progress gestures – new NgDiagramService.cancelActiveInteraction() aborts the active linking, drag, resize, rotate or pan gesture immediately and restores the pre-gesture state: dragged nodes snap back to their initial positions, resized/rotated nodes regain their original geometry, and the temporary edge is discarded (state cleared, document listeners removed, no need to wait for pointer release). Bound to Escape by default via the new cancelInteraction shortcut action. The edgeDrawEnded event gains a cancelled reason, and nodeDragEnded/nodeResizeEnded/nodeRotateEnded gain an optional cancelReason field (#747, #766)
  • Gesture reliability — drag, resize, rotate and linking now behave correctly with slow (asynchronous) middlewares and throwing user callbacks: nodeDragStarted/nodeDragEnded, nodeResizeStarted/nodeResizeEnded and nodeRotateStarted/nodeRotateEnded always come in pairs and report the node(s) of their own gesture; a throwing user callback (e.g. connection validation or a grouping check) no longer leaves a gesture stuck — linking cannot become permanently blocked and node measurements are not suppressed afterwards; and deleting a node mid-gesture (while resizing, rotating or linking it) cleans up correctly (#769)
  • A throwing middleware no longer freezes the diagram — an uncaught error (or unhandled rejection) inside a middleware used to silently freeze every subsequent update; it now rejects the promise returned by the mutating call and the diagram keeps working (#769)
  • waitForMeasurements no longer resolves early when measurement rounds overlap — a leftover debounce timer from a previous round could settle a newly started round before its measurements arrived (#778)
  • Initialization resilience — an error thrown during initialization (e.g. by a custom ModelAdapter) no longer leaves the diagram in a state where nothing gets measured; initialization completes and the error is logged (#769)
  • waitForMeasurements accuracy — a concurrent unrelated update can no longer make waitForMeasurements resolve too early or wait for the wrong elements; the option passed to a nested transaction now logs a warning instead of silently resolving before the updates are applied. As a consequence, a transaction now waits only for measurements caused by its own changes: a transaction with an empty callback resolves immediately, so it no longer works as a barrier that waits for unrelated measurement activity (a pattern some apps used after invalidateMeasurements) — await invalidateMeasurements() itself instead (#769)
  • Overlapping transactions no longer lose updates — a transaction (or a plain service call) issued while a previous un-awaited transaction was still committing used to be silently dropped; every transaction now applies its own updates, and the overlap logs a console warning recommending to await transactions (#769)
  • Port and edge-label measurements no longer get lost during rapid re-rendering — fast mount/unmount cycles (e.g. virtualization while panning) could apply port and label updates out of order or drop them entirely when one update failed, leaving stale port positions or labels; a failed batch no longer blocks all future ones either (#769)
  • NgDiagramNodeService.resizeNode now resizes an unselected group — a programmatic resizeNode call targeting a group node used to silently do nothing unless the group was selected; it now applies regardless of selection state, still enforcing the group resize constraints (children containment, minimum node size and resize snapping). Interactive resize behavior is unchanged. Calls that used to silently no-op now apply (#772)
  • Dangling edges survive persistence — initializeModel and initializeModelAdapter no longer strip the authored sourcePosition/targetPosition of an edge’s free endpoint (empty source/target), and toJSON() now includes them in the serialized output, so dangling edges load from a persisted model the same way they work when added at runtime; no more “Invalid edge coordinates detected” for valid dangling edges on init (#751, #760)
  • Port side/type no longer stay stale after a port moves — recreating a port with the same id in a different place (e.g. toggling a port between a side: 'left' and a side: 'right' block) now updates measuredPorts with the new side/type, so edges anchor to the correct side; measured size/position keep coming from the DOM as before. The same applies to edge labels re-registered with a changed positionOnEdge (#750, #763)
  • Group with children jumping on resize snap — resizing a group that contains child nodes from the bottom/right edge no longer moves the group when a resize snap is configured (#765, #770) — thanks @logan-brd for the issue submission! 🙏
  • Keyboard shortcuts work when a gesture starts with focus outside the diagram — the resize/rotate handles stop the pointerdown propagation, which used to skip the diagram’s focus grab; starting a resize right after clicking an external control (e.g. a toolbar button) left every shortcut dead — in particular Escape could not cancel the gesture. The diagram now takes focus on any pointerdown inside it (#766)
  • Touch gestures stay exclusive under virtualization — on touch devices with virtualization enabled, nodes and ports leaving the rendered area during a pan or pinch-zoom no longer reset the internal gesture-exclusivity state, so a stray touch can no longer start a second gesture (drag, resize, linking) in the middle of an active one (#766)
  • Resize snapping no longer cuts group children — with allowResizeBelowChildrenBounds: false, a snapped group size that would land inside the children bounds now rounds up to the next snap value that still contains the children (#770)
  • Size changes made right after a resize gesture are no longer overwritten by a stale measurement — a middleware reverting an invalid resize on resizeNodeStop, or a nodeResizeEnded handler correcting the size, used to lose when the mouse button was released while still moving; the corrected size now always wins. The resized node is also re-measured once after the gesture, so when CSS (e.g. min-width) keeps the element larger than the resized size, the model picks up the element’s real size (#771, #776) — thanks @logan-brd for the report and the reproduction! 🙏
  • Releasing the mouse while still moving no longer loses the end of a resize, drag or rotation — the node used to stop short of where the pointer was released (the faster the release, the bigger the miss; with resize snapping the difference could reach a whole grid step). The release position is now applied as the final update of the gesture, before nodeDragEnded/nodeResizeEnded/nodeRotateEnded fires, so the reported geometry always matches where the pointer stopped (#771, #779)
  • Drawing an edge is cancelled when another touch gesture takes over — on touch devices, a two-finger pan or pinch started in the middle of drawing an edge used to finish the edge at the other finger’s position, sometimes connecting it to a target the user never pointed at; the drawing is now cancelled and edgeDrawEnded reports the cancelled reason (#779)
  • Improved trackpad gestures support - panning with a trackpad now smoothly pans the diagram. Pinching defaults to zooming in/out on the diagram (#717)
  • Panning direction change with Shift - On MacOS devices pressing the Shift and using the wheel on a mouse didn’t change the panning axis (#717)
  • Updated z-index defaults — selectedZIndex changed from 1000 to 10000 to provide more headroom for explicit zOrder values; temporaryEdgeZIndex changed from 1000 to 2147483647 (max 32-bit int) so the edge being drawn always renders on top (#697)
  • invalidateMeasurements(options?) method on NgDiagramService — forces re-measurement of nodes, ports, and edge labels via ResizeObserver. Call with no arguments to re-measure the entire diagram, or pass { nodes: [...], edges: [...] } to target specific elements. Invalidating a node also re-measures all its ports. Use this when CSS-only repositioning (class toggles, style bindings) changes port positions without changing sizes, which ResizeObserver cannot detect (#698)
  • initialConnectedEdgesMap on MiddlewareContext — a Map<string, string[]> from node ID to connected edge IDs (source or target) captured before any modifications. Allows middleware to find edges connected to specific nodes without scanning all edges (#697)
  • MCP server: inline code snippets — search_docs and get_doc now resolve <CodeSnippet> and <CodeViewer> tags in documentation pages, inlining the referenced source files directly into tool results. AI assistants see complete, runnable examples without needing access to the source repository (#699)
  • Z-ordering reworked — rewrote the z-index assignment middleware and bringToFront/sendToBack commands, fixing multiple issues: children are now always rendered above their parent group regardless of zOrder values, and siblings within a group are correctly re-sorted when zOrder, selection, or group membership changes.(#697)
  • Fixed ports and edge labels not being measured when a node or edge is removed and re-added with the same ID in the same tick (#701)
  • Reworked port and label measurement pipeline to batch DOM reads and writes, reducing layout thrashing during bulk operations (#671)
  • getChangedNodeIds() and getChangedEdgeIds() helpers on MiddlewareHelpers — return IDs of all nodes/edges with property changes in the current update (#671)
  • Fixed waitForMeasurements transaction not tracking port measurements, causing the transaction to resolve before ports were measured (#685)
  • Fixed race condition when applying multiple port changes in a single transaction (#671)
  • Fixed touch input (text fields, dropdowns) inside custom nodes not responding on iOS/iPadOS due to preventDefault in the box selection touch handler (#686)
  • New Templates section — we’re excited to introduce a dedicated space for production-grade starter kits curated and built by the ngDiagram team. Kicking it off with an interactive Org Chart starter kit featuring drag-and-drop reordering, expand/collapse subtrees, sidebar node editing, dynamic layouts, dark/light theme, minimap, and automatic tree layout powered by ELK.js. Clone it, explore the code, and use it as a launchpad for your own app! 🚀 (#667)
  • Fixed linking state not being cleared when edge drawing fails before creating a temporary edge (e.g., starting from a target port), which permanently blocked all subsequent edge drawing (#666)
  • deferNodeUpdates input on NgDiagramMinimapComponent — freezes minimap node positions during drag, resize, and rotation operations, updating only when the interaction ends. Use this to eliminate minimap overhead in large diagrams (#638)
  • watermarkPosition property on FlowConfig — allows configuring the watermark corner position via NgDiagramPanelPosition, with automatic collision avoidance when a panel occupies the same corner (#621, #652) — thanks @jimmeryn for the issue submission! 🙏
  • setViewport(x, y, scale) method on NgDiagramViewportService — sets absolute viewport position and scale in a single call, enabling custom-anchor zoomToFit implementations (#591, #653) — thanks @MeMeMax for the discussion that led to this! 🙏
  • Generic type parameters on NgDiagramModelService getter methods (getNodeById, getEdgeById, getConnectedNodes, getConnectedEdges, getChildren, getChildrenNested, getParentHierarchy, getOverlappingNodes, getNodesInRange, getNearestNodeInRange, getNodeEnds) — eliminates the need for as casts when accessing typed node.data or edge.data (#654)
  • Exported DataObject type from public API (#654)
  • edgeDrawEnded event — fires on every linking gesture completion (success and cancel), with source, drop position, and cancel reason (noTarget, invalidConnection, invalidTarget) (#637, #655) — thanks @ninjapiratica for the inspiration! 🙏
  • selectNodeOnPortPress option on LinkingConfig — when false, port press only initiates linking without selecting the parent node. Default true preserves existing behavior (#637, #655) — thanks @ninjapiratica for the issue submission! 🙏
  • Minimap now caches MinimapNodeData by Node object reference, reusing cached data for unchanged nodes and reducing per-frame computation during interactions (#638)
  • initializeModel can now be safely called inside reactive contexts (computed, effect, linkedSignal) without throwing NG0602 (#608, #622)
  • Fixed palette drag preview not rendering when an ancestor element has overflow: hidden (#624)
  • Fixed port position not updating when side or originPoint input changes at runtime (#647) — thanks @ninjapiratica for the issue submission! 🙏
  • Fixed waitForMeasurements incurring a 2-second timeout when a transaction includes no-op updates (#648)
  • Fixed node position not being snapped when node snapping is enabled and node is dropped from palette or pasted onto the canvas (#649)
  • Fixed port hitbox (::before pseudo-element) not being centered on the port (#650)
  • updateNodeData and updateEdgeData now accept interfaces and union types — relaxed generic constraint from Record<string, unknown> | undefined to DataObject (#654)
  • edgeDrawn event — use edgeDrawEnded instead, which fires for both successful and cancelled draws. edgeDrawn continues to fire for backward compatibility (#655)
  • Updated MCP server README with ASCII diagrams, Windows setup instructions, and streamlined documentation (#610)
  • Added MCP Server documentation page and updated roadmap status (#610)
  • Fixed broken internal documentation URLs in Configuration, Edges, Changelog, and Policies pages (#610)
  • Exported missing PanningActionState and SelectionActionState types from public API (#610)
  • New MCP server for enhanced AI-assisted development with MiniSearch indexing and API symbol search (#590)
  • Fixed documentation pages being difficult to read on mobile devices due to excessive margins (#605 - thanks @martinboue for reporting this 💪)
  • Zoom support in Shortcut Manager - configurable keyboard shortcuts (keyboardZoomIn, keyboardZoomOut) and wheel-based zoom with modifier keys (zoom) via new WheelOnlyShortcutDefinition (#571)
  • Start and end lifecycle events for node interactions: nodeDragStarted/nodeDragEnded, nodeResizeStarted/nodeResizeEnded, nodeRotateStarted/nodeRotateEnded (#572)
  • selectionGestureEnded event - fires on pointerup after a selection gesture completes (object click, box selection, or select-all), providing the currently selected nodes and edges. Use this for actions that should run after selection is done, such as showing toolbars or updating panels (#582)
  • Absolute edge label positioning - positionOnEdge now accepts pixel-based strings ('30px', '-20px') in addition to relative numbers (0–1). Negative pixel values measure from the target end (#580)
  • Default edge now supports positionOnEdge data property to control label positioning (defaults to 0.5) (#581)
  • nodeIds property on DraggingActionState containing IDs of all nodes participating in the drag operation (#572)
  • movementStarted property on DraggingActionState that indicates whether pointer movement exceeded the drag threshold before entering the dragging state (#569)
  • initializeModelAdapter function for initializing custom ModelAdapter implementations. Use this when providing a custom adapter (e.g., backed by localStorage, NgRx, or an external store). The function prepares the adapter for use with ng-diagram. initializeModel continues to create the default SignalModelAdapter from Partial<Model> data. (#586)
  • Custom Model example now uses initializeModelAdapter and improved LocalStorageModelAdapter with Partial<Model> and ModelChanges types (#586)
  • Added explicit ModelAdapter return type to initializeModel() to prevent TypeScript errors when building with declaration: true (#573) (thanks @MeMeMax for reporting this 💪)
  • Edge labels vanishing permanently after model reinitialization (#585)
  • Edge labels not being measured when loading a model with pre-existing edge points (e.g., from localStorage) (#586)
  • selectionChanged event now fires after paste action, ensuring selection state stays in sync (#584)
  • Fixed compatibility issue with Angular 18 in default edge and minimap components (#587)

🎉 We’ve reached v1.0! This milestone marks a stable, feature-complete library for building interactive diagrams in Angular. We’d love to hear your feedback — share your thoughts in our GitHub Discussions or join us on Discord!

  • Improved diagram panning on Mac with Figma-like trackpad experience (#498)
  • Fixed keyboard shortcuts not working when CapsLock is enabled. Letter key shortcuts (e.g., Ctrl+C, Ctrl+V, Ctrl+A) now match case-insensitively (#546)
  • Fixed model reinitialization issues: viewport dimensions being undefined (causing zoomToFit and linking failures) and missing _internalId for nodes (causing Angular tracking issues) (#523)
  • toJSON() now strips readonly computed fields (measuredPorts, measuredBounds, computedZIndex) from serialized nodes and (measuredLabels, computedZIndex) from serialized edges. These are system-computed values that should be re-derived from the DOM on load, not persisted (#545)
  • Fixed resizing group with rotated child nodes (#504)
  • Fixed error on drag&drop HTML object (not palette node) to the diagram (#510)
  • API stability and deprecation policy documentation with defined stability levels and Angular version support matrix (#462)
  • API Extractor integration for automated breaking change detection with CI validation (#462)
  • Landing page diagram example in documentation (#464)
  • Floating edges for edges with no ports specified (#465)
  • Ports with custom content - ports can now render custom Angular components instead of simple circles (#468)
  • hideWatermark config option to hide the ngDiagram watermark via diagram configuration (#469)
  • Expose computePartsBounds method in API (#477)
  • Added overload to getOverlappingNodes to accept Node object in addition to node ID, supporting cases when the node object has newer data than the node in state (e.g., within middlewares) (#486)
  • modelActionTypes property on MiddlewareContext - an array containing all action types that triggered the middleware execution. For transactions, this includes the transaction name followed by all action types from commands executed within the transaction. For single commands, this is a single-element array (#489)
  • Add grab cursor on background when panning (#479)
  • Disable diagram panning by config viewportPanningEnabled (#480)
  • Async transaction support - transactions now accept async callbacks, allowing asynchronous operations like data fetching before adding or modifying the diagram (#493)
  • waitForMeasurements transaction option - ensures the transaction promise resolves only after all DOM measurements (node sizes, port positions, edge labels) are complete. Useful when performing viewport operations like zoomToFit() after adding or modifying elements (#493)
  • Standardized error messages across the ng-diagram library (#463)
  • Fixed misleading error when destroying NgDiagramModelService after engine is already destroyed. The error incorrectly reported “Library engine not initialized yet”. Now the service checks if engine is available and skips listener cleanup if already destroyed. (#466 - thanks for finding this @Filipstrozik 💪)
  • Fixed keyboard movement of nodes with arrow keys when using large snap step values (#461)
  • Fixed drag-snapping issues with different snapping configurations. The issue still occurred when dragging multiple nodes at the same hierarchy level (i.e., nodes without groups) (#470)
  • Fixed incorrectly computed measuredBounds for nodes (#486)
  • Fixed missing edge arrowheads in Safari. Safari doesn’t support context-stroke in SVG markers, so a fallback using inline markers with currentColor substitution is now used for Safari compatibility (#487)
  • Fixed copy-paste retaining groupId when pasting nodes outside their group. Now groupId is only preserved when the group is also copied, with the reference updated to the new group’s ID (#491)
  • Fixed zoom to fit not working correctly on diagram initialization (#492)
  • Fixed bullet points styles in the documentation (#494)
  • modelActionType property on MiddlewareContext is now deprecated. Use modelActionTypes instead, which supports multiple actions from transactions. (#489)
  • Tailwind CSS example in documentation (#436)
  • Fixed drag snapping with different snapping config issue (#451)
  • Fixed ungrouping when dragging node selected with group (#446)
  • Fixed shortcut capture, events, and collision with inputs (#447)
  • Fixed zIndex assignment (#449)
  • Fixed Layout in documentation (#438)
  • Fixed Reactive config in background (#445)
  • Fixed Example zomming in documentation (#448)

🎉 This is our first stable release! We’ve graduated from beta and are proud to present a production-ready version.

  • Zoom to fit feature with configurable padding and option to automatically apply on model initialization (#386)
  • Environment layer for unified environment - related functionalities (#350)
  • Helpers for node relationships and traversal (#395)
  • Box selection for selecting multiple nodes at once (#374)
  • Implemented multiple event hooks for ng-diagram (#387)
  • Configurable built-in grid background (#397)
  • Configurable Shortcut Manager (#398)
  • Improved collision detection for rotated nodes and introduced measuredBounds property to Node interface (#407)
  • Improved diagram navigation experience - smooth panning (#417)
  • Snapping documentation article explaining node snapping functionality (#414)
  • Diagram configuration documentation article (#419)
  • Microsnapping for angle adjustments (#404)
  • Background guide documentation article (#400)
  • Label support for default edges (#376)
  • Default node exported for public use (#377)
  • Center on node and center on rect command handlers for programmatic viewport control (#371)
  • Renamed ‘internal’ folder to ‘guides’ in documentation and updated all related links (#358)
  • Improved documentation examples structure for consistency (#360)
  • Unified documentation styles (#357)
  • Redirected documentation root to quick-start page and reordered Intro articles (#370)
  • Changed default behavior for resizable and rotatable properties on diagram nodes (#374)
  • Complete API documentation reorganization and improvements (#421)
  • Better configuration for resizable and rotatable properties on diagram nodes (#374)
  • Fixed NgDiagramModelService.addEdges not redrawing diagram (#369)
  • Fixed download image example not working in Angular 18 (#375)
  • Fixed model synchronization issues (#372)
  • Fixed base edge label component name and maintained backward compatibility with deprecated BaseEdgeLabelComponent alias (#368)
  • Fixed ESLint errors in Angular templates (#367)
  • Fixed multiple documentation issues and broken API links (#356)
  • Fixed post-release Angular 18 issues (#355)
  • Resolved context menu example to enable copying multiple nodes
  • Fixed diagram capturing all keyboard events on page (#444)
  • Fixed zIndex assignment for added nodes and multiple selection of group and children (#449)

Initial tagged release.