@likec4/diagram 1.20.0 → 1.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +1 -0
  2. package/dist/LikeC4Diagram.css.js +3 -1
  3. package/dist/base/BaseXYFlow.js +15 -14
  4. package/dist/base/Link.css.d.ts +3 -0
  5. package/dist/base/Link.css.js +9 -0
  6. package/dist/base/Link.d.ts +5 -0
  7. package/dist/base/Link.js +58 -0
  8. package/dist/base/const.d.ts +1 -0
  9. package/dist/base/const.js +2 -1
  10. package/dist/base/example/custom.d.ts +1 -1
  11. package/dist/base/index.d.ts +1 -0
  12. package/dist/base/primitives/compound/CompoundActionButton.js +2 -1
  13. package/dist/base/primitives/compound/CompoundDetailsButton.js +2 -1
  14. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
  15. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -6
  16. package/dist/base/primitives/compound/CompoundNodeContainer.js +2 -2
  17. package/dist/base/primitives/customEdge.d.ts +1 -1
  18. package/dist/base/primitives/customEdge.js +1 -1
  19. package/dist/base/primitives/customNode.js +1 -1
  20. package/dist/base/primitives/edge/EdgeLabel.js +3 -1
  21. package/dist/base/primitives/edge/EdgePath.d.ts +2 -1
  22. package/dist/base/primitives/edge/EdgePath.js +2 -0
  23. package/dist/base/primitives/element/ElementActionButtons.js +2 -1
  24. package/dist/base/primitives/element/ElementDetailsButton.js +2 -1
  25. package/dist/base/primitives/element/ElementNodeContainer.css.d.ts +0 -1
  26. package/dist/base/primitives/element/ElementNodeContainer.css.js +1 -3
  27. package/dist/base/primitives/element/ElementNodeContainer.js +2 -3
  28. package/dist/base/primitives/element/ElementShape.js +4 -2
  29. package/dist/base/primitives/element/ElementTitle.js +2 -1
  30. package/dist/base/updateEdges.js +3 -3
  31. package/dist/base/updateNodes.js +2 -2
  32. package/dist/context/FramerMotionConfig.js +1 -1
  33. package/dist/hooks/useDiagram.d.ts +313 -90
  34. package/dist/hooks/useDiagramActor.d.ts +2 -2
  35. package/dist/hooks/useDiagramContext.d.ts +1 -1
  36. package/dist/hooks/useXYFlow.d.ts +2 -1
  37. package/dist/hooks/useXYFlow.js +5 -0
  38. package/dist/likec4diagram/XYFlow.js +12 -10
  39. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +1 -1
  40. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -2
  41. package/dist/likec4diagram/custom/edges/utils.js +0 -1
  42. package/dist/likec4diagram/custom/index.d.ts +1 -1
  43. package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
  44. package/dist/likec4diagram/state/actorContext.d.ts +1438 -364
  45. package/dist/likec4diagram/state/assign.js +2 -2
  46. package/dist/likec4diagram/state/machine.d.ts +321 -93
  47. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +1 -1
  48. package/dist/likec4diagram/types.d.ts +1 -0
  49. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +4 -3
  50. package/dist/likec4diagram/useViewToNodesEdges.js +3 -1
  51. package/dist/overlays/element-details/ElementDetailsCard.css.d.ts +0 -1
  52. package/dist/overlays/element-details/ElementDetailsCard.css.js +1 -3
  53. package/dist/overlays/element-details/ElementDetailsCard.js +5 -30
  54. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -4
  55. package/dist/overlays/relationship-details/custom/ElementActions.js +1 -3
  56. package/dist/overlays/relationship-details/custom/edgeTypes.d.ts +1 -1
  57. package/dist/overlays/relationship-details/hooks.d.ts +2 -2
  58. package/dist/overlays/relationship-details/hooks.js +7 -7
  59. package/dist/overlays/relationship-details/useLayoutedDetails.js +7 -10
  60. package/dist/overlays/relationships-browser/-useRelationshipsView.d.ts +4 -1
  61. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -11
  62. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +81 -66
  63. package/dist/overlays/relationships-browser/SelectElement.js +0 -1
  64. package/dist/overlays/relationships-browser/_types.d.ts +2 -1
  65. package/dist/overlays/relationships-browser/actor.d.ts +15 -2
  66. package/dist/overlays/relationships-browser/actor.js +262 -65
  67. package/dist/overlays/relationships-browser/const.d.ts +1 -0
  68. package/dist/overlays/relationships-browser/const.js +4 -0
  69. package/dist/overlays/relationships-browser/custom/ElementActions.js +1 -1
  70. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -2
  71. package/dist/overlays/relationships-browser/custom/edgeTypes.js +24 -6
  72. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -2
  73. package/dist/overlays/relationships-browser/hooks.d.ts +6 -3
  74. package/dist/overlays/relationships-browser/hooks.js +4 -2
  75. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +7 -2
  76. package/dist/style.css +1 -1
  77. package/dist/utils/xyflow.js +9 -1
  78. package/package.json +8 -8
  79. package/src/LikeC4Diagram.css.ts +8 -0
  80. package/src/base/BaseXYFlow.tsx +23 -14
  81. package/src/base/Link.css.ts +49 -0
  82. package/src/base/Link.tsx +66 -0
  83. package/src/base/const.ts +1 -0
  84. package/src/base/index.ts +2 -0
  85. package/src/base/primitives/compound/CompoundActionButton.tsx +2 -1
  86. package/src/base/primitives/compound/CompoundDetailsButton.tsx +2 -1
  87. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +16 -10
  88. package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -1
  89. package/src/base/primitives/customEdge.ts +2 -0
  90. package/src/base/primitives/customNode.ts +3 -0
  91. package/src/base/primitives/edge/EdgeLabel.tsx +3 -1
  92. package/src/base/primitives/edge/EdgePath.tsx +3 -0
  93. package/src/base/primitives/element/ElementActionButtons.tsx +2 -1
  94. package/src/base/primitives/element/ElementDetailsButton.tsx +2 -1
  95. package/src/base/primitives/element/ElementNodeContainer.css.ts +9 -4
  96. package/src/base/primitives/element/ElementNodeContainer.tsx +1 -2
  97. package/src/base/primitives/element/ElementShape.tsx +4 -3
  98. package/src/base/primitives/element/ElementTitle.tsx +2 -1
  99. package/src/base/updateEdges.ts +3 -2
  100. package/src/base/updateNodes.ts +3 -2
  101. package/src/context/DiagramFeatures.tsx +1 -0
  102. package/src/context/FramerMotionConfig.tsx +1 -1
  103. package/src/hooks/useDiagramActor.ts +7 -5
  104. package/src/hooks/useDiagramContext.ts +4 -1
  105. package/src/hooks/useXYFlow.ts +6 -1
  106. package/src/likec4diagram/XYFlow.tsx +20 -9
  107. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -2
  108. package/src/likec4diagram/custom/edges/utils.ts +0 -1
  109. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  110. package/src/likec4diagram/types.ts +1 -0
  111. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +4 -3
  112. package/src/likec4diagram/useViewToNodesEdges.ts +1 -0
  113. package/src/overlays/element-details/ElementDetailsCard.css.ts +0 -28
  114. package/src/overlays/element-details/ElementDetailsCard.tsx +2 -50
  115. package/src/overlays/relationship-details/RelationshipDetails.tsx +4 -4
  116. package/src/overlays/relationship-details/custom/ElementActions.tsx +1 -2
  117. package/src/overlays/relationship-details/custom/edgeTypes.tsx +0 -5
  118. package/src/overlays/relationship-details/hooks.ts +6 -6
  119. package/src/overlays/relationship-details/useLayoutedDetails.ts +7 -10
  120. package/src/overlays/relationships-browser/-useRelationshipsView.ts +16 -12
  121. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +105 -68
  122. package/src/overlays/relationships-browser/SelectElement.tsx +0 -1
  123. package/src/overlays/relationships-browser/_types.ts +2 -1
  124. package/src/overlays/relationships-browser/actor.ts +321 -62
  125. package/src/overlays/relationships-browser/const.ts +1 -0
  126. package/src/overlays/relationships-browser/custom/ElementActions.tsx +1 -1
  127. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +25 -6
  128. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +3 -2
  129. package/src/overlays/relationships-browser/hooks.ts +3 -1
  130. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +8 -2
  131. package/dist/likec4diagram/ui/diagram-title/Link.js +0 -36
  132. package/dist/ui/Link.d.ts +0 -4
  133. package/src/ui/Link.tsx +0 -40
@@ -1,9 +1,10 @@
1
1
  import { BBox, DiagramView, Fqn } from '@likec4/core';
2
- import { EdgeChange, NodeChange, ReactFlowInstance } from '@xyflow/react';
2
+ import { EdgeChange, NodeChange, ReactFlowInstance, useStoreApi } from '@xyflow/react';
3
3
  import { ActorLogic, ActorRefFromLogic, MachineSnapshot, SnapshotFrom } from 'xstate';
4
4
  import { LayoutRelationshipsViewResult } from './-useRelationshipsView';
5
5
  import { RelationshipsBrowserTypes } from './_types';
6
6
  type XYFLowInstance = ReactFlowInstance<RelationshipsBrowserTypes.Node, RelationshipsBrowserTypes.Edge>;
7
+ export type XYStoreApi = ReturnType<typeof useStoreApi<RelationshipsBrowserTypes.Node, RelationshipsBrowserTypes.Edge>>;
7
8
  export type Input = {
8
9
  subject: Fqn;
9
10
  scope?: DiagramView | null;
@@ -14,14 +15,17 @@ export type Context = Readonly<Input & {
14
15
  closeable: boolean;
15
16
  enableNavigationMenu: boolean;
16
17
  xyflow: XYFLowInstance | null;
18
+ xystore: XYStoreApi | null;
17
19
  initialized: boolean;
18
20
  layouted: LayoutRelationshipsViewResult | null;
21
+ navigateFromNode: string | null;
19
22
  xynodes: RelationshipsBrowserTypes.Node[];
20
23
  xyedges: RelationshipsBrowserTypes.Edge[];
21
24
  }>;
22
25
  export type Events = {
23
26
  type: 'xyflow.init';
24
27
  instance: XYFLowInstance;
28
+ store: XYStoreApi;
25
29
  } | {
26
30
  type: 'xyflow.nodeClick';
27
31
  node: RelationshipsBrowserTypes.Node;
@@ -46,6 +50,8 @@ export type Events = {
46
50
  } | {
47
51
  type: 'xyflow.edgeMouseLeave';
48
52
  edge: RelationshipsBrowserTypes.Edge;
53
+ } | {
54
+ type: 'xyflow.updateNodeInternals';
49
55
  } | {
50
56
  type: 'fitDiagram';
51
57
  duration?: number;
@@ -53,13 +59,20 @@ export type Events = {
53
59
  } | {
54
60
  type: 'navigate.to';
55
61
  subject: Fqn;
62
+ fromNode?: string | undefined;
63
+ } | {
64
+ type: 'update.xydata';
65
+ xynodes: RelationshipsBrowserTypes.Node[];
66
+ xyedges: RelationshipsBrowserTypes.Edge[];
56
67
  } | {
57
68
  type: 'update.view';
58
69
  layouted: LayoutRelationshipsViewResult;
59
70
  } | {
60
71
  type: 'close';
61
72
  };
62
- export declare const relationshipsBrowserActor: ActorLogic<MachineSnapshot<Context, Events, any, "opening" | "active" | "closed", any, any, any, any>, Events, Input, any, any>;
73
+ export declare const relationshipsBrowserActor: ActorLogic<MachineSnapshot<Context, Events, any, "initializing" | "waiting-data" | "closed" | {
74
+ active: "idle" | "layouting";
75
+ }, "active", any, any, any>, Events, Input, any, any>;
63
76
  export type RelationshipsBrowserLogic = typeof relationshipsBrowserActor;
64
77
  export type RelationshipsBrowserActorRef = ActorRefFromLogic<typeof relationshipsBrowserActor>;
65
78
  export type RelationshipsBrowserSnapshot = SnapshotFrom<RelationshipsBrowserActorRef>;
@@ -1,85 +1,276 @@
1
- import { invariant } from "@likec4/core";
1
+ import { invariant, nonNullable, delay } from "@likec4/core";
2
2
  import { applyNodeChanges, applyEdgeChanges } from "@xyflow/react";
3
- import { prop } from "remeda";
4
- import { setup, assign, enqueueActions, cancel, raise } from "xstate";
5
- import { MinZoom } from "../../base/const.js";
3
+ import { getViewportForBounds, getNodeDimensions } from "@xyflow/system";
4
+ import { omit, prop, isNullish } from "remeda";
5
+ import { setup, fromPromise, assign, raise, enqueueActions, cancel } from "xstate";
6
+ import { MinZoom, ZIndexes } from "../../base/const.js";
6
7
  import { updateEdges } from "../../base/updateEdges.js";
7
8
  import { updateNodes } from "../../base/updateNodes.js";
9
+ import { ViewPadding } from "./const.js";
8
10
  import { viewToNodesEdge } from "./useViewToNodesEdges.js";
11
+ import { centerXYInternalNode } from "../../utils/xyflow.js";
12
+ import { Base } from "../../base/types.js";
13
+ const findRootSubject = (nodes) => nodes.find(
14
+ (n) => n.data.column === "subjects" && isNullish(n.parentId)
15
+ );
9
16
  const relationshipsBrowserActor = setup({
10
17
  types: {
11
18
  context: {},
19
+ tags: "",
12
20
  input: {},
13
21
  events: {}
14
22
  },
15
23
  actions: {
24
+ "xyflow:updateNodeInternals": ({ context }) => {
25
+ invariant(context.xystore, "xystore is not initialized");
26
+ const { domNode, updateNodeInternals } = context.xystore.getState();
27
+ const nodeIds = new Set(context.xyedges.flatMap((e) => [e.source, e.target]));
28
+ if (nodeIds.size === 0 || !domNode) {
29
+ return;
30
+ }
31
+ const updates = /* @__PURE__ */ new Map();
32
+ for (const updateId of nodeIds) {
33
+ const nodeElement = domNode.querySelector(`.react-flow__node[data-id="${updateId}"]`);
34
+ if (nodeElement) {
35
+ updates.set(updateId, { id: updateId, nodeElement, force: true });
36
+ }
37
+ }
38
+ requestAnimationFrame(() => updateNodeInternals(updates, { triggerFitView: false }));
39
+ },
16
40
  "xyflow:fitDiagram": ({ context }, params) => {
17
- const {
41
+ var _a;
42
+ let {
18
43
  duration = 450,
19
44
  bounds
20
45
  } = params ?? {};
21
- const { xyflow } = context;
46
+ const { xyflow, xystore } = context;
22
47
  invariant(xyflow, "xyflow is not initialized");
48
+ invariant(xystore, "xystore is not initialized");
49
+ bounds ?? (bounds = (_a = context.layouted) == null ? void 0 : _a.bounds);
50
+ const maxZoom = Math.max(xyflow.getZoom(), 1);
23
51
  if (bounds) {
24
- xyflow.fitBounds(bounds, duration > 0 ? { duration } : void 0);
52
+ const { width, height } = xystore.getState();
53
+ const viewport = getViewportForBounds(bounds, width, height, MinZoom, maxZoom, ViewPadding);
54
+ requestAnimationFrame(() => xyflow.setViewport(viewport, duration > 0 ? { duration } : void 0));
25
55
  } else {
26
- const maxZoom = Math.max(xyflow.getZoom(), 1);
27
- xyflow.fitView({
28
- minZoom: MinZoom,
29
- maxZoom,
30
- padding: 0.1,
31
- ...duration > 0 && { duration }
32
- });
56
+ requestAnimationFrame(
57
+ () => xyflow.fitView({
58
+ minZoom: MinZoom,
59
+ maxZoom,
60
+ padding: ViewPadding,
61
+ ...duration > 0 && { duration }
62
+ })
63
+ );
33
64
  }
34
65
  }
35
66
  },
36
67
  guards: {
37
- "enable: navigate.to": () => true
68
+ "enable: navigate.to": () => true,
69
+ "closeable": ({ context }) => context.closeable
70
+ },
71
+ actors: {
72
+ layouter: fromPromise(async ({ input, self, signal }) => {
73
+ const {
74
+ subjectId,
75
+ navigateFromNode,
76
+ xyflow,
77
+ xystore,
78
+ update
79
+ } = input;
80
+ let {
81
+ nodes: currentNodes,
82
+ setNodes,
83
+ setEdges,
84
+ width,
85
+ height
86
+ } = xystore.getState();
87
+ const next = viewToNodesEdge(update);
88
+ const updateXYData = () => {
89
+ const { nodes, edges } = xystore.getState();
90
+ return {
91
+ xynodes: updateNodes(nodes, next.xynodes),
92
+ xyedges: updateEdges(edges, next.xyedges)
93
+ };
94
+ };
95
+ const parent = nonNullable(self._parent);
96
+ let zoom = xyflow.getZoom();
97
+ const maxZoom = Math.max(zoom, 1);
98
+ const nextviewport = getViewportForBounds(update.bounds, width, height, MinZoom, maxZoom, ViewPadding);
99
+ const nextSubjectNode = next.xynodes.find(
100
+ (n) => n.type !== "empty" && n.data.column === "subjects" && n.data.fqn === subjectId
101
+ ) ?? findRootSubject(next.xynodes);
102
+ const currentSubjectNode = findRootSubject(currentNodes);
103
+ const existingNode = navigateFromNode ? currentNodes.find((n) => n.id === navigateFromNode) : currentNodes.find((n) => n.type !== "empty" && n.data.column !== "subjects" && n.data.fqn === subjectId);
104
+ if (!nextSubjectNode || !existingNode || nextSubjectNode.type === "empty" || !currentSubjectNode || nextSubjectNode.data.fqn === currentSubjectNode.data.fqn) {
105
+ xyflow.setViewport(nextviewport);
106
+ return updateXYData();
107
+ }
108
+ const nextSubjectCenter = {
109
+ x: nextSubjectNode.position.x + (nextSubjectNode.initialWidth ?? 0) / 2,
110
+ y: nextSubjectNode.position.y + (nextSubjectNode.initialHeight ?? 0) / 2
111
+ };
112
+ const currentSubjectInternalNode = xyflow.getInternalNode(currentSubjectNode.id);
113
+ const currentSubjectCenter = centerXYInternalNode(currentSubjectInternalNode);
114
+ const existingInternalNode = xyflow.getInternalNode(existingNode.id);
115
+ const existingDimensions = getNodeDimensions(existingInternalNode);
116
+ const nested = /* @__PURE__ */ new Set();
117
+ currentNodes.forEach((n) => {
118
+ if (n.id === existingNode.id) {
119
+ return;
120
+ }
121
+ if (n.data.column === "subjects") {
122
+ nested.add(n.id);
123
+ return;
124
+ }
125
+ if (n.parentId && (n.parentId === existingNode.id || nested.has(n.parentId))) {
126
+ nested.add(n.id);
127
+ }
128
+ });
129
+ currentNodes = updateNodes(
130
+ currentNodes,
131
+ currentNodes.flatMap((n) => {
132
+ if (nested.has(n.id)) {
133
+ return [];
134
+ }
135
+ if (n.id !== existingNode.id) {
136
+ return {
137
+ ...n,
138
+ data: {
139
+ ...n.data,
140
+ dimmed: n.data.column === "subjects" ? "immediate" : true
141
+ }
142
+ };
143
+ }
144
+ return {
145
+ ...omit(n, ["parentId"]),
146
+ position: {
147
+ x: currentSubjectCenter.x - existingDimensions.width / 2,
148
+ y: currentSubjectCenter.y - existingDimensions.height / 2
149
+ },
150
+ zIndex: ZIndexes.Max,
151
+ hidden: false,
152
+ data: {
153
+ ...n.data,
154
+ dimmed: false
155
+ }
156
+ };
157
+ })
158
+ );
159
+ parent.send({
160
+ type: "update.xydata",
161
+ xynodes: currentNodes,
162
+ xyedges: []
163
+ });
164
+ zoom = Math.min(
165
+ zoom,
166
+ nextviewport.zoom
167
+ );
168
+ await delay(180);
169
+ next.xynodes = next.xynodes.map(Base.setDimmed(false));
170
+ if (signal.aborted) {
171
+ return updateXYData();
172
+ }
173
+ await xyflow.setCenter(currentSubjectCenter.x, currentSubjectCenter.y, { zoom, duration: 350 });
174
+ xyflow.setCenter(nextSubjectCenter.x, nextSubjectCenter.y, { zoom });
175
+ return updateXYData();
176
+ })
38
177
  }
39
178
  }).createMachine({
40
- initial: "waiting-data",
179
+ initial: "initializing",
41
180
  context: ({ input }) => ({
42
181
  ...input,
43
182
  closeable: input.closeable ?? true,
44
183
  enableNavigationMenu: input.enableNavigationMenu ?? true,
45
184
  initialized: false,
46
185
  xyflow: null,
186
+ xystore: null,
47
187
  layouted: null,
188
+ navigateFromNode: null,
48
189
  xynodes: [],
49
190
  xyedges: []
50
191
  }),
51
192
  states: {
52
- "waiting-data": {
193
+ "initializing": {
53
194
  on: {
54
- "update.view": {
55
- actions: assign(({ event }) => {
56
- return {
57
- layouted: event.layouted,
58
- ...viewToNodesEdge(event.layouted)
59
- };
60
- }),
61
- target: "opening"
195
+ "xyflow.init": {
196
+ actions: [
197
+ assign({
198
+ initialized: true,
199
+ xyflow: ({ event }) => event.instance,
200
+ xystore: ({ event }) => event.store
201
+ })
202
+ ],
203
+ target: "waiting-data"
204
+ },
205
+ "close": {
206
+ target: "closed"
62
207
  }
63
208
  }
64
209
  },
65
- "opening": {
210
+ "waiting-data": {
66
211
  on: {
67
- "xyflow.init": {
68
- actions: assign({
69
- initialized: true,
70
- xyflow: ({ event }) => event.instance
71
- }),
212
+ "update.view": {
213
+ actions: [
214
+ assign(({ event }) => {
215
+ return {
216
+ layouted: event.layouted,
217
+ ...viewToNodesEdge(event.layouted)
218
+ };
219
+ }),
220
+ raise({ type: "fitDiagram", duration: 0 })
221
+ ],
72
222
  target: "active"
73
- },
74
- "close": {
75
- target: "closed"
76
223
  }
77
224
  }
78
225
  },
79
226
  "active": {
80
- entry: {
81
- type: "xyflow:fitDiagram",
82
- params: { duration: 0 }
227
+ initial: "idle",
228
+ tags: ["active"],
229
+ states: {
230
+ "idle": {},
231
+ "layouting": {
232
+ invoke: {
233
+ id: "layouter",
234
+ src: "layouter",
235
+ input: ({ context }) => {
236
+ return {
237
+ subjectId: context.subject,
238
+ navigateFromNode: context.navigateFromNode,
239
+ xyflow: nonNullable(context.xyflow),
240
+ xystore: nonNullable(context.xystore),
241
+ update: nonNullable(context.layouted)
242
+ };
243
+ },
244
+ onDone: {
245
+ target: "idle",
246
+ actions: [
247
+ assign({
248
+ xynodes: ({ event }) => event.output.xynodes,
249
+ xyedges: ({ event }) => event.output.xyedges,
250
+ navigateFromNode: null
251
+ }),
252
+ raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 80 }),
253
+ raise({ type: "xyflow.updateNodeInternals" }, { delay: 100 }),
254
+ raise({ type: "xyflow.updateNodeInternals" }, { delay: 250 }),
255
+ raise({ type: "xyflow.updateNodeInternals" }, { delay: 500 })
256
+ ]
257
+ }
258
+ },
259
+ on: {
260
+ "update.xydata": {
261
+ actions: assign({
262
+ xynodes: ({ event }) => event.xynodes,
263
+ xyedges: ({ event }) => event.xyedges
264
+ })
265
+ },
266
+ "xyflow.applyEdgeChages": {
267
+ // actions: log('layouting: ignore xyflow.applyEdgeChages'),
268
+ },
269
+ "xyflow.applyNodeChages": {
270
+ // actions: log('layouting: ignore xyflow.applyNodeChages'),
271
+ }
272
+ }
273
+ }
83
274
  },
84
275
  on: {
85
276
  "xyflow.nodeClick": {
@@ -87,8 +278,10 @@ const relationshipsBrowserActor = setup({
87
278
  actions: enqueueActions(({ event, enqueue }) => {
88
279
  if ("fqn" in event.node.data) {
89
280
  const fqn = event.node.data.fqn;
90
- enqueue.assign({
91
- subject: fqn
281
+ enqueue.raise({
282
+ type: "navigate.to",
283
+ subject: fqn,
284
+ fromNode: event.node.id
92
285
  });
93
286
  }
94
287
  })
@@ -97,7 +290,8 @@ const relationshipsBrowserActor = setup({
97
290
  guard: "enable: navigate.to",
98
291
  actions: [
99
292
  assign({
100
- subject: ({ event }) => event.subject
293
+ subject: ({ event }) => event.subject,
294
+ navigateFromNode: ({ event }) => event.fromNode ?? null
101
295
  })
102
296
  ]
103
297
  },
@@ -105,24 +299,35 @@ const relationshipsBrowserActor = setup({
105
299
  actions: "xyflow:fitDiagram"
106
300
  },
107
301
  "update.view": {
108
- actions: enqueueActions(({ context, event, enqueue }) => {
109
- const updated = viewToNodesEdge(event.layouted);
110
- enqueue.assign({
111
- layouted: event.layouted,
112
- xynodes: updateNodes(context.xynodes, updated.xynodes),
113
- xyedges: updateEdges(context.xyedges, updated.xyedges)
114
- });
115
- enqueue.raise({ type: "fitDiagram" }, { delay: 50 });
116
- })
302
+ actions: assign({
303
+ layouted: ({ event }) => event.layouted
304
+ }),
305
+ target: ".layouting"
306
+ },
307
+ "xyflow.updateNodeInternals": {
308
+ actions: "xyflow:updateNodeInternals"
309
+ },
310
+ "fitDiagram": {
311
+ actions: {
312
+ type: "xyflow:fitDiagram",
313
+ params: prop("event")
314
+ }
315
+ },
316
+ "xyflow.resized": {
317
+ actions: [
318
+ cancel("fitDiagram"),
319
+ raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 300 })
320
+ ]
117
321
  },
118
322
  "close": {
119
- guard: ({ context }) => context.closeable,
323
+ guard: "closeable",
120
324
  target: "closed"
121
325
  }
122
326
  },
123
327
  exit: assign({
124
328
  initialized: false,
125
- xyflow: null
329
+ xyflow: null,
330
+ xystore: null
126
331
  })
127
332
  },
128
333
  "closed": {
@@ -130,26 +335,18 @@ const relationshipsBrowserActor = setup({
130
335
  }
131
336
  },
132
337
  on: {
133
- "fitDiagram": {
134
- actions: {
135
- type: "xyflow:fitDiagram",
136
- params: prop("event")
137
- }
138
- },
139
- "xyflow.resized": {
140
- actions: [
141
- cancel("fitDiagram"),
142
- raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 200 })
143
- ]
144
- },
145
338
  "xyflow.applyNodeChages": {
146
339
  actions: assign({
147
- xynodes: ({ context, event }) => applyNodeChanges(event.changes, context.xynodes)
340
+ xynodes: ({ context, event }) => {
341
+ return applyNodeChanges(event.changes, context.xynodes);
342
+ }
148
343
  })
149
344
  },
150
345
  "xyflow.applyEdgeChages": {
151
346
  actions: assign({
152
- xyedges: ({ context, event }) => applyEdgeChanges(event.changes, context.xyedges)
347
+ xyedges: ({ context, event }) => {
348
+ return applyEdgeChanges(event.changes, context.xyedges);
349
+ }
153
350
  })
154
351
  }
155
352
  }
@@ -0,0 +1 @@
1
+ export declare const ViewPadding = 0.08;
@@ -0,0 +1,4 @@
1
+ const ViewPadding = 0.08;
2
+ export {
3
+ ViewPadding
4
+ };
@@ -26,7 +26,7 @@ const ElementActions = (props) => {
26
26
  icon: /* @__PURE__ */ jsx(IconTransform, {}),
27
27
  onClick: (e) => {
28
28
  e.stopPropagation();
29
- browser.navigateTo(fqn);
29
+ browser.navigateTo(fqn, props.id);
30
30
  }
31
31
  });
32
32
  }
@@ -1,15 +1,15 @@
1
1
  import { NamedExoticComponent, CSSProperties, ReactNode } from 'react';
2
2
  import { Position } from '@xyflow/system';
3
- import { Color, ViewId, RelationshipLineType } from '@likec4/core';
3
+ import { NonEmptyArray, RelationId, Color, ViewId, RelationshipLineType } from '@likec4/core';
4
4
  export declare const edgeTypes: {
5
5
  relationships: NamedExoticComponent<{
6
6
  source: string;
7
7
  target: string;
8
8
  style?: CSSProperties;
9
9
  id: string;
10
- selected?: boolean;
11
10
  selectable?: boolean;
12
11
  deletable?: boolean;
12
+ selected?: boolean;
13
13
  animated?: boolean;
14
14
  sourceX: number;
15
15
  sourceY: number;
@@ -34,6 +34,7 @@ export declare const edgeTypes: {
34
34
  active?: boolean;
35
35
  dimmed?: import("../../../base").Base.Dimmed;
36
36
  } & {
37
+ relations: NonEmptyArray<RelationId>;
37
38
  color: Color | undefined;
38
39
  label: string | null;
39
40
  navigateTo: ViewId | null;
@@ -11,18 +11,36 @@ const edgeTypes = {
11
11
  relationships: customEdge((props) => {
12
12
  const { enableNavigateTo } = useEnabledFeature("NavigateTo");
13
13
  const {
14
- sourceX,
15
- targetY,
16
- data: { navigateTo }
14
+ data: {
15
+ navigateTo,
16
+ relations
17
+ }
17
18
  } = props;
18
19
  const [svgPath, labelX, labelY] = getBezierPath(props);
19
20
  const diagram = useDiagram();
20
- return /* @__PURE__ */ jsxs(EdgeContainer, { ...props, children: [
21
- /* @__PURE__ */ jsx(EdgePath, { ...props, svgPath }),
21
+ const edgeProps = relations.length > 1 ? {
22
+ ...props,
23
+ data: {
24
+ ...props.data,
25
+ line: "solid",
26
+ color: "amber"
27
+ }
28
+ } : props;
29
+ return /* @__PURE__ */ jsxs(EdgeContainer, { ...edgeProps, children: [
30
+ /* @__PURE__ */ jsx(
31
+ EdgePath,
32
+ {
33
+ ...edgeProps,
34
+ svgPath,
35
+ ...relations.length > 1 && {
36
+ strokeWidth: 5
37
+ }
38
+ }
39
+ ),
22
40
  /* @__PURE__ */ jsx(
23
41
  EdgeLabel,
24
42
  {
25
- edgeProps: props,
43
+ edgeProps,
26
44
  labelPosition: {
27
45
  x: labelX,
28
46
  y: labelY,
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { Handle } from "@xyflow/react";
3
3
  import { Position } from "@xyflow/system";
4
+ import { m } from "framer-motion";
4
5
  import { ElementActions } from "./ElementActions.js";
5
6
  import { EmptyNode } from "./EmptyNode.js";
6
7
  import { customNode } from "../../../base/primitives/customNode.js";
@@ -11,7 +12,7 @@ import { CompoundNodeContainer } from "../../../base/primitives/compound/Compoun
11
12
  import { CompoundTitle } from "../../../base/primitives/compound/CompoundTitle.js";
12
13
  const nodeTypes = {
13
14
  element: customNode((props) => {
14
- return /* @__PURE__ */ jsxs(ElementNodeContainer, { nodeProps: props, children: [
15
+ return /* @__PURE__ */ jsxs(ElementNodeContainer, { component: m.div, layoutId: props.id, nodeProps: props, children: [
15
16
  /* @__PURE__ */ jsx(ElementShape, { ...props }),
16
17
  /* @__PURE__ */ jsx(ElementTitle, { ...props, iconSize: 40 }),
17
18
  /* @__PURE__ */ jsx(ElementActions, { ...props }),
@@ -19,7 +20,7 @@ const nodeTypes = {
19
20
  ] });
20
21
  }),
21
22
  compound: customNode((props) => {
22
- return /* @__PURE__ */ jsxs(CompoundNodeContainer, { nodeProps: props, children: [
23
+ return /* @__PURE__ */ jsxs(CompoundNodeContainer, { component: m.div, layoutId: props.id, nodeProps: props, children: [
23
24
  /* @__PURE__ */ jsx(CompoundTitle, { ...props }),
24
25
  /* @__PURE__ */ jsx(CompoundPorts, { ...props })
25
26
  ] });
@@ -1,5 +1,5 @@
1
- import { Fqn, Color, ViewId, RelationshipLineType } from '@likec4/core';
2
- import { RelationshipsBrowserActorRef, RelationshipsBrowserSnapshot, Input, Events } from './actor';
1
+ import { Fqn, NonEmptyArray, RelationId, Color, ViewId, RelationshipLineType } from '@likec4/core';
2
+ import { RelationshipsBrowserActorRef, RelationshipsBrowserSnapshot, Input, XYStoreApi, Events } from './actor';
3
3
  import { Context, ReactNode, CSSProperties } from 'react';
4
4
  import { GeneralHelpers, ViewportHelperFunctions } from '@xyflow/react';
5
5
  import { EdgeMarkerType, HandleType } from '@xyflow/system';
@@ -39,6 +39,7 @@ export declare function useRelationshipsBrowser(): {
39
39
  focusable?: boolean;
40
40
  type: "relationships";
41
41
  data: {
42
+ relations: NonEmptyArray<RelationId>;
42
43
  color: Color | undefined;
43
44
  label: string | null;
44
45
  navigateTo: ViewId | null;
@@ -50,13 +51,15 @@ export declare function useRelationshipsBrowser(): {
50
51
  }> & Omit< ViewportHelperFunctions, "initialized"> & {
51
52
  viewportInitialized: boolean;
52
53
  }) | null;
54
+ xystore: XYStoreApi | null;
53
55
  initialized: boolean;
54
56
  layouted: LayoutRelationshipsViewResult | null;
57
+ navigateFromNode: string | null;
55
58
  xynodes: import("./_types").RelationshipsBrowserTypes.Node[];
56
59
  xyedges: import("./_types").RelationshipsBrowserTypes.Edge[];
57
60
  }>;
58
61
  send: (event: Events) => void;
59
- navigateTo: (subject: Fqn) => void;
62
+ navigateTo: (subject: Fqn, fromNode?: string) => void;
60
63
  fitDiagram: () => void;
61
64
  close: () => void;
62
65
  };
@@ -1,6 +1,7 @@
1
1
  import { nonNullable } from "@likec4/core";
2
2
  import { useCallbackRef } from "@mantine/hooks";
3
3
  import { useSelector } from "@xstate/react";
4
+ import "@xyflow/react";
4
5
  import { shallowEqual } from "fast-equals";
5
6
  import { createContext, useTransition, useMemo, useContext } from "react";
6
7
  const RelationshipsBrowserActorContext = createContext(null);
@@ -18,11 +19,12 @@ function useRelationshipsBrowser() {
18
19
  return useMemo(() => ({
19
20
  getState: () => actor.getSnapshot().context,
20
21
  send: actor.send,
21
- navigateTo: (subject) => {
22
+ navigateTo: (subject, fromNode) => {
22
23
  startTransition(() => {
23
24
  actor.send({
24
25
  type: "navigate.to",
25
- subject
26
+ subject,
27
+ fromNode
26
28
  });
27
29
  });
28
30
  },
@@ -122,18 +122,23 @@ function viewToNodesEdge(view) {
122
122
  console.error("edge should have at least 2 points", edge);
123
123
  continue;
124
124
  }
125
+ if (!hasAtLeast(edge.relations, 1)) {
126
+ console.error("edge should have at least 1 relation", edge);
127
+ continue;
128
+ }
125
129
  xyedges.push({
126
130
  id,
127
131
  type: "relationships",
128
132
  source: ns + source,
129
133
  target: ns + target,
130
- sourceHandle: ns + target,
131
- targetHandle: ns + source,
134
+ sourceHandle: edge.sourceHandle,
135
+ targetHandle: edge.targetHandle,
132
136
  zIndex: ZIndexes.Edge,
133
137
  // selectable: selectable,
134
138
  // hidden: !visiblePredicate(edge),
135
139
  deletable: false,
136
140
  data: {
141
+ relations: edge.relations,
137
142
  color: edge.color ?? "gray",
138
143
  label: edge.label,
139
144
  navigateTo: edge.navigateTo ?? null,