@likec4/diagram 1.27.1 → 1.27.3

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 (123) hide show
  1. package/bundle/index.js +2152 -6757
  2. package/dist/LikeC4Diagram.js +1 -1
  3. package/dist/LikeC4Diagram.props.d.ts +5 -3
  4. package/dist/StaticLikeC4Diagram.js +1 -1
  5. package/dist/_virtual/set.js +5 -0
  6. package/dist/_virtual/set2.js +3 -0
  7. package/dist/base/BaseXYFlow.d.ts +2 -1
  8. package/dist/base/primitives/compound/CompoundNodeContainer.d.ts +1 -1
  9. package/dist/base/primitives/compound/CompoundNodeContainer.js +0 -1
  10. package/dist/base/primitives/edge/EdgeLabelContainer.js +3 -2
  11. package/dist/base/primitives/element/ElementActionButtons.js +8 -11
  12. package/dist/base/primitives/element/ElementNodeContainer.css.js +4 -4
  13. package/dist/base/updateEdges.js +1 -9
  14. package/dist/base/updateNodes.js +1 -12
  15. package/dist/bundle/LikeC4View.js +2 -2
  16. package/dist/bundle/LikeC4View.props.d.ts +7 -7
  17. package/dist/hooks/useCurrentViewId.js +8 -2
  18. package/dist/hooks/useDiagram.d.ts +1522 -685
  19. package/dist/hooks/useDiagram.js +21 -24
  20. package/dist/likec4diagram/DiagramXYFlow.js +6 -6
  21. package/dist/likec4diagram/ui/notation/NotationPanel.css.js +3 -3
  22. package/dist/likec4diagram/ui/notation/NotationPanel.js +10 -4
  23. package/dist/node_modules/.pnpm/mnemonist@0.40.3_patch_hash_5c12abb43fcff6fd81e53021b89075d53249640d165a2556cc1e58d71201a13a/node_modules/mnemonist/set.js +369 -0
  24. package/dist/overlays/Overlays.js +3 -1
  25. package/dist/overlays/element-details/actor.d.ts +2769 -173
  26. package/dist/overlays/overlay/Overlay.css.d.ts +26 -2
  27. package/dist/overlays/overlay/Overlay.css.js +66 -18
  28. package/dist/overlays/overlay/Overlay.d.ts +7 -6
  29. package/dist/overlays/overlay/Overlay.js +12 -10
  30. package/dist/overlays/overlaysActor.js +2 -2
  31. package/dist/overlays/relationship-details/RelationshipDetails.js +113 -70
  32. package/dist/overlays/relationship-details/SelectEdge.d.ts +1 -1
  33. package/dist/overlays/relationship-details/SelectEdge.js +15 -9
  34. package/dist/overlays/relationship-details/_types.d.ts +4 -1
  35. package/dist/overlays/relationship-details/actor.d.ts +425 -128
  36. package/dist/overlays/relationship-details/actor.js +157 -53
  37. package/dist/overlays/relationship-details/compute.d.ts +12 -0
  38. package/dist/overlays/relationship-details/compute.js +139 -0
  39. package/dist/overlays/relationship-details/custom/ElementActions.js +27 -5
  40. package/dist/overlays/relationship-details/custom/edgeTypes.js +1 -1
  41. package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +2 -1
  42. package/dist/overlays/relationship-details/hooks.d.ts +336 -6
  43. package/dist/overlays/relationship-details/hooks.js +11 -12
  44. package/dist/overlays/relationship-details/layout-to-xyflow.d.ts +12 -0
  45. package/dist/overlays/relationship-details/layout-to-xyflow.js +145 -0
  46. package/dist/overlays/relationship-details/layout.d.ts +22 -0
  47. package/dist/overlays/relationship-details/layout.js +314 -0
  48. package/dist/overlays/relationships-browser/RelationshipsBrowser.d.ts +3 -1
  49. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +2 -2
  50. package/dist/overlays/relationships-browser/_types.d.ts +2 -0
  51. package/dist/overlays/relationships-browser/actor.d.ts +130 -14
  52. package/dist/overlays/relationships-browser/actor.js +30 -16
  53. package/dist/overlays/relationships-browser/custom/ElementActions.js +5 -3
  54. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -1
  55. package/dist/overlays/relationships-browser/custom/edgeTypes.js +1 -1
  56. package/dist/overlays/relationships-browser/hooks.d.ts +3 -1
  57. package/dist/overlays/relationships-browser/hooks.js +6 -9
  58. package/dist/overlays/relationships-browser/{-useRelationshipsView.d.ts → layout.d.ts} +3 -1
  59. package/dist/overlays/relationships-browser/{-useRelationshipsView.js → layout.js} +21 -8
  60. package/dist/overlays/relationships-browser/useViewToNodesEdges.d.ts +1 -1
  61. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +3 -1
  62. package/dist/packages/core/src/compute-view/relationships-view/compute.js +1 -1
  63. package/dist/packages/core/src/model/connection/model/ConnectionModel.js +121 -0
  64. package/dist/packages/core/src/model/connection/model/find.js +39 -0
  65. package/dist/packages/core/src/model/connection/ops.js +7 -0
  66. package/dist/packages/core/src/utils/const.js +3 -0
  67. package/dist/packages/core/src/utils/fqn.js +9 -1
  68. package/dist/packages/core/src/utils/iterable/find.js +21 -0
  69. package/dist/packages/core/src/utils/iterable/some.js +21 -0
  70. package/dist/packages/core/src/utils/set.js +48 -0
  71. package/dist/packages/core/src/utils/string-hash.js +13 -0
  72. package/dist/state/diagram-machine.d.ts +1552 -703
  73. package/dist/state/diagram-machine.js +39 -3
  74. package/dist/state/syncManualLayoutActor.d.ts +1 -1
  75. package/dist/styles.css.js +1 -1
  76. package/dist/utils/index.d.ts +0 -1
  77. package/package.json +11 -11
  78. package/src/LikeC4Diagram.props.ts +7 -3
  79. package/src/LikeC4Diagram.tsx +1 -1
  80. package/src/StaticLikeC4Diagram.tsx +1 -1
  81. package/src/base/BaseXYFlow.tsx +2 -1
  82. package/src/base/primitives/compound/CompoundNodeContainer.tsx +0 -2
  83. package/src/base/primitives/edge/EdgeLabelContainer.tsx +7 -5
  84. package/src/base/primitives/element/ElementActionButtons.tsx +10 -11
  85. package/src/base/primitives/element/ElementNodeContainer.css.ts +4 -4
  86. package/src/base/updateEdges.ts +8 -8
  87. package/src/base/updateNodes.ts +11 -11
  88. package/src/bundle/LikeC4View.props.ts +7 -7
  89. package/src/bundle/LikeC4View.tsx +2 -2
  90. package/src/hooks/useCurrentViewId.ts +4 -2
  91. package/src/hooks/useDiagram.ts +22 -24
  92. package/src/likec4diagram/DiagramXYFlow.tsx +6 -6
  93. package/src/likec4diagram/ui/notation/NotationPanel.css.ts +2 -2
  94. package/src/likec4diagram/ui/notation/NotationPanel.tsx +8 -6
  95. package/src/overlays/Overlays.tsx +4 -2
  96. package/src/overlays/overlay/Overlay.css.ts +67 -17
  97. package/src/overlays/overlay/Overlay.tsx +27 -24
  98. package/src/overlays/overlaysActor.ts +1 -1
  99. package/src/overlays/relationship-details/RelationshipDetails.tsx +131 -68
  100. package/src/overlays/relationship-details/SelectEdge.tsx +19 -12
  101. package/src/overlays/relationship-details/_types.ts +3 -1
  102. package/src/overlays/relationship-details/actor.ts +212 -68
  103. package/src/overlays/relationship-details/compute.ts +186 -0
  104. package/src/overlays/relationship-details/custom/ElementActions.tsx +27 -18
  105. package/src/overlays/relationship-details/custom/edgeTypes.tsx +1 -1
  106. package/src/overlays/relationship-details/hooks.ts +12 -13
  107. package/src/overlays/relationship-details/layout-to-xyflow.ts +172 -0
  108. package/src/overlays/relationship-details/layout.ts +470 -0
  109. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +2 -2
  110. package/src/overlays/relationships-browser/_types.ts +2 -0
  111. package/src/overlays/relationships-browser/actor.ts +39 -14
  112. package/src/overlays/relationships-browser/custom/ElementActions.tsx +5 -3
  113. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +1 -1
  114. package/src/overlays/relationships-browser/hooks.ts +7 -10
  115. package/src/overlays/relationships-browser/{-useRelationshipsView.ts → layout.ts} +26 -33
  116. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +3 -1
  117. package/src/state/diagram-machine.ts +51 -6
  118. package/src/utils/index.ts +0 -1
  119. package/dist/overlays/relationship-details/useLayoutedDetails.d.ts +0 -19
  120. package/dist/overlays/relationship-details/useLayoutedDetails.js +0 -267
  121. package/dist/utils/types.d.ts +0 -8
  122. package/src/overlays/relationship-details/useLayoutedDetails.ts +0 -376
  123. package/src/utils/types.ts +0 -6
@@ -6,6 +6,7 @@ import { setup, assign, fromPromise, assertEvent, enqueueActions, cancel, raise
6
6
  import { MinZoom, ZIndexes } from '../../base/const.js';
7
7
  import { updateEdges } from '../../base/updateEdges.js';
8
8
  import { updateNodes } from '../../base/updateNodes.js';
9
+ import { typedSystem } from '../../state/utils.js';
9
10
  import { ViewPadding } from './const.js';
10
11
  import { viewToNodesEdge } from './useViewToNodesEdges.js';
11
12
  import { Base } from '../../base/types.js';
@@ -129,6 +130,7 @@ const relationshipsBrowserLogic = setup({
129
130
  layouter
130
131
  },
131
132
  guards: {
133
+ hasViewId: ({ context }) => context.viewId !== null,
132
134
  isReady: ({ context }) => context.xyflow !== null && context.xystore !== null && context.layouted !== null,
133
135
  anotherSubject: ({ context, event }) => {
134
136
  if (event.type === "update.view") {
@@ -271,10 +273,24 @@ const relationshipsBrowserLogic = setup({
271
273
  }
272
274
  })
273
275
  },
276
+ "xyflow.edgeClick": {
277
+ guard: "hasViewId",
278
+ actions: enqueueActions(({ event, context, system, enqueue }) => {
279
+ if (event.edge.selected || event.edge.data.relations.length > 1) {
280
+ enqueue.sendTo(typedSystem(system).overlaysActorRef, {
281
+ type: "open.relationshipDetails",
282
+ viewId: context.viewId,
283
+ source: event.edge.data.sourceFqn,
284
+ target: event.edge.data.targetFqn
285
+ });
286
+ }
287
+ })
288
+ },
274
289
  "navigate.to": {
275
290
  actions: [
276
291
  assign({
277
292
  subject: ({ event }) => event.subject,
293
+ viewId: ({ event, context }) => event.viewId ?? context.viewId ?? null,
278
294
  navigateFromNode: ({ event }) => event.fromNode ?? null
279
295
  })
280
296
  ]
@@ -321,7 +337,7 @@ const relationshipsBrowserLogic = setup({
321
337
  actions: [
322
338
  assign({
323
339
  xyedges: ({ context, event }) => {
324
- const hasDimmed = context.xyedges.some((edge) => edge.data.dimmed === true);
340
+ const hasDimmed = context.xyedges.some((edge) => edge.data.dimmed !== false || edge.selected);
325
341
  return context.xyedges.map((edge) => {
326
342
  if (edge.id === event.edge.id) {
327
343
  return Base.setData(edge, {
@@ -329,24 +345,26 @@ const relationshipsBrowserLogic = setup({
329
345
  dimmed: false
330
346
  });
331
347
  }
332
- return hasDimmed && !edge.data.dimmed && !edge.selected ? Base.setDimmed(edge, "immediate") : edge;
348
+ return hasDimmed && !edge.selected ? Base.setDimmed(edge, "immediate") : edge;
333
349
  });
334
350
  }
335
351
  }),
336
352
  cancel("undim.edges"),
337
353
  cancel("dim.nonhovered.edges"),
338
- raise({ type: "dim.nonhovered.edges" }, { id: "dim.nonhovered.edges", delay: 100 })
354
+ raise({ type: "dim.nonhovered.edges" }, { id: "dim.nonhovered.edges", delay: 200 })
339
355
  ]
340
356
  },
341
357
  "xyflow.edgeMouseLeave": {
342
358
  actions: [
343
359
  assign({
344
- xyedges: ({ context, event }) => context.xyedges.map((edge) => {
345
- if (edge.id === event.edge.id) {
346
- return Base.setHovered(edge, false);
347
- }
348
- return edge;
349
- })
360
+ xyedges: ({ context, event }) => {
361
+ return context.xyedges.map((edge) => {
362
+ if (edge.id === event.edge.id) {
363
+ return Base.setHovered(edge, false);
364
+ }
365
+ return edge;
366
+ });
367
+ }
350
368
  }),
351
369
  cancel("dim.nonhovered.edges"),
352
370
  raise({ type: "undim.edges" }, { id: "undim.edges", delay: 400 })
@@ -354,18 +372,14 @@ const relationshipsBrowserLogic = setup({
354
372
  },
355
373
  "dim.nonhovered.edges": {
356
374
  actions: assign({
357
- xyedges: ({ context }) => context.xyedges.map((edge) => Base.setDimmed(edge, edge.data.hovered !== true))
375
+ xyedges: ({ context }) => context.xyedges.map(
376
+ (edge) => edge.data.hovered ? edge : Base.setDimmed(edge, edge.data.dimmed === "immediate" ? "immediate" : true)
377
+ )
358
378
  })
359
379
  },
360
380
  "undim.edges": {
361
381
  actions: assign({
362
382
  xyedges: ({ context }) => {
363
- const hasSelected = context.xyedges.some((edge) => edge.selected === true);
364
- if (hasSelected) {
365
- return context.xyedges.map(
366
- (edge) => Base.setDimmed(edge, edge.selected !== true ? edge.data.dimmed || "immediate" : false)
367
- );
368
- }
369
383
  return context.xyedges.map(Base.setDimmed(false));
370
384
  }
371
385
  })
@@ -1,18 +1,20 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { IconZoomScan, IconTransform, IconFileSymlink } from '@tabler/icons-react';
3
- import { useEnabledFeature } from '../../../context/DiagramFeatures.js';
3
+ import { useEnabledFeatures } from '../../../context/DiagramFeatures.js';
4
+ import { useCurrentViewId } from '../../../hooks/useCurrentViewId.js';
4
5
  import { useDiagram } from '../../../hooks/useDiagram.js';
5
6
  import { useRelationshipsBrowser, useRelationshipsBrowserState } from '../hooks.js';
6
7
  import { ElementActionButtons } from '../../../base/primitives/element/ElementActionButtons.js';
7
8
 
8
9
  const ElementActions = (props) => {
9
- const { enableNavigateTo, enableVscode } = useEnabledFeature("NavigateTo", "Vscode");
10
+ const { enableNavigateTo, enableVscode } = useEnabledFeatures();
10
11
  const diagram = useDiagram();
12
+ const currentViewId = useCurrentViewId();
11
13
  const browser = useRelationshipsBrowser();
12
14
  const subject = useRelationshipsBrowserState((s) => s.context.subject);
13
15
  const buttons = [];
14
16
  const { navigateTo, fqn } = props.data;
15
- if (navigateTo && enableNavigateTo) {
17
+ if (navigateTo && enableNavigateTo && currentViewId !== navigateTo) {
16
18
  buttons.push({
17
19
  key: "navigate",
18
20
  icon: /* @__PURE__ */ jsx(IconZoomScan, {}),
@@ -1,6 +1,6 @@
1
1
  import { NamedExoticComponent, CSSProperties, ReactNode } from 'react';
2
2
  import { Position } from '@xyflow/system';
3
- import { NonEmptyArray, RelationId, Color, ViewId, RelationshipLineType } from '@likec4/core';
3
+ import { Fqn, NonEmptyArray, RelationId, Color, ViewId, RelationshipLineType } from '@likec4/core';
4
4
  export declare const RelationshipEdge: NamedExoticComponent<{
5
5
  id: string;
6
6
  selected?: boolean;
@@ -33,6 +33,8 @@ export declare const RelationshipEdge: NamedExoticComponent<{
33
33
  active?: boolean;
34
34
  dimmed?: import("../../../base").Base.Dimmed;
35
35
  } & {
36
+ sourceFqn: Fqn;
37
+ targetFqn: Fqn;
36
38
  relations: NonEmptyArray<RelationId>;
37
39
  color: Color | undefined;
38
40
  label: string | null;
@@ -61,7 +61,7 @@ const RelationshipEdge = customEdge((props) => {
61
61
  c: "black",
62
62
  label: "This relationship is not included in the current view",
63
63
  portalProps: {
64
- target: `#${browser.rootElementId.replaceAll(":", "\\:")}`
64
+ target: `#${browser.rootElementId}`
65
65
  },
66
66
  openDelay: 800,
67
67
  children: label
@@ -1,6 +1,6 @@
1
1
  import { Fqn, ViewId, NonEmptyArray, RelationId, Color, RelationshipLineType } from '@likec4/core';
2
- import { LayoutRelationshipsViewResult } from './-useRelationshipsView';
3
2
  import { RelationshipsBrowserActorRef, RelationshipsBrowserSnapshot, XYStoreApi, Events } from './actor';
3
+ import { LayoutRelationshipsViewResult } from './layout';
4
4
  import { Context, ReactNode, CSSProperties } from 'react';
5
5
  import { GeneralHelpers, ViewportHelperFunctions } from '@xyflow/react';
6
6
  import { EdgeMarkerType, HandleType } from '@xyflow/system';
@@ -45,6 +45,8 @@ export declare function useRelationshipsBrowser(): {
45
45
  focusable?: boolean;
46
46
  type: "relationship";
47
47
  data: {
48
+ sourceFqn: Fqn;
49
+ targetFqn: Fqn;
48
50
  relations: NonEmptyArray<RelationId>;
49
51
  color: Color | undefined;
50
52
  label: string | null;
@@ -2,7 +2,7 @@ import { nonNullable } from '@likec4/core';
2
2
  import { useCallbackRef } from '@mantine/hooks';
3
3
  import { useSelector } from '@xstate/react';
4
4
  import { shallowEqual } from 'fast-equals';
5
- import { createContext, useTransition, useMemo, useContext } from 'react';
5
+ import { createContext, useMemo, useContext } from 'react';
6
6
 
7
7
  const RelationshipsBrowserActorContext = createContext(null);
8
8
  function useRelationshipsBrowserActor() {
@@ -14,11 +14,10 @@ function useRelationshipsBrowserState(selector, compare = shallowEqual) {
14
14
  }
15
15
  function useRelationshipsBrowser() {
16
16
  const actor = useRelationshipsBrowserActor();
17
- const [, startTransition] = useTransition();
18
17
  return useMemo(() => ({
19
18
  actor,
20
19
  get rootElementId() {
21
- return `relationships-browser-${actor.sessionId}`;
20
+ return `relationships-browser-${actor.sessionId.replaceAll(":", "_")}`;
22
21
  },
23
22
  getState: () => actor.getSnapshot().context,
24
23
  send: actor.send,
@@ -35,12 +34,10 @@ function useRelationshipsBrowser() {
35
34
  });
36
35
  },
37
36
  navigateTo: (subject, fromNode) => {
38
- startTransition(() => {
39
- actor.send({
40
- type: "navigate.to",
41
- subject,
42
- fromNode
43
- });
37
+ actor.send({
38
+ type: "navigate.to",
39
+ subject,
40
+ fromNode
44
41
  });
45
42
  },
46
43
  fitDiagram: () => {
@@ -11,12 +11,14 @@ export type LayoutRelationshipsViewResult = {
11
11
  };
12
12
  export declare namespace LayoutRelationshipsViewResult {
13
13
  const Empty: ElementKind;
14
- type Node = DiagramNode & {
14
+ type Node = Omit<DiagramNode, 'deploymentRef' | 'inEdges' | 'outEdges'> & {
15
15
  column: RelationshipsBrowserTypes.Column;
16
16
  ports: RelationshipsBrowserTypes.Ports;
17
17
  existsInCurrentView: boolean;
18
18
  };
19
19
  type Edge = DiagramEdge & {
20
+ sourceFqn: Fqn;
21
+ targetFqn: Fqn;
20
22
  sourceHandle: string;
21
23
  targetHandle: string;
22
24
  existsInCurrentView: boolean;
@@ -13,7 +13,7 @@ const Sizes = {
13
13
  edgesep: 25
14
14
  },
15
15
  edgeLabel: {
16
- width: 120,
16
+ width: 140,
17
17
  height: 10,
18
18
  minlen: 1,
19
19
  weight: 1
@@ -128,6 +128,8 @@ function layoutRelationshipsView(data, scope) {
128
128
  toArray(data.incoming),
129
129
  map((r) => ({
130
130
  id: r.source.id,
131
+ sourceFqn: r.source.id,
132
+ targetFqn: r.target.id,
131
133
  source: incomers.byId(r.source.id).graph,
132
134
  target: subjects.byId(r.target.id).graph,
133
135
  relation: r
@@ -137,6 +139,8 @@ function layoutRelationshipsView(data, scope) {
137
139
  toArray(data.outgoing),
138
140
  map((r) => ({
139
141
  id: r.target.id,
142
+ sourceFqn: r.source.id,
143
+ targetFqn: r.target.id,
140
144
  source: subjects.byId(r.source.id).graph,
141
145
  target: outgoers.byId(r.target.id).graph,
142
146
  relation: r
@@ -160,6 +164,8 @@ function layoutRelationshipsView(data, scope) {
160
164
  }, name);
161
165
  edges.push({
162
166
  name,
167
+ sourceFqn: grouped[0].sourceFqn,
168
+ targetFqn: grouped[0].targetFqn,
163
169
  source: source.id,
164
170
  sourceHandle: source.id + "_out" + (g.node(source.id).outPorts.length - 1),
165
171
  target: target.id,
@@ -229,7 +235,7 @@ function layoutRelationshipsView(data, scope) {
229
235
  for (const edge of g.edges()) {
230
236
  g.setEdge(edge, {
231
237
  ...Sizes.edgeLabel,
232
- width: edgeCount > 30 ? 800 : 400
238
+ width: edgeCount > 25 ? 800 : 400
233
239
  });
234
240
  }
235
241
  }
@@ -340,8 +346,6 @@ function layoutRelationshipsView(data, scope) {
340
346
  width,
341
347
  height
342
348
  },
343
- inEdges: [],
344
- outEdges: [],
345
349
  children: [],
346
350
  width,
347
351
  height,
@@ -357,7 +361,7 @@ function layoutRelationshipsView(data, scope) {
357
361
  const children = (g.children(id) ?? []).filter((c) => !c.endsWith(PortSuffix));
358
362
  minX = Math.min(minX, position.x);
359
363
  minY = Math.min(minY, position.y);
360
- const navigateTo = element.defaultView?.id ?? null;
364
+ const navigateTo = scope ? ifind(element.scopedViews(), (v) => v.id !== scope.id)?.id ?? null : null;
361
365
  const inheritFromNode = scope?.findNodeWithElement(element.id);
362
366
  const scopedAncestor = scope && !inheritFromNode ? ifind(element.ancestors(), (a) => !!scope.findNodeWithElement(a.id))?.id : null;
363
367
  const inheritFromNodeOrAncestor = inheritFromNode ?? (scopedAncestor && scope?.findNodeWithElement(scopedAncestor));
@@ -387,8 +391,6 @@ function layoutRelationshipsView(data, scope) {
387
391
  ...element.$element.style
388
392
  },
389
393
  navigateTo,
390
- inEdges: [],
391
- outEdges: [],
392
394
  ...children.length > 0 && { depth: nodeDepth(id) },
393
395
  children,
394
396
  width,
@@ -416,15 +418,26 @@ function layoutRelationshipsView(data, scope) {
416
418
  if (!ename) {
417
419
  return acc;
418
420
  }
419
- const { name, source, target, relations, sourceHandle, targetHandle } = find(edges, (e2) => e2.name === ename);
421
+ const {
422
+ name,
423
+ source,
424
+ sourceFqn,
425
+ target,
426
+ targetFqn,
427
+ relations,
428
+ sourceHandle,
429
+ targetHandle
430
+ } = find(edges, (e2) => e2.name === ename);
420
431
  const onlyRelation = only(relations);
421
432
  const label = onlyRelation?.title ?? "untitled";
422
433
  const isMultiple = relations.length > 1;
423
434
  const navigateTo = only(unique(relations.flatMap((r) => r.navigateTo?.id ? r.navigateTo.id : [])));
424
435
  acc.push({
425
436
  id: name,
437
+ sourceFqn,
426
438
  source,
427
439
  sourceHandle,
440
+ targetFqn,
428
441
  target,
429
442
  targetHandle,
430
443
  label: isMultiple ? `${relations.length} relationships` : label,
@@ -1,5 +1,5 @@
1
- import { LayoutRelationshipsViewResult } from './-useRelationshipsView';
2
1
  import { RelationshipsBrowserTypes } from './_types';
2
+ import { LayoutRelationshipsViewResult } from './layout';
3
3
  export declare function viewToNodesEdge(view: Pick<LayoutRelationshipsViewResult, 'nodes' | 'edges'>): {
4
4
  xynodes: RelationshipsBrowserTypes.Node[];
5
5
  xyedges: RelationshipsBrowserTypes.Edge[];
@@ -2,7 +2,7 @@ import { Queue, ElementKind, nonNullable, DiagramNode, invariant } from '@likec4
2
2
  import '@react-hookz/web';
3
3
  import { hasAtLeast } from 'remeda';
4
4
  import { ZIndexes } from '../../base/const.js';
5
- import { LayoutRelationshipsViewResult } from './-useRelationshipsView.js';
5
+ import { LayoutRelationshipsViewResult } from './layout.js';
6
6
 
7
7
  function viewToNodesEdge(view) {
8
8
  const xynodes = [], xyedges = [], nodeLookup = /* @__PURE__ */ new Map();
@@ -142,6 +142,8 @@ function viewToNodesEdge(view) {
142
142
  // hidden: !visiblePredicate(edge),
143
143
  deletable: false,
144
144
  data: {
145
+ sourceFqn: edge.sourceFqn,
146
+ targetFqn: edge.targetFqn,
145
147
  relations: edge.relations,
146
148
  color: edge.color ?? "gray",
147
149
  label: edge.label,
@@ -1,6 +1,6 @@
1
1
  import { pipe } from 'remeda';
2
2
  import { invariant } from '../../errors/index.js';
3
- import { sortParentsFirst, isDescendantOf } from '../../utils/fqn.js';
3
+ import { isDescendantOf, sortParentsFirst } from '../../utils/fqn.js';
4
4
  import { treeFromElements } from './utils.js';
5
5
  import { toSet, toArray } from '../../utils/iterable/to.js';
6
6
  import { ifilter } from '../../utils/iterable/filter.js';
@@ -0,0 +1,121 @@
1
+ import { isNot } from 'remeda';
2
+ import { invariant } from '../../../errors/index.js';
3
+ import { customInspectSymbol } from '../../../utils/const.js';
4
+ import { union, difference, intersection, equals } from '../../../utils/set.js';
5
+ import { stringHash } from '../../../utils/string-hash.js';
6
+ import { hasSameSourceTarget } from '../ops.js';
7
+ import { findConnection } from './find.js';
8
+ import { isome } from '../../../utils/iterable/some.js';
9
+ import { ifilter } from '../../../utils/iterable/filter.js';
10
+
11
+ class ConnectionModel {
12
+ constructor(source, target, relations = /* @__PURE__ */ new Set()) {
13
+ this.source = source;
14
+ this.target = target;
15
+ this.relations = relations;
16
+ this.id = stringHash(`model:${source.id}:${target.id}`);
17
+ }
18
+ id;
19
+ _boundary;
20
+ /**
21
+ * Common ancestor of the source and target elements.
22
+ * Represents the boundary of the connection.
23
+ */
24
+ get boundary() {
25
+ return this._boundary ??= this.source.commonAncestor(this.target);
26
+ }
27
+ /**
28
+ * Human readable expression of the connection
29
+ * Mostly used for testing and debugging
30
+ */
31
+ get expression() {
32
+ return `${this.source.id} -> ${this.target.id}`;
33
+ }
34
+ /**
35
+ * Returns true if only includes relations between the source and target elements.
36
+ */
37
+ get isDirect() {
38
+ return this.nonEmpty() && !this.isImplicit;
39
+ }
40
+ /**
41
+ * Returns true if includes relations between nested elements of the source and target elements.
42
+ */
43
+ get isImplicit() {
44
+ return this.nonEmpty() && isome(this.relations, isNot(hasSameSourceTarget(this)));
45
+ }
46
+ get directRelations() {
47
+ return new Set(ifilter(this.relations, hasSameSourceTarget(this)));
48
+ }
49
+ nonEmpty() {
50
+ return this.relations.size > 0;
51
+ }
52
+ mergeWith(other) {
53
+ invariant(this.source.id === other.source.id, "Cannot merge connections with different sources");
54
+ invariant(this.target.id === other.target.id, "Cannot merge connections with different targets");
55
+ return new ConnectionModel(
56
+ this.source,
57
+ this.target,
58
+ union(this.relations, other.relations)
59
+ );
60
+ }
61
+ difference(other) {
62
+ return new ConnectionModel(
63
+ this.source,
64
+ this.target,
65
+ difference(this.relations, other.relations)
66
+ );
67
+ }
68
+ intersect(other) {
69
+ invariant(other instanceof ConnectionModel, "Cannot intersect connection with different type");
70
+ return new ConnectionModel(
71
+ this.source,
72
+ this.target,
73
+ intersection(this.relations, other.relations)
74
+ );
75
+ }
76
+ equals(other) {
77
+ invariant(other instanceof ConnectionModel, "Cannot merge connection with different type");
78
+ return this.id === other.id && this.source.id === other.source.id && this.target.id === other.target.id && equals(this.relations, other.relations);
79
+ }
80
+ /**
81
+ * Returns a new instance with the updated relations.
82
+ *
83
+ * @param relations - A readonly set of `RelationshipModel` instances representing the new relations.
84
+ * @returns A new `ConnectionModel` instance with the updated relations.
85
+ */
86
+ update(relations) {
87
+ return new ConnectionModel(
88
+ this.source,
89
+ this.target,
90
+ relations
91
+ );
92
+ }
93
+ [customInspectSymbol](depth, inspectOptions, inspect) {
94
+ const asString = this.toString();
95
+ Object.defineProperty(asString, "constructor", {
96
+ value: ConnectionModel,
97
+ enumerable: false
98
+ });
99
+ return asString;
100
+ }
101
+ toString() {
102
+ return [
103
+ this.expression,
104
+ this.relations.size ? " relations:" : " relations: [ ]",
105
+ ...[...this.relations].map((c) => " " + c.expression)
106
+ ].join("\n");
107
+ }
108
+ /**
109
+ * Creates a new connection with reversed direction (target becomes source and vice versa)
110
+ * @param search - When true, attempts to find an existing connection between the reversed nodes
111
+ */
112
+ reversed(search = false) {
113
+ if (!search) {
114
+ return new ConnectionModel(this.target, this.source);
115
+ }
116
+ const [found] = findConnection(this.target, this.source, "directed");
117
+ return found ?? new ConnectionModel(this.target, this.source, /* @__PURE__ */ new Set());
118
+ }
119
+ }
120
+
121
+ export { ConnectionModel };
@@ -0,0 +1,39 @@
1
+ import { isSameHierarchy } from '../../../utils/fqn.js';
2
+ import { intersection } from '../../../utils/set.js';
3
+ import { ConnectionModel } from './ConnectionModel.js';
4
+
5
+ function findConnection(source, target, direction = "directed") {
6
+ if (source === target) {
7
+ return [];
8
+ }
9
+ if (isSameHierarchy(source, target)) {
10
+ return [];
11
+ }
12
+ const directedIntersection = intersection(source.allOutgoing, target.allIncoming);
13
+ const directed = directedIntersection.size > 0 ? new ConnectionModel(
14
+ source,
15
+ target,
16
+ directedIntersection
17
+ ) : null;
18
+ if (direction === "directed") {
19
+ return directed ? [directed] : [];
20
+ }
21
+ const reverseIntersection = intersection(source.allIncoming, target.allOutgoing);
22
+ const reverse = reverseIntersection.size > 0 ? new ConnectionModel(
23
+ target,
24
+ source,
25
+ reverseIntersection
26
+ ) : null;
27
+ if (directed && reverse) {
28
+ return [directed, reverse];
29
+ }
30
+ if (directed) {
31
+ return [directed];
32
+ }
33
+ if (reverse) {
34
+ return [reverse];
35
+ }
36
+ return [];
37
+ }
38
+
39
+ export { findConnection };
@@ -0,0 +1,7 @@
1
+ import 'remeda';
2
+
3
+ function hasSameSourceTarget(a, b) {
4
+ return (b2) => a.source === b2.source && a.target === b2.target;
5
+ }
6
+
7
+ export { hasSameSourceTarget };
@@ -0,0 +1,3 @@
1
+ const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
2
+
3
+ export { customInspectSymbol };
@@ -12,6 +12,14 @@ function isAncestor(arg1, arg2) {
12
12
  return arg1Id.startsWith(ancestorId + ".");
13
13
  };
14
14
  }
15
+ function isSameHierarchy(one, another) {
16
+ if (!another) {
17
+ return (b) => isSameHierarchy(one, b);
18
+ }
19
+ const first = asString(one);
20
+ const second = asString(another);
21
+ return first === second || second.startsWith(first + ".") || first.startsWith(second + ".");
22
+ }
15
23
  function isDescendantOf(descedant, ancestor) {
16
24
  {
17
25
  return (d) => isAncestor(descedant, d);
@@ -40,4 +48,4 @@ function sortParentsFirst(array) {
40
48
  return result;
41
49
  }
42
50
 
43
- export { isAncestor, isDescendantOf, sortParentsFirst };
51
+ export { isAncestor, isDescendantOf, isSameHierarchy, sortParentsFirst };
@@ -0,0 +1,21 @@
1
+ import { isFunction } from 'remeda';
2
+ import { invariant } from '../../errors/index.js';
3
+
4
+ function ifind(arg1, arg2) {
5
+ const pred = arg2 ?? arg1;
6
+ invariant(isFunction(pred));
7
+ function _find(iter) {
8
+ for (const value of iter) {
9
+ if (pred(value)) {
10
+ return value;
11
+ }
12
+ }
13
+ return;
14
+ }
15
+ if (!arg2) {
16
+ return _find;
17
+ }
18
+ return _find(arg1);
19
+ }
20
+
21
+ export { ifind };
@@ -0,0 +1,21 @@
1
+ import { isFunction } from 'remeda';
2
+ import { invariant } from '../../errors/index.js';
3
+
4
+ function isome(arg1, arg2) {
5
+ const pred = arg2 ?? arg1;
6
+ invariant(isFunction(pred));
7
+ function _some(iter) {
8
+ for (const value of iter) {
9
+ if (pred(value)) {
10
+ return true;
11
+ }
12
+ }
13
+ return false;
14
+ }
15
+ if (!arg2) {
16
+ return _some;
17
+ }
18
+ return _some(arg1);
19
+ }
20
+
21
+ export { isome };
@@ -0,0 +1,48 @@
1
+ import { hasAtLeast } from 'remeda';
2
+ import { s as setExports } from '../../../../_virtual/set.js';
3
+
4
+ function union(...sets) {
5
+ let result = /* @__PURE__ */ new Set();
6
+ for (const set of sets) {
7
+ for (const value of set) {
8
+ result.add(value);
9
+ }
10
+ }
11
+ return result;
12
+ }
13
+ function intersection(first, ...sets) {
14
+ let result = /* @__PURE__ */ new Set();
15
+ if (first.size === 0) {
16
+ return result;
17
+ }
18
+ let other = hasAtLeast(sets, 2) ? setExports.intersection(...sets) : sets[0];
19
+ if (other.size === 0) {
20
+ return result;
21
+ }
22
+ for (const value of first) {
23
+ if (other.has(value)) {
24
+ result.add(value);
25
+ }
26
+ }
27
+ return result;
28
+ }
29
+ function difference(a, b) {
30
+ if (a.size === 0) {
31
+ return /* @__PURE__ */ new Set();
32
+ }
33
+ if (b.size === 0) {
34
+ return new Set(a);
35
+ }
36
+ let result = /* @__PURE__ */ new Set();
37
+ for (const value of a) {
38
+ if (!b.has(value)) {
39
+ result.add(value);
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ function equals(a, b) {
45
+ return a.size === b.size && [...a].every((value) => b.has(value));
46
+ }
47
+
48
+ export { difference, equals, intersection, union };
@@ -0,0 +1,13 @@
1
+ import { invariant } from '../errors/index.js';
2
+
3
+ function stringHash(str) {
4
+ let hash = 5381;
5
+ const len = str.length;
6
+ invariant(len > 0, "stringHash: empty string");
7
+ for (let i = 0; i < len; i++) {
8
+ hash = hash * 33 ^ str.charCodeAt(i);
9
+ }
10
+ return (hash >>> 0).toString(36);
11
+ }
12
+
13
+ export { stringHash };