Skip to content

Commit 5f36a2f

Browse files
Merge pull request #589 from synergycodes/main
Release v.1.1.0
2 parents 22febac + 35a533d commit 5f36a2f

File tree

194 files changed

+9353
-1533
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+9353
-1533
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ yarn-error.log*
3535
*.pem
3636

3737
__tmp__*
38+
39+
# Kiro
40+
.kiro

CHANGELOG.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
## [1.1.0] - 2026-02-27
11+
12+
### Added
13+
14+
- Zoom support in [Shortcut Manager](/docs/guides/shortcut-manager/) - configurable keyboard shortcuts (`keyboardZoomIn`, `keyboardZoomOut`) and wheel-based zoom with modifier keys (`zoom`) via new `WheelOnlyShortcutDefinition` ([#571](https://github.com/synergycodes/ng-diagram/pull/571))
15+
- Start and end lifecycle events for node interactions: [`nodeDragStarted`](/docs/api/components/ngdiagramcomponent/#nodedragstarted)/[`nodeDragEnded`](/docs/api/components/ngdiagramcomponent/#nodedragended), [`nodeResizeStarted`](/docs/api/components/ngdiagramcomponent/#noderesizestarted)/[`nodeResizeEnded`](/docs/api/components/ngdiagramcomponent/#noderesizeended), [`nodeRotateStarted`](/docs/api/components/ngdiagramcomponent/#noderotatestarted)/[`nodeRotateEnded`](/docs/api/components/ngdiagramcomponent/#noderotateended) ([#572](https://github.com/synergycodes/ng-diagram/pull/572))
16+
- [`selectionGestureEnded`](/docs/api/types/events/selectiongestureendedevent) 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](https://github.com/synergycodes/ng-diagram/pull/582))
17+
- [Absolute edge label positioning](/docs/guides/edges/labels/#absolute-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](https://github.com/synergycodes/ng-diagram/pull/580))
18+
- Default edge now supports `positionOnEdge` data property to control [label positioning](/docs/guides/edges/labels/#using-labels-in-default-edges) (defaults to `0.5`) ([#581](https://github.com/synergycodes/ng-diagram/pull/581))
19+
- [`nodeIds`](/docs/api/types/action-state/draggingactionstate/#nodeids) property on `DraggingActionState` containing IDs of all nodes participating in the drag operation ([#572](https://github.com/synergycodes/ng-diagram/pull/572))
20+
- [`movementStarted`](/docs/api/types/action-state/draggingactionstate/#movementstarted) property on `DraggingActionState` that indicates whether pointer movement exceeded the drag threshold before entering the dragging state ([#569](https://github.com/synergycodes/ng-diagram/pull/569))
21+
- [`initializeModelAdapter`](/docs/api/utilities/initializemodeladapter) function for initializing custom [`ModelAdapter`](/docs/api/types/model/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](https://github.com/synergycodes/ng-diagram/pull/586))
22+
23+
### Changed
24+
25+
- [Custom Model example](/docs/examples/custom-model) now uses `initializeModelAdapter` and improved `LocalStorageModelAdapter` with `Partial<Model>` and `ModelChanges` types ([#586](https://github.com/synergycodes/ng-diagram/pull/586))
26+
27+
### Fixed
28+
29+
- Added explicit `ModelAdapter` return type to `initializeModel()` to prevent TypeScript errors when building with `declaration: true` ([#573](https://github.com/synergycodes/ng-diagram/pull/573)) (thanks [@MeMeMax](https://github.com/MeMeMax) for reporting this 💪)
30+
- Edge labels vanishing permanently after model reinitialization ([#585](https://github.com/synergycodes/ng-diagram/pull/585))
31+
- Edge labels not being measured when loading a model with pre-existing edge points (e.g., from localStorage) ([#586](https://github.com/synergycodes/ng-diagram/pull/586))
32+
- `selectionChanged` event now fires after paste action, ensuring selection state stays in sync ([#584](https://github.com/synergycodes/ng-diagram/pull/584))
33+
- Fixed compatibility issue with Angular 18 in default edge and minimap components ([#587](https://github.com/synergycodes/ng-diagram/pull/587))
34+
835
## [1.0.0] - 2026-02-06
936

1037
🎉 **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](https://github.com/synergycodes/ng-diagram/discussions) or join us on [Discord](https://discord.gg/FDMjRuarFb)!
@@ -137,7 +164,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
137164

138165
Initial tagged release.
139166

140-
[unreleased]: https://github.com/synergycodes/ng-diagram/compare/v1.0.0...HEAD
167+
[unreleased]: https://github.com/synergycodes/ng-diagram/compare/v1.1.0...HEAD
168+
[1.1.0]: https://github.com/synergycodes/ng-diagram/compare/v1.0.0...v1.1.0
141169
[1.0.0]: https://github.com/synergycodes/ng-diagram/compare/v0.9.1...v1.0.0
142170
[0.9.1]: https://github.com/synergycodes/ng-diagram/compare/v0.9.0...v0.9.1
143171
[0.9.0]: https://github.com/synergycodes/ng-diagram/releases/tag/v0.9.0

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ With ng-diagram, you can create:
3030
- **Circuit Diagrams**: Electronic schematics and technical drawings
3131
- **Custom Visualizations**: Any diagram type with custom node and edge templates
3232

33+
## 🎮 Try the Demo
34+
35+
See ng-diagram in action: **[Live Demo](https://synergycodes.github.io/ng-diagram-demo/)** | **[Source Code](https://github.com/synergycodes/ng-diagram-demo)**
36+
3337
## 🚀 Quick Start
3438

3539
### Installation
@@ -205,11 +209,13 @@ This project is licensed under the Apache 2.0 License - see the [LICENSE](https:
205209
- **Documentation**: [https://www.ngdiagram.dev/docs](https://www.ngdiagram.dev/docs)
206210
- **GitHub**: [https://github.com/synergycodes/ng-diagram](https://github.com/synergycodes/ng-diagram)
207211
- **NPM**: [https://www.npmjs.com/package/ng-diagram](https://www.npmjs.com/package/ng-diagram)
212+
- **Website**: [https://www.ngdiagram.dev](https://www.ngdiagram.dev)
208213

209214
## 🆘 Support
210215

211216
- **Issues**: [GitHub Issues](https://github.com/synergycodes/ng-diagram/issues)
212-
- **Discussions**: [GitHub Discussions](https://github.com/synergycodes/ng-diagram/discussions)
217+
- **Discussions**: [GitHub Discussions](https://github.com/synergycodes/ng-diagram/discussions), [Discord](https://discord.gg/FDMjRuarFb)
218+
- **Project consulting**: [Contact](https://www.ngdiagram.dev/contact)
213219
- **Documentation**: [https://www.ngdiagram.dev/docs](https://www.ngdiagram.dev/docs)
214220

215221
---

apps/angular-demo/src/app/app.component.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,29 @@
66
[config]="config"
77
(diagramInit)="onDiagramInit($event)"
88
(selectionChanged)="onSelectionChanged($event)"
9+
(selectionGestureEnded)="onSelectionGestureEnded($event)"
910
(groupMembershipChanged)="onGroupMembershipChanged($event)"
1011
(selectionRotated)="onSelectionRotated($event)"
12+
(nodeRotateStarted)="onNodeRotateStarted($event)"
13+
(nodeRotateEnded)="onNodeRotateEnded($event)"
1114
(edgeDrawn)="onEdgeDrawn($event)"
1215
(clipboardPasted)="onClipboardPasted($event)"
1316
(nodeResized)="onNodeResized($event)"
17+
(nodeResizeStarted)="onNodeResizeStarted($event)"
18+
(nodeResizeEnded)="onNodeResizeEnded($event)"
1419
(paletteItemDropped)="onPaletteItemDropped($event)"
1520
(selectionRemoved)="onSelectionRemoved($event)"
21+
(nodeDragStarted)="onNodeDragStarted($event)"
22+
(nodeDragEnded)="onNodeDragEnded($event)"
1623
>
1724
<ng-diagram-background type="grid"></ng-diagram-background>
1825
</ng-diagram>
1926
<ng-diagram-minimap [nodeStyle]="nodeStyle" [minimapNodeTemplateMap]="minimapNodeTemplateMap" />
2027
<app-toolbar
2128
(testVirtualizationClick)="enableVirtualizationTest()"
2229
(reinitializeModelClick)="onReinitializeModel()"
30+
(saveModelClick)="onSaveModel()"
31+
(loadModelClick)="onLoadModel()"
2332
/>
2433
<app-palette [model]="paletteModel" />
2534
</div>

apps/angular-demo/src/app/app.component.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ import {
1313
NgDiagramEdgeTemplateMap,
1414
NgDiagramMinimapComponent,
1515
NgDiagramMinimapNodeTemplateMap,
16+
NgDiagramModelService,
1617
NgDiagramNodeTemplateMap,
1718
NgDiagramPaletteItem,
19+
NodeDragEndedEvent,
20+
NodeDragStartedEvent,
1821
NodeResizedEvent,
22+
NodeResizeEndedEvent,
23+
NodeResizeStartedEvent,
24+
NodeRotateEndedEvent,
25+
NodeRotateStartedEvent,
1926
PaletteItemDroppedEvent,
2027
provideNgDiagram,
2128
SelectionChangedEvent,
29+
SelectionGestureEndedEvent,
2230
SelectionRemovedEvent,
2331
SelectionRotatedEvent,
2432
type Edge,
@@ -39,6 +47,8 @@ import { ImageMinimapNodeComponent } from './minimap-node-template/image-minimap
3947
import { PaletteComponent } from './palette/palette.component';
4048
import { ToolbarComponent } from './toolbar/toolbar.component';
4149

50+
const LOCAL_STORAGE_KEY = 'ng-diagram-demo';
51+
4252
@Component({
4353
selector: 'app-root',
4454
templateUrl: './app.component.html',
@@ -55,6 +65,7 @@ import { ToolbarComponent } from './toolbar/toolbar.component';
5565
})
5666
export class AppComponent {
5767
private readonly injector = inject(Injector);
68+
private readonly modelService = inject(NgDiagramModelService);
5869

5970
paletteModel: NgDiagramPaletteItem[] = paletteModel;
6071
nodeTemplateMap: NgDiagramNodeTemplateMap = nodeTemplateMap;
@@ -143,6 +154,13 @@ export class AppComponent {
143154
});
144155
}
145156

157+
onSelectionGestureEnded(event: SelectionGestureEndedEvent): void {
158+
console.log('Selection Gesture Ended:', {
159+
nodes: event.nodes.map((n: Node) => n.id),
160+
edges: event.edges.map((e: Edge) => e.id),
161+
});
162+
}
163+
146164
onEdgeDrawn(event: EdgeDrawnEvent): void {
147165
console.log('Edge Drawn:', {
148166
edge: event.edge.id,
@@ -170,6 +188,18 @@ export class AppComponent {
170188
});
171189
}
172190

191+
onNodeResizeStarted(event: NodeResizeStartedEvent): void {
192+
console.log('Node Resize Started:', {
193+
node: event.node.id,
194+
});
195+
}
196+
197+
onNodeResizeEnded(event: NodeResizeEndedEvent): void {
198+
console.log('Node Resize Ended:', {
199+
node: { id: event.node.id, size: event.node.size },
200+
});
201+
}
202+
173203
onPaletteItemDropped(event: PaletteItemDroppedEvent): void {
174204
console.log('Palette Item Dropped:', {
175205
node: event.node.id,
@@ -209,6 +239,42 @@ export class AppComponent {
209239
});
210240
}
211241

242+
onNodeRotateStarted(event: NodeRotateStartedEvent): void {
243+
console.log('Node Rotate Started:', {
244+
node: event.node.id,
245+
});
246+
}
247+
248+
onNodeRotateEnded(event: NodeRotateEndedEvent): void {
249+
console.log('Node Rotate Ended:', {
250+
node: { id: event.node.id, angle: event.node.angle },
251+
});
252+
}
253+
254+
onNodeDragStarted(event: NodeDragStartedEvent): void {
255+
console.log('Node Drag Started:', {
256+
nodes: event.nodes.map((n: Node) => n.id),
257+
});
258+
}
259+
260+
onNodeDragEnded(event: NodeDragEndedEvent): void {
261+
console.log('Node Drag Ended:', {
262+
nodes: event.nodes.map((n: Node) => ({ id: n.id, position: n.position })),
263+
});
264+
}
265+
266+
onSaveModel(): void {
267+
localStorage.setItem(LOCAL_STORAGE_KEY, this.modelService.toJSON());
268+
}
269+
270+
onLoadModel(): void {
271+
const json = localStorage.getItem(LOCAL_STORAGE_KEY);
272+
if (!json) return;
273+
274+
const data = JSON.parse(json);
275+
this.model = initializeModel(data, this.injector);
276+
}
277+
212278
onReinitializeModel(): void {
213279
this.config = {
214280
...this.config,

apps/angular-demo/src/app/data/default-model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const defaultModel: DiagramModel = {
139139
id: '3',
140140
source: '5',
141141
target: '6',
142-
data: { labelPosition: '0.45' },
142+
data: { labelPosition: 0.45 },
143143
sourcePort: 'port-right',
144144
targetPort: 'port-left',
145145
type: 'labelled-edge',

apps/angular-demo/src/app/edge-template/labelled-edge/labelled-edge.component.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';
2-
import { Edge, NgDiagramBaseEdgeComponent, NgDiagramBaseEdgeLabelComponent, NgDiagramEdgeTemplate } from 'ng-diagram';
2+
import {
3+
Edge,
4+
EdgeLabelPosition,
5+
NgDiagramBaseEdgeComponent,
6+
NgDiagramBaseEdgeLabelComponent,
7+
NgDiagramEdgeTemplate,
8+
} from 'ng-diagram';
39

410
/**
511
* Simple edge with a single label at the center.
@@ -21,5 +27,5 @@ export class LabelledEdgeComponent implements NgDiagramEdgeTemplate {
2127
}
2228

2329
interface Data {
24-
labelPosition?: number;
30+
labelPosition?: EdgeLabelPosition;
2531
}

apps/angular-demo/src/app/toolbar/toolbar.component.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
<button (click)="onLinkCreationClick()">Start Link Creation</button>
44
<button [disabled]="!isNodeSelected()" (click)="onCenterOnClick()">Center on selected node</button>
55
<button [disabled]="!isNodeSelected()" (click)="onChangeNodeTypeClick()">Change Node Type</button>
6+
<button [disabled]="!selectedLabelledEdge()" (click)="onToggleLabelPositionClick()">
7+
{{ isLabelAbsolute() ? 'Label: Absolute → Relative' : 'Label: Relative → Absolute' }}
8+
</button>
69
<button (click)="onZoomToFitClick()">Zoom to fit</button>
710
<button (click)="onAsyncTransactionWithMeasurementsDemo()">Async + Measurements + Zoom</button>
11+
<button (click)="saveModelClick.emit()">Save to localStorage</button>
12+
<button (click)="loadModelClick.emit()">Load from localStorage</button>
813
<button (click)="testVirtualizationClick.emit()">Test Virtualization</button>
914
<button (click)="reinitializeModelClick.emit()">Reinitialize Model</button>
1015
</div>

apps/angular-demo/src/app/toolbar/toolbar.component.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { CommonModule } from '@angular/common';
22
import { Component, computed, inject, output } from '@angular/core';
33
import {
4+
Edge,
5+
EdgeLabelPosition,
46
NgDiagramModelService,
57
NgDiagramSelectionService,
68
NgDiagramService,
@@ -25,11 +27,35 @@ export class ToolbarComponent {
2527

2628
reinitializeModelClick = output<void>();
2729
testVirtualizationClick = output<void>();
30+
saveModelClick = output<void>();
31+
loadModelClick = output<void>();
2832

2933
isNodeSelected = computed(() => this.ngDiagramSelectionService.selection().nodes.length > 0);
3034

35+
selectedLabelledEdge = computed(() => {
36+
const edges = this.ngDiagramSelectionService.selection().edges;
37+
return edges.find((edge: Edge) => edge.type === 'labelled-edge') ?? null;
38+
});
39+
40+
isLabelAbsolute = computed(() => {
41+
const edge = this.selectedLabelledEdge();
42+
if (!edge) return false;
43+
const pos = (edge.data as { labelPosition?: EdgeLabelPosition }).labelPosition;
44+
return typeof pos === 'string';
45+
});
46+
3147
isDebugModeEnabled = computed(() => this.ngDiagramService.config().debugMode || false);
3248

49+
onToggleLabelPositionClick(): void {
50+
const edge = this.selectedLabelledEdge();
51+
if (!edge) return;
52+
53+
const currentPos = (edge.data as { labelPosition?: EdgeLabelPosition }).labelPosition ?? 0.5;
54+
const newPos: EdgeLabelPosition = typeof currentPos === 'string' ? 0.5 : '50px';
55+
56+
this.ngDiagramModelService.updateEdgeData(edge.id, { ...edge.data, labelPosition: newPos });
57+
}
58+
3359
onToggleDebugModeClick(): void {
3460
this.ngDiagramService.updateConfig({ debugMode: !this.isDebugModeEnabled() });
3561
}

apps/docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export default defineConfig({
145145
}),
146146
],
147147
components: {
148+
SiteTitle: './src/components/site-title/site-title.astro',
148149
SocialIcons: './src/components/social-icons/social-icons.astro',
149150
PageTitle: './src/components/page-title/page-title.astro',
150151
ThemeProvider: './src/components/ForceDarkTheme.astro',

0 commit comments

Comments
 (0)