@likec4/diagram 1.39.5 → 1.40.0

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/{chunk-CW_KClZe.js → chunk-CDr-4Ymp.js} +936 -903
  2. package/bundle/custom.js +61 -60
  3. package/bundle/index.js +2667 -539
  4. package/dist/LikeC4Diagram.d.ts +1 -1
  5. package/dist/LikeC4Diagram.js +7 -4
  6. package/dist/LikeC4Diagram.props.d.ts +6 -1
  7. package/dist/base/const.d.ts +9 -0
  8. package/dist/base/const.js +10 -1
  9. package/dist/base/index.d.ts +1 -0
  10. package/dist/base/primitives/edge/EdgeLabel.js +2 -1
  11. package/dist/base/primitives/edge/EdgeLabelContainer.js +10 -4
  12. package/dist/base/primitives/edge/EdgePath.js +17 -8
  13. package/dist/base/primitives/edge/edge.css.d.ts +0 -1
  14. package/dist/base/primitives/edge/edge.css.js +6 -14
  15. package/dist/base/primitives/element/ElementActionButtons.js +1 -3
  16. package/dist/base/primitives/element/ElementNodeContainer.d.ts +0 -6
  17. package/dist/base/primitives/element/ElementNodeContainer.js +3 -26
  18. package/dist/base/primitives/element/ElementTitle.js +2 -2
  19. package/dist/base/types.d.ts +3 -3
  20. package/dist/bundle/LikeC4View.js +4 -12
  21. package/dist/bundle/LikeC4View.props.d.ts +12 -3
  22. package/dist/bundle/custom.js +1 -1
  23. package/dist/custom/customNodes.d.ts +6 -0
  24. package/dist/custom/customNodes.js +2 -1
  25. package/dist/custom/index.js +1 -1
  26. package/dist/hooks/useDiagram.d.ts +5 -1
  27. package/dist/hooks/useDiagram.js +3 -0
  28. package/dist/hooks/useXYFlow.d.ts +4 -439
  29. package/dist/likec4diagram/DiagramUI.js +1 -1
  30. package/dist/likec4diagram/DiagramXYFlow.js +8 -4
  31. package/dist/likec4diagram/convert-to-xyflow.d.ts +14 -0
  32. package/dist/likec4diagram/convert-to-xyflow.js +29 -0
  33. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
  34. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +0 -1
  35. package/dist/likec4diagram/custom/edges/SequenceStepEdge.d.ts +3 -0
  36. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +71 -0
  37. package/dist/likec4diagram/custom/index.js +7 -2
  38. package/dist/likec4diagram/custom/nodes/ElementActions.d.ts +2 -1
  39. package/dist/likec4diagram/custom/nodes/SequenceActorNode.d.ts +4 -0
  40. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +157 -0
  41. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  42. package/dist/likec4diagram/custom/nodes/nodes.d.ts +1 -2
  43. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +31 -27
  44. package/dist/likec4diagram/types.d.ts +41 -7
  45. package/dist/likec4diagram/xyflow-diagram/diagram-view.d.ts +11 -0
  46. package/dist/likec4diagram/{useViewToNodesEdges.js → xyflow-diagram/diagram-view.js} +4 -13
  47. package/dist/likec4diagram/xyflow-sequence/_types.d.ts +41 -0
  48. package/dist/likec4diagram/xyflow-sequence/const.d.ts +17 -0
  49. package/dist/likec4diagram/xyflow-sequence/const.js +16 -0
  50. package/dist/likec4diagram/xyflow-sequence/layouter.d.ts +46 -0
  51. package/dist/likec4diagram/xyflow-sequence/layouter.js +408 -0
  52. package/dist/likec4diagram/xyflow-sequence/sequence-view.d.ts +7 -0
  53. package/dist/likec4diagram/xyflow-sequence/sequence-view.js +273 -0
  54. package/dist/likec4diagram/xyflow-sequence/utils.d.ts +14 -0
  55. package/dist/likec4diagram/xyflow-sequence/utils.js +92 -0
  56. package/dist/navigationpanel/NavigationPanelControls.js +2 -2
  57. package/dist/navigationpanel/controls/ViewDetailsCard.js +5 -2
  58. package/dist/navigationpanel/dropdown/ProjectsMenu.js +53 -44
  59. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.d.ts +1 -2
  60. package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.js +10 -41
  61. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +292 -0
  62. package/dist/navigationpanel/walkthrough/DynamicViewControls.js +119 -0
  63. package/dist/navigationpanel/walkthrough/index.d.ts +2 -1
  64. package/dist/overlays/relationship-details/RelationshipDetails.js +4 -1
  65. package/dist/state/DiagramActorProvider.d.ts +3 -2
  66. package/dist/state/DiagramActorProvider.js +19 -11
  67. package/dist/state/assign.js +11 -4
  68. package/dist/state/diagram-machine.d.ts +224 -194
  69. package/dist/state/diagram-machine.js +170 -63
  70. package/dist/state/utils.d.ts +7 -1
  71. package/dist/state/utils.js +67 -7
  72. package/dist/styles-min.css +1 -1
  73. package/dist/styles.css +1 -1
  74. package/dist/styles.css2.js +1 -1
  75. package/package.json +8 -7
  76. package/panda.buildinfo.json +30 -9
  77. package/src/LikeC4Diagram.props.ts +14 -1
  78. package/src/LikeC4Diagram.tsx +7 -4
  79. package/src/base/const.ts +12 -0
  80. package/src/base/index.ts +2 -0
  81. package/src/base/primitives/edge/EdgeLabel.tsx +23 -19
  82. package/src/base/primitives/edge/EdgeLabelContainer.tsx +12 -7
  83. package/src/base/primitives/edge/EdgePath.tsx +25 -14
  84. package/src/base/primitives/edge/edge.css.ts +5 -14
  85. package/src/base/primitives/element/ElementActionButtons.tsx +1 -3
  86. package/src/base/primitives/element/ElementNodeContainer.tsx +3 -30
  87. package/src/base/primitives/element/ElementTitle.tsx +2 -3
  88. package/src/base/types.ts +7 -5
  89. package/src/bundle/LikeC4View.props.ts +14 -3
  90. package/src/bundle/LikeC4View.tsx +4 -14
  91. package/src/bundle/ReactLikeC4.tsx +1 -2
  92. package/src/custom/customNodes.ts +7 -0
  93. package/src/hooks/useDiagram.ts +10 -0
  94. package/src/likec4diagram/DiagramUI.tsx +1 -1
  95. package/src/likec4diagram/DiagramXYFlow.tsx +10 -5
  96. package/src/likec4diagram/convert-to-xyflow.ts +45 -0
  97. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
  98. package/src/likec4diagram/custom/edges/SequenceStepEdge.tsx +65 -0
  99. package/src/likec4diagram/custom/index.ts +7 -1
  100. package/src/likec4diagram/custom/nodes/ElementActions.tsx +4 -2
  101. package/src/likec4diagram/custom/nodes/SequenceActorNode.tsx +175 -0
  102. package/src/likec4diagram/custom/nodes/index.ts +1 -0
  103. package/src/likec4diagram/custom/nodes/nodes.tsx +1 -1
  104. package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +36 -30
  105. package/src/likec4diagram/types.ts +96 -13
  106. package/src/likec4diagram/{useViewToNodesEdges.ts → xyflow-diagram/diagram-view.ts} +7 -28
  107. package/src/likec4diagram/xyflow-sequence/_types.ts +45 -0
  108. package/src/likec4diagram/xyflow-sequence/const.ts +37 -0
  109. package/src/likec4diagram/xyflow-sequence/layouter.ts +567 -0
  110. package/src/likec4diagram/xyflow-sequence/sequence-view.ts +337 -0
  111. package/src/likec4diagram/xyflow-sequence/utils.ts +112 -0
  112. package/src/navigationpanel/NavigationPanelControls.tsx +2 -2
  113. package/src/navigationpanel/controls/ViewDetailsCard.tsx +5 -2
  114. package/src/navigationpanel/dropdown/ProjectsMenu.tsx +5 -1
  115. package/src/navigationpanel/walkthrough/ActiveWalkthroughControls.tsx +5 -40
  116. package/src/navigationpanel/walkthrough/DynamicViewControls.tsx +125 -0
  117. package/src/navigationpanel/walkthrough/index.tsx +2 -1
  118. package/src/overlays/relationship-details/RelationshipDetails.tsx +5 -2
  119. package/src/state/DiagramActorProvider.tsx +21 -10
  120. package/src/state/assign.ts +18 -5
  121. package/src/state/diagram-machine.ts +207 -75
  122. package/src/state/utils.ts +81 -7
  123. package/dist/likec4diagram/useViewToNodesEdges.d.ts +0 -12
@@ -2,11 +2,12 @@ import { nonNullable, invariant, nonexhaustive, BBox, isStepEdgeId, getParallelS
2
2
  import { getViewportForBounds, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';
3
3
  import { nodeToRect } from '@xyflow/system';
4
4
  import { hasAtLeast, prop, clamp, first } from 'remeda';
5
- import { setup, emit, assign, enqueueActions, sendTo, assertEvent, cancel, stopChild, raise, spawnChild, and, or } from 'xstate';
5
+ import { setup, assign, emit, sendTo, enqueueActions, assertEvent, cancel, stopChild, raise, spawnChild, and, or } from 'xstate';
6
6
  import { MinZoom } from '../base/const.js';
7
7
  import { Base } from '../base/types.js';
8
8
  import { AllDisabled } from '../context/DiagramFeatures.js';
9
9
  import { createLayoutConstraints } from '../likec4diagram/useLayoutConstraints.js';
10
+ import { SeqParallelAreaColor } from '../likec4diagram/xyflow-sequence/const.js';
10
11
  import { overlaysActorLogic } from '../overlays/overlaysActor.js';
11
12
  import { searchActorLogic } from '../search/searchActor.js';
12
13
  import { getAligner, toNodeRect } from './aligners.js';
@@ -14,7 +15,7 @@ import { updateActiveWalkthrough, focusNodesEdges, lastClickedNode, updateNaviga
14
15
  import { hotkeyActorLogic } from './hotkeyActor.js';
15
16
  import { DiagramToggledFeaturesPersistence } from './persistence.js';
16
17
  import { syncManualLayoutActorLogic } from './syncManualLayoutActor.js';
17
- import { findDiagramEdge, findDiagramNode, typedSystem, focusedBounds } from './utils.js';
18
+ import { findDiagramEdge, findDiagramNode, typedSystem, activeSequenceBounds, focusedBounds } from './utils.js';
18
19
 
19
20
  const _diagramMachine = setup({
20
21
  types: {
@@ -33,10 +34,11 @@ const _diagramMachine = setup({
33
34
  guards: {
34
35
  "isReady": ({ context }) => context.initialized.xydata && context.initialized.xyflow,
35
36
  "enabled: FitView": ({ context }) => context.features.enableFitView,
36
- "enabled: FocusMode": ({ context }) => context.features.enableFocusMode,
37
+ "enabled: FocusMode": ({ context }) => context.features.enableFocusMode && (context.view._type !== "dynamic" || context.dynamicViewVariant !== "sequence"),
37
38
  "enabled: Readonly": ({ context }) => context.features.enableReadOnly,
38
39
  "enabled: RelationshipDetails": ({ context }) => context.features.enableRelationshipDetails,
39
40
  "enabled: Search": ({ context }) => context.features.enableSearch,
41
+ "enabled: ElementDetails": ({ context }) => context.features.enableElementDetails,
40
42
  "not readonly": ({ context }) => !context.features.enableReadOnly,
41
43
  "is dynamic view": ({ context }) => context.view._type === "dynamic",
42
44
  "is another view": ({ context, event }) => {
@@ -49,6 +51,10 @@ const _diagramMachine = setup({
49
51
  }
50
52
  nonexhaustive(event.type);
51
53
  },
54
+ "click: node has modelFqn": ({ event }) => {
55
+ assertEvent(event, "xyflow.nodeClick");
56
+ return "modelFqn" in event.node.data;
57
+ },
52
58
  "click: selected node": ({ event }) => {
53
59
  assertEvent(event, "xyflow.nodeClick");
54
60
  return event.node.selected === true;
@@ -85,6 +91,22 @@ const _diagramMachine = setup({
85
91
  lastClickedNode: lastClickedNode({ context, event })
86
92
  };
87
93
  }),
94
+ "assign: focusedNode": assign(({ context, event }) => {
95
+ let focusedNode;
96
+ switch (event.type) {
97
+ case "xyflow.nodeClick":
98
+ focusedNode = event.node.data.id;
99
+ break;
100
+ case "focus.node":
101
+ focusedNode = event.nodeId;
102
+ break;
103
+ default:
104
+ throw new Error(`Unexpected event type: ${event.type} in action 'assign: focusedNode'`);
105
+ }
106
+ return {
107
+ focusedNode
108
+ };
109
+ }),
88
110
  "reset lastClickedNode": assign(() => ({
89
111
  lastClickedNode: null
90
112
  })),
@@ -119,7 +141,8 @@ const _diagramMachine = setup({
119
141
  panZoom?.setViewport(viewport, duration > 0 ? { duration } : void 0);
120
142
  },
121
143
  "xyflow:fitFocusedBounds": ({ context }) => {
122
- const { bounds, duration = 450 } = focusedBounds({ context });
144
+ const isActiveSequenceWalkthrough = !!context.activeWalkthrough && context.dynamicViewVariant === "sequence";
145
+ const { bounds, duration = 450 } = isActiveSequenceWalkthrough ? activeSequenceBounds({ context }) : focusedBounds({ context });
123
146
  const { width, height, panZoom, transform } = nonNullable(context.xystore).getState();
124
147
  const maxZoom = Math.max(1, transform[2]);
125
148
  const viewport = getViewportForBounds(
@@ -340,6 +363,50 @@ const _diagramMachine = setup({
340
363
  }))
341
364
  })),
342
365
  "update active walkthrough": assign(updateActiveWalkthrough),
366
+ "open element details": sendTo(
367
+ ({ system }) => typedSystem(system).overlaysActorRef,
368
+ ({ context, event }, params) => {
369
+ let initiatedFrom = null;
370
+ let fromNodeId, subject;
371
+ if (params) {
372
+ subject = params.fqn;
373
+ fromNodeId = params.fromNode ?? context.view.nodes.find((n) => n.modelRef === params.fqn)?.id;
374
+ } else if (event.type === "xyflow.nodeClick") {
375
+ invariant("modelFqn" in event.node.data && !!event.node.data.modelFqn, "No modelFqn in event node data");
376
+ subject = event.node.data.modelFqn;
377
+ fromNodeId = event.node.data.id;
378
+ } else if (event.type === "open.elementDetails") {
379
+ subject = event.fqn;
380
+ fromNodeId = event.fromNode;
381
+ } else {
382
+ invariant(context.lastClickedNode, "No last clicked node");
383
+ fromNodeId = nonNullable(context.lastClickedNode).id;
384
+ const node = nonNullable(context.xynodes.find((n) => n.id === fromNodeId));
385
+ invariant("modelFqn" in node.data && !!node.data.modelFqn, "No modelFqn in last clicked node");
386
+ subject = node.data.modelFqn;
387
+ }
388
+ const internalNode = fromNodeId ? context.xystore.getState().nodeLookup.get(fromNodeId) : null;
389
+ if (fromNodeId && internalNode) {
390
+ const nodeRect = nodeToRect(internalNode);
391
+ const zoom = context.xyflow.getZoom();
392
+ const clientRect = {
393
+ ...context.xyflow.flowToScreenPosition(nodeRect),
394
+ width: nodeRect.width * zoom,
395
+ height: nodeRect.height * zoom
396
+ };
397
+ initiatedFrom = {
398
+ node: fromNodeId,
399
+ clientRect
400
+ };
401
+ }
402
+ return {
403
+ type: "open.elementDetails",
404
+ subject,
405
+ currentView: context.view,
406
+ ...initiatedFrom && { initiatedFrom }
407
+ };
408
+ }
409
+ ),
343
410
  "emit: walkthroughStarted": emit(({ context }) => {
344
411
  const edge = context.xyedges.find((x) => x.id === context.activeWalkthrough?.stepId);
345
412
  invariant(edge, "Invalid walkthrough state");
@@ -387,6 +454,12 @@ const _diagramMachine = setup({
387
454
  edge,
388
455
  xyedge: event.edge
389
456
  };
457
+ }),
458
+ "assign: dynamicViewVariant": assign(({ event }) => {
459
+ assertEvent(event, "switch.dynamicViewVariant");
460
+ return {
461
+ dynamicViewVariant: event.variant
462
+ };
390
463
  })
391
464
  }
392
465
  }).createMachine({
@@ -416,6 +489,7 @@ const _diagramMachine = setup({
416
489
  viewport: { x: 0, y: 0, zoom: 1 },
417
490
  xyflow: null,
418
491
  syncLayoutActorRef: null,
492
+ dynamicViewVariant: input.dynamicViewVariant ?? (input.view._type === "dynamic" ? input.view.variant : "diagram") ?? "diagram",
419
493
  activeWalkthrough: null
420
494
  }),
421
495
  // entry: ({ spawn }) => spawn(layoutActor, { id: 'layout', input: { parent: self } }),
@@ -520,30 +594,8 @@ const _diagramMachine = setup({
520
594
  ]
521
595
  },
522
596
  "open.elementDetails": {
523
- actions: sendTo(({ system }) => typedSystem(system).overlaysActorRef, ({ context, event }) => {
524
- let initiatedFrom = null;
525
- const fromNodeId = event.fromNode ?? context.view.nodes.find((n) => n.modelRef === event.fqn)?.id;
526
- const internalNode = fromNodeId ? nonNullable(context.xystore).getState().nodeLookup.get(fromNodeId) : null;
527
- if (fromNodeId && internalNode) {
528
- const nodeRect = nodeToRect(internalNode);
529
- const zoom = context.xyflow.getZoom();
530
- const clientRect = {
531
- ...context.xyflow.flowToScreenPosition(nodeRect),
532
- width: nodeRect.width * zoom,
533
- height: nodeRect.height * zoom
534
- };
535
- initiatedFrom = {
536
- node: fromNodeId,
537
- clientRect
538
- };
539
- }
540
- return {
541
- type: "open.elementDetails",
542
- subject: event.fqn,
543
- currentView: context.view,
544
- ...initiatedFrom && { initiatedFrom }
545
- };
546
- })
597
+ guard: "enabled: ElementDetails",
598
+ actions: "open element details"
547
599
  },
548
600
  "open.relationshipsBrowser": {
549
601
  actions: sendTo(({ system }) => typedSystem(system).overlaysActorRef, ({ context, event }) => ({
@@ -655,14 +707,29 @@ const _diagramMachine = setup({
655
707
  ])
656
708
  ]),
657
709
  actions: [
658
- assign({
659
- lastClickedNode,
660
- focusedNode: ({ event }) => event.node.id
661
- }),
710
+ "assign lastClickedNode",
711
+ "assign: focusedNode",
662
712
  "emit: nodeClick"
663
713
  ],
664
714
  target: "focused"
665
715
  },
716
+ {
717
+ // TODO: xstate fails to infer the type of the guard
718
+ guard: and([
719
+ "enabled: ElementDetails",
720
+ "click: node has modelFqn",
721
+ or([
722
+ "click: same node",
723
+ "click: selected node"
724
+ ])
725
+ ]),
726
+ actions: [
727
+ "assign lastClickedNode",
728
+ "open source of focused or last clicked node",
729
+ "open element details",
730
+ "emit: nodeClick"
731
+ ]
732
+ },
666
733
  {
667
734
  actions: [
668
735
  "assign lastClickedNode",
@@ -686,9 +753,7 @@ const _diagramMachine = setup({
686
753
  },
687
754
  "focus.node": {
688
755
  guard: "enabled: FocusMode",
689
- actions: assign({
690
- focusedNode: ({ event }) => event.nodeId
691
- }),
756
+ actions: "assign: focusedNode",
692
757
  target: "focused"
693
758
  },
694
759
  "xyflow.edgeClick": {
@@ -731,6 +796,16 @@ const _diagramMachine = setup({
731
796
  }),
732
797
  on: {
733
798
  "xyflow.nodeClick": [
799
+ {
800
+ guard: and([
801
+ "click: focused node",
802
+ "click: node has modelFqn"
803
+ ]),
804
+ actions: [
805
+ "open element details",
806
+ "emit: nodeClick"
807
+ ]
808
+ },
734
809
  {
735
810
  guard: "click: focused node",
736
811
  actions: "emit: nodeClick",
@@ -738,9 +813,7 @@ const _diagramMachine = setup({
738
813
  },
739
814
  {
740
815
  actions: [
741
- assign({
742
- lastClickedNode
743
- }),
816
+ "assign lastClickedNode",
744
817
  raise(({ event }) => ({
745
818
  type: "focus.node",
746
819
  nodeId: event.node.id
@@ -751,9 +824,7 @@ const _diagramMachine = setup({
751
824
  ],
752
825
  "focus.node": {
753
826
  actions: [
754
- assign({
755
- focusedNode: ({ event }) => event.nodeId
756
- }),
827
+ "assign: focusedNode",
757
828
  "focus on nodes and edges",
758
829
  "open source of focused or last clicked node",
759
830
  "xyflow:fitFocusedBounds"
@@ -795,6 +866,32 @@ const _diagramMachine = setup({
795
866
  "xyflow:fitFocusedBounds",
796
867
  "emit: walkthroughStarted"
797
868
  ],
869
+ exit: enqueueActions(({ enqueue, context, event }) => {
870
+ enqueue.stopChild("hotkey");
871
+ if (context.viewportBeforeFocus) {
872
+ enqueue({ type: "xyflow:setViewport", params: { viewport: context.viewportBeforeFocus } });
873
+ } else {
874
+ enqueue("xyflow:fitDiagram");
875
+ }
876
+ if (context.dynamicViewVariant === "sequence" && context.activeWalkthrough?.parallelPrefix) {
877
+ enqueue.assign({
878
+ xynodes: context.xynodes.map((n) => {
879
+ if (n.type === "seq-parallel") {
880
+ return Base.setData(n, {
881
+ color: SeqParallelAreaColor.default
882
+ });
883
+ }
884
+ return n;
885
+ })
886
+ });
887
+ }
888
+ enqueue("undim everything");
889
+ enqueue.assign({
890
+ activeWalkthrough: null,
891
+ viewportBeforeFocus: null
892
+ });
893
+ enqueue("emit: walkthroughStopped");
894
+ }),
798
895
  on: {
799
896
  "key.esc": {
800
897
  target: "idle"
@@ -867,21 +964,7 @@ const _diagramMachine = setup({
867
964
  actions: raise(({ event }) => event, { delay: 50 }),
868
965
  target: "idle"
869
966
  }
870
- },
871
- exit: enqueueActions(({ enqueue, context, event }) => {
872
- enqueue.stopChild("hotkey");
873
- if (context.viewportBeforeFocus) {
874
- enqueue({ type: "xyflow:setViewport", params: { viewport: context.viewportBeforeFocus } });
875
- } else {
876
- enqueue("xyflow:fitDiagram");
877
- }
878
- enqueue("undim everything");
879
- enqueue.assign({
880
- activeWalkthrough: null,
881
- viewportBeforeFocus: null
882
- });
883
- enqueue("emit: walkthroughStopped");
884
- })
967
+ }
885
968
  }
886
969
  }
887
970
  },
@@ -904,8 +987,8 @@ const _diagramMachine = setup({
904
987
  params: {
905
988
  fromNode: fromNode.id,
906
989
  toPosition: {
907
- x: toNode.position[0],
908
- y: toNode.position[1]
990
+ x: toNode.data.position[0],
991
+ y: toNode.data.position[1]
909
992
  }
910
993
  }
911
994
  });
@@ -989,8 +1072,8 @@ const _diagramMachine = setup({
989
1072
  params: {
990
1073
  fromNode: fromNode.id,
991
1074
  toPosition: {
992
- x: toNode.position[0],
993
- y: toNode.position[1]
1075
+ x: toNode.data.position[0],
1076
+ y: toNode.data.position[1]
994
1077
  }
995
1078
  }
996
1079
  });
@@ -1002,6 +1085,14 @@ const _diagramMachine = setup({
1002
1085
  });
1003
1086
  enqueue.raise({ type: "fitDiagram", duration: 200 }, { id: "fitDiagram", delay: 25 });
1004
1087
  }
1088
+ } else {
1089
+ const nextView = event.view;
1090
+ if (nextView._type === "dynamic" && context.view._type === "dynamic" && nextView.variant !== context.view.variant) {
1091
+ enqueue({
1092
+ type: "xyflow:setViewportCenter",
1093
+ params: BBox.center(nextView.bounds)
1094
+ });
1095
+ }
1005
1096
  }
1006
1097
  enqueue.assign({
1007
1098
  ...mergeXYNodesEdges({ context, event }),
@@ -1027,6 +1118,9 @@ const _diagramMachine = setup({
1027
1118
  "ensure overlays actor state",
1028
1119
  "ensure search actor state"
1029
1120
  ]
1121
+ },
1122
+ "switch.dynamicViewVariant": {
1123
+ actions: "assign: dynamicViewVariant"
1030
1124
  }
1031
1125
  },
1032
1126
  exit: [
@@ -1049,13 +1143,26 @@ const _diagramMachine = setup({
1049
1143
  ]
1050
1144
  });
1051
1145
  function nodeRef(node) {
1052
- return node.modelRef ?? node.deploymentRef;
1146
+ switch (node.type) {
1147
+ case "element":
1148
+ case "compound-element":
1149
+ case "seq-actor":
1150
+ return node.data.modelFqn;
1151
+ case "deployment":
1152
+ case "compound-deployment":
1153
+ return node.data.modelFqn ?? node.data.deploymentFqn;
1154
+ case "seq-parallel":
1155
+ case "view-group":
1156
+ return null;
1157
+ default:
1158
+ nonexhaustive(node);
1159
+ }
1053
1160
  }
1054
1161
  function findCorrespondingNode(context, event) {
1055
1162
  const fromNodeId = context.lastOnNavigate?.fromNode;
1056
- const fromNode = fromNodeId && context.view.nodes.find((n) => n.id === fromNodeId);
1163
+ const fromNode = fromNodeId && context.xynodes.find((n) => n.id === fromNodeId);
1057
1164
  const fromRef = fromNode && nodeRef(fromNode);
1058
- const toNode = fromRef && event.view.nodes.find((n) => nodeRef(n) === fromRef);
1165
+ const toNode = fromRef && event.xynodes.find((n) => nodeRef(n) === fromRef);
1059
1166
  return { fromNode, toNode };
1060
1167
  }
1061
1168
  const diagramMachine = _diagramMachine;
@@ -1,4 +1,4 @@
1
- import { BBox, DiagramEdge, DiagramNode } from '@likec4/core/types';
1
+ import { DiagramEdge, DiagramNode, BBox } from '@likec4/core/types';
2
2
  import { ActorSystem } from 'xstate';
3
3
  import { OverlaysActorRef } from '../overlays/overlaysActor';
4
4
  import { SearchActorRef } from '../search/searchActor';
@@ -17,3 +17,9 @@ export declare function focusedBounds(params: {
17
17
  bounds: BBox;
18
18
  duration?: number;
19
19
  };
20
+ export declare function activeSequenceBounds(params: {
21
+ context: Context;
22
+ }): {
23
+ bounds: BBox;
24
+ duration?: number;
25
+ };
@@ -1,3 +1,7 @@
1
+ import { BBox } from '@likec4/core/types';
2
+ import { nonNullable } from '@likec4/core/utils';
3
+ import { getNodesBounds, getNodeDimensions, getEdgePosition } from '@xyflow/system';
4
+
1
5
  function typedSystem(system) {
2
6
  return {
3
7
  get overlaysActorRef() {
@@ -34,12 +38,11 @@ function focusedBounds(params) {
34
38
  }
35
39
  const width = node.measured?.width ?? node.width ?? node.initialWidth;
36
40
  const height = node.measured?.height ?? node.height ?? node.initialHeight;
37
- return {
38
- minX: Math.min(acc.minX, position.x),
39
- minY: Math.min(acc.minY, position.y),
40
- maxX: Math.max(acc.maxX, position.x + width),
41
- maxY: Math.max(acc.maxY, position.y + height)
42
- };
41
+ acc.minX = Math.min(acc.minX, position.x);
42
+ acc.minY = Math.min(acc.minY, position.y);
43
+ acc.maxX = Math.max(acc.maxX, position.x + width);
44
+ acc.maxY = Math.max(acc.maxY, position.y + height);
45
+ return acc;
43
46
  }, {
44
47
  minX: Infinity,
45
48
  minY: Infinity,
@@ -61,5 +64,62 @@ function focusedBounds(params) {
61
64
  }
62
65
  };
63
66
  }
67
+ const MARGIN = 32;
68
+ function activeSequenceBounds(params) {
69
+ const activeWalkthrough = nonNullable(params.context.activeWalkthrough);
70
+ const stepEdge = nonNullable(params.context.xyedges.find((e) => e.id === activeWalkthrough.stepId));
71
+ const xystate = params.context.xystore.getState();
72
+ const sourceNode = nonNullable(xystate.nodeLookup.get(stepEdge.source));
73
+ const targetNode = nonNullable(xystate.nodeLookup.get(stepEdge.target));
74
+ const actorsBounds = getNodesBounds([sourceNode, targetNode], xystate);
75
+ let stepBounds;
76
+ if (activeWalkthrough.parallelPrefix) {
77
+ const parallelArea = params.context.xynodes.find(
78
+ (n) => n.type === "seq-parallel" && n.data.parallelPrefix === activeWalkthrough.parallelPrefix
79
+ );
80
+ if (parallelArea) {
81
+ stepBounds = {
82
+ x: parallelArea.position.x,
83
+ y: parallelArea.position.y,
84
+ ...getNodeDimensions(parallelArea)
85
+ };
86
+ }
87
+ }
88
+ stepBounds ??= getEdgeBounds(stepEdge, xystate);
89
+ if (stepBounds) {
90
+ stepBounds = BBox.merge(stepBounds, actorsBounds);
91
+ } else {
92
+ stepBounds = actorsBounds;
93
+ }
94
+ return {
95
+ duration: 350,
96
+ bounds: BBox.expand(
97
+ stepBounds,
98
+ MARGIN
99
+ )
100
+ };
101
+ }
102
+ function getEdgeBounds(edge, store) {
103
+ const sourceNode = store.nodeLookup.get(edge.source);
104
+ const targetNode = store.nodeLookup.get(edge.target);
105
+ if (!sourceNode || !targetNode) {
106
+ return null;
107
+ }
108
+ const edgePosition = getEdgePosition({
109
+ id: edge.id,
110
+ sourceNode,
111
+ targetNode,
112
+ sourceHandle: edge.sourceHandle || null,
113
+ targetHandle: edge.targetHandle || null,
114
+ connectionMode: store.connectionMode
115
+ });
116
+ if (!edgePosition) {
117
+ return null;
118
+ }
119
+ return BBox.fromPoints([
120
+ [edgePosition.sourceX, edgePosition.sourceY],
121
+ [edgePosition.targetX, edgePosition.targetY]
122
+ ]);
123
+ }
64
124
 
65
- export { findDiagramEdge, findDiagramNode, focusedBounds, typedSystem };
125
+ export { activeSequenceBounds, findDiagramEdge, findDiagramNode, focusedBounds, typedSystem };