@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
@@ -10,6 +10,7 @@ import type {
10
10
  DiagramEdge,
11
11
  DiagramNode,
12
12
  DiagramView,
13
+ DynamicViewDisplayVariant,
13
14
  EdgeId,
14
15
  Fqn,
15
16
  NodeId,
@@ -29,11 +30,13 @@ import { type EdgeChange, type NodeChange, type Rect, type Viewport, nodeToRect
29
30
  import type { MouseEvent } from 'react'
30
31
  import { clamp, first, hasAtLeast, prop } from 'remeda'
31
32
  import type { PartialDeep } from 'type-fest'
33
+ import type {
34
+ ActorLogicFrom,
35
+ ActorRef,
36
+ AnyEventObject,
37
+ Snapshot,
38
+ } from 'xstate'
32
39
  import {
33
- type ActorLogicFrom,
34
- type ActorRef,
35
- type AnyEventObject,
36
- type Snapshot,
37
40
  and,
38
41
  assertEvent,
39
42
  assign,
@@ -53,6 +56,7 @@ import { type EnabledFeatures, type FeatureName, AllDisabled } from '../context/
53
56
  import type { OpenSourceParams, ViewPadding } from '../LikeC4Diagram.props'
54
57
  import type { Types } from '../likec4diagram/types'
55
58
  import { createLayoutConstraints } from '../likec4diagram/useLayoutConstraints'
59
+ import { SeqParallelAreaColor } from '../likec4diagram/xyflow-sequence/const'
56
60
  import { overlaysActorLogic } from '../overlays/overlaysActor'
57
61
  import { searchActorLogic } from '../search/searchActor'
58
62
  import { type AlignmentMode, getAligner, toNodeRect } from './aligners'
@@ -71,7 +75,7 @@ import {
71
75
  import { type HotKeyEvent, hotkeyActorLogic } from './hotkeyActor'
72
76
  import { DiagramToggledFeaturesPersistence } from './persistence'
73
77
  import { type Events as SyncLayoutEvents, syncManualLayoutActorLogic } from './syncManualLayoutActor'
74
- import { findDiagramEdge, findDiagramNode, focusedBounds, typedSystem } from './utils'
78
+ import { activeSequenceBounds, findDiagramEdge, findDiagramNode, focusedBounds, typedSystem } from './utils'
75
79
 
76
80
  export type XYStoreApi = ReturnType<typeof useStoreApi<Types.Node, Types.Edge>>
77
81
 
@@ -91,6 +95,7 @@ export interface Input {
91
95
  pannable: boolean
92
96
  fitViewPadding: ViewPadding
93
97
  nodesSelectable: boolean
98
+ dynamicViewVariant?: DynamicViewDisplayVariant | undefined
94
99
  }
95
100
 
96
101
  export type ToggledFeatures = Partial<EnabledFeatures>
@@ -133,6 +138,7 @@ export interface Context extends Input {
133
138
  syncLayoutActorRef: null | ActorRef<Snapshot<unknown>, SyncLayoutEvents, AnyEventObject>
134
139
 
135
140
  // If Dynamic View
141
+ dynamicViewVariant: DynamicViewDisplayVariant
136
142
  activeWalkthrough: null | {
137
143
  stepId: StepEdgeId
138
144
  parallelPrefix: string | null
@@ -158,7 +164,7 @@ export type Events =
158
164
  | { type: 'update.nodeData'; nodeId: NodeId; data: PartialDeep<Types.NodeData> }
159
165
  | { type: 'update.edgeData'; edgeId: EdgeId; data: PartialDeep<Types.EdgeData> }
160
166
  | { type: 'update.view'; view: DiagramView; xynodes: Types.Node[]; xyedges: Types.Edge[] }
161
- | { type: 'update.inputs'; inputs: Partial<Omit<Input, 'view' | 'xystore'>> }
167
+ | { type: 'update.inputs'; inputs: Partial<Omit<Input, 'view' | 'xystore' | 'dynamicViewVariant'>> }
162
168
  | { type: 'update.features'; features: EnabledFeatures }
163
169
  | { type: 'fitDiagram'; duration?: number; bounds?: BBox }
164
170
  | ({ type: 'open.source' } & OpenSourceParams)
@@ -175,6 +181,7 @@ export type Events =
175
181
  | { type: 'saveManualLayout.schedule' }
176
182
  | { type: 'saveManualLayout.cancel' }
177
183
  | { type: 'focus.node'; nodeId: NodeId }
184
+ | { type: 'switch.dynamicViewVariant'; variant: DynamicViewDisplayVariant }
178
185
  | { type: 'walkthrough.start'; stepId?: StepEdgeId }
179
186
  | { type: 'walkthrough.step'; direction: 'next' | 'previous' }
180
187
  | { type: 'walkthrough.end' }
@@ -222,10 +229,13 @@ const _diagramMachine = setup({
222
229
  guards: {
223
230
  'isReady': ({ context }) => context.initialized.xydata && context.initialized.xyflow,
224
231
  'enabled: FitView': ({ context }) => context.features.enableFitView,
225
- 'enabled: FocusMode': ({ context }) => context.features.enableFocusMode,
232
+ 'enabled: FocusMode': ({ context }) =>
233
+ context.features.enableFocusMode &&
234
+ (context.view._type !== 'dynamic' || context.dynamicViewVariant !== 'sequence'),
226
235
  'enabled: Readonly': ({ context }) => context.features.enableReadOnly,
227
236
  'enabled: RelationshipDetails': ({ context }) => context.features.enableRelationshipDetails,
228
237
  'enabled: Search': ({ context }) => context.features.enableSearch,
238
+ 'enabled: ElementDetails': ({ context }) => context.features.enableElementDetails,
229
239
  'not readonly': ({ context }) => !context.features.enableReadOnly,
230
240
  'is dynamic view': ({ context }) => context.view._type === 'dynamic',
231
241
  'is another view': ({ context, event }) => {
@@ -238,6 +248,10 @@ const _diagramMachine = setup({
238
248
  }
239
249
  nonexhaustive(event.type)
240
250
  },
251
+ 'click: node has modelFqn': ({ event }) => {
252
+ assertEvent(event, 'xyflow.nodeClick')
253
+ return 'modelFqn' in event.node.data
254
+ },
241
255
  'click: selected node': ({ event }) => {
242
256
  assertEvent(event, 'xyflow.nodeClick')
243
257
  return event.node.selected === true
@@ -269,13 +283,28 @@ const _diagramMachine = setup({
269
283
  type: 'openSource',
270
284
  params: _params,
271
285
  })),
272
-
273
286
  'assign lastClickedNode': assign(({ context, event }) => {
274
287
  assertEvent(event, 'xyflow.nodeClick')
275
288
  return {
276
289
  lastClickedNode: lastClickedNode({ context, event }),
277
290
  }
278
291
  }),
292
+ 'assign: focusedNode': assign(({ context, event }) => {
293
+ let focusedNode
294
+ switch (event.type) {
295
+ case 'xyflow.nodeClick':
296
+ focusedNode = event.node.data.id
297
+ break
298
+ case 'focus.node':
299
+ focusedNode = event.nodeId
300
+ break
301
+ default:
302
+ throw new Error(`Unexpected event type: ${event.type} in action 'assign: focusedNode'`)
303
+ }
304
+ return {
305
+ focusedNode,
306
+ }
307
+ }),
279
308
  'reset lastClickedNode': assign(() => ({
280
309
  lastClickedNode: null,
281
310
  })),
@@ -315,7 +344,10 @@ const _diagramMachine = setup({
315
344
  },
316
345
 
317
346
  'xyflow:fitFocusedBounds': ({ context }) => {
318
- const { bounds, duration = 450 } = focusedBounds({ context })
347
+ const isActiveSequenceWalkthrough = !!context.activeWalkthrough && context.dynamicViewVariant === 'sequence'
348
+ const { bounds, duration = 450 } = isActiveSequenceWalkthrough
349
+ ? activeSequenceBounds({ context })
350
+ : focusedBounds({ context })
319
351
  const { width, height, panZoom, transform } = nonNullable(context.xystore).getState()
320
352
 
321
353
  const maxZoom = Math.max(1, transform[2])
@@ -554,6 +586,61 @@ const _diagramMachine = setup({
554
586
  })),
555
587
  })),
556
588
  'update active walkthrough': assign(updateActiveWalkthrough),
589
+ 'open element details': sendTo(
590
+ ({ system }) => typedSystem(system).overlaysActorRef!,
591
+ ({ context, event }, params?: { fqn: Fqn; fromNode?: NodeId | undefined }) => {
592
+ let initiatedFrom = null as null | {
593
+ node: NodeId
594
+ clientRect: Rect
595
+ }
596
+ let fromNodeId: NodeId | undefined, subject: Fqn
597
+ // Use from params if available
598
+ if (params) {
599
+ subject = params.fqn
600
+ fromNodeId = params.fromNode ?? context.view.nodes.find(n => n.modelRef === params.fqn)?.id
601
+ }
602
+ // Use from event if available
603
+ else if (event.type === 'xyflow.nodeClick') {
604
+ invariant('modelFqn' in event.node.data && !!event.node.data.modelFqn, 'No modelFqn in event node data')
605
+ subject = event.node.data.modelFqn
606
+ fromNodeId = event.node.data.id
607
+ }
608
+ else if (event.type === 'open.elementDetails') {
609
+ subject = event.fqn
610
+ fromNodeId = event.fromNode
611
+ }
612
+ // Use from lastClickedNode if available
613
+ else {
614
+ invariant(context.lastClickedNode, 'No last clicked node')
615
+ fromNodeId = nonNullable(context.lastClickedNode).id
616
+ const node = nonNullable(context.xynodes.find(n => n.id === fromNodeId))
617
+ invariant('modelFqn' in node.data && !!node.data.modelFqn, 'No modelFqn in last clicked node')
618
+ subject = node.data.modelFqn
619
+ }
620
+
621
+ const internalNode = fromNodeId ? context.xystore.getState().nodeLookup.get(fromNodeId) : null
622
+ if (fromNodeId && internalNode) {
623
+ const nodeRect = nodeToRect(internalNode)
624
+ const zoom = context.xyflow!.getZoom()
625
+
626
+ const clientRect = {
627
+ ...context.xyflow!.flowToScreenPosition(nodeRect),
628
+ width: nodeRect.width * zoom,
629
+ height: nodeRect.height * zoom,
630
+ }
631
+ initiatedFrom = {
632
+ node: fromNodeId,
633
+ clientRect,
634
+ }
635
+ }
636
+ return ({
637
+ type: 'open.elementDetails' as const,
638
+ subject,
639
+ currentView: context.view,
640
+ ...(initiatedFrom && { initiatedFrom }),
641
+ })
642
+ },
643
+ ),
557
644
  'emit: walkthroughStarted': emit(({ context }) => {
558
645
  const edge = context.xyedges.find(x => x.id === context.activeWalkthrough?.stepId)
559
646
  invariant(edge, 'Invalid walkthrough state')
@@ -602,6 +689,12 @@ const _diagramMachine = setup({
602
689
  xyedge: event.edge,
603
690
  }
604
691
  }),
692
+ 'assign: dynamicViewVariant': assign(({ event }) => {
693
+ assertEvent(event, 'switch.dynamicViewVariant')
694
+ return {
695
+ dynamicViewVariant: event.variant,
696
+ }
697
+ }),
605
698
  },
606
699
  }).createMachine({
607
700
  initial: 'initializing',
@@ -630,6 +723,9 @@ const _diagramMachine = setup({
630
723
  viewport: { x: 0, y: 0, zoom: 1 },
631
724
  xyflow: null,
632
725
  syncLayoutActorRef: null,
726
+ dynamicViewVariant: input.dynamicViewVariant ?? (
727
+ input.view._type === 'dynamic' ? input.view.variant : 'diagram'
728
+ ) ?? 'diagram',
633
729
  activeWalkthrough: null,
634
730
  }),
635
731
  // entry: ({ spawn }) => spawn(layoutActor, { id: 'layout', input: { parent: self } }),
@@ -736,34 +832,8 @@ const _diagramMachine = setup({
736
832
  ],
737
833
  },
738
834
  'open.elementDetails': {
739
- actions: sendTo(({ system }) => typedSystem(system).overlaysActorRef!, ({ context, event }) => {
740
- let initiatedFrom = null as null | {
741
- node: NodeId
742
- clientRect: Rect
743
- }
744
- const fromNodeId = event.fromNode ?? context.view.nodes.find(n => n.modelRef === event.fqn)?.id
745
- const internalNode = fromNodeId ? nonNullable(context.xystore).getState().nodeLookup.get(fromNodeId) : null
746
- if (fromNodeId && internalNode) {
747
- const nodeRect = nodeToRect(internalNode)
748
- const zoom = context.xyflow!.getZoom()
749
-
750
- const clientRect = {
751
- ...context.xyflow!.flowToScreenPosition(nodeRect),
752
- width: nodeRect.width * zoom,
753
- height: nodeRect.height * zoom,
754
- }
755
- initiatedFrom = {
756
- node: fromNodeId,
757
- clientRect,
758
- }
759
- }
760
- return ({
761
- type: 'open.elementDetails' as const,
762
- subject: event.fqn,
763
- currentView: context.view,
764
- ...(initiatedFrom && { initiatedFrom }),
765
- })
766
- }),
835
+ guard: 'enabled: ElementDetails',
836
+ actions: 'open element details',
767
837
  },
768
838
  'open.relationshipsBrowser': {
769
839
  actions: sendTo(({ system }) => typedSystem(system).overlaysActorRef!, ({ context, event }) => ({
@@ -876,14 +946,29 @@ const _diagramMachine = setup({
876
946
  ]),
877
947
  ]) as any,
878
948
  actions: [
879
- assign({
880
- lastClickedNode,
881
- focusedNode: ({ event }) => event.node.id as NodeId,
882
- }),
949
+ 'assign lastClickedNode',
950
+ 'assign: focusedNode',
883
951
  'emit: nodeClick',
884
952
  ],
885
953
  target: 'focused',
886
954
  },
955
+ {
956
+ // TODO: xstate fails to infer the type of the guard
957
+ guard: and([
958
+ 'enabled: ElementDetails',
959
+ 'click: node has modelFqn',
960
+ or([
961
+ 'click: same node',
962
+ 'click: selected node',
963
+ ]),
964
+ ]) as any,
965
+ actions: [
966
+ 'assign lastClickedNode',
967
+ 'open source of focused or last clicked node',
968
+ 'open element details',
969
+ 'emit: nodeClick',
970
+ ],
971
+ },
887
972
  {
888
973
  actions: [
889
974
  'assign lastClickedNode',
@@ -907,9 +992,7 @@ const _diagramMachine = setup({
907
992
  },
908
993
  'focus.node': {
909
994
  guard: 'enabled: FocusMode',
910
- actions: assign({
911
- focusedNode: ({ event }) => event.nodeId as NodeId,
912
- }),
995
+ actions: 'assign: focusedNode',
913
996
  target: 'focused',
914
997
  },
915
998
  'xyflow.edgeClick': {
@@ -952,6 +1035,16 @@ const _diagramMachine = setup({
952
1035
  }),
953
1036
  on: {
954
1037
  'xyflow.nodeClick': [
1038
+ {
1039
+ guard: and([
1040
+ 'click: focused node',
1041
+ 'click: node has modelFqn',
1042
+ ]) as any,
1043
+ actions: [
1044
+ 'open element details',
1045
+ 'emit: nodeClick',
1046
+ ],
1047
+ },
955
1048
  {
956
1049
  guard: 'click: focused node',
957
1050
  actions: 'emit: nodeClick',
@@ -959,9 +1052,7 @@ const _diagramMachine = setup({
959
1052
  },
960
1053
  {
961
1054
  actions: [
962
- assign({
963
- lastClickedNode,
964
- }),
1055
+ 'assign lastClickedNode',
965
1056
  raise(({ event }) => ({
966
1057
  type: 'focus.node',
967
1058
  nodeId: event.node.id as NodeId,
@@ -972,9 +1063,7 @@ const _diagramMachine = setup({
972
1063
  ],
973
1064
  'focus.node': {
974
1065
  actions: [
975
- assign({
976
- focusedNode: ({ event }) => event.nodeId,
977
- }),
1066
+ 'assign: focusedNode',
978
1067
  'focus on nodes and edges',
979
1068
  'open source of focused or last clicked node',
980
1069
  'xyflow:fitFocusedBounds',
@@ -1016,6 +1105,34 @@ const _diagramMachine = setup({
1016
1105
  'xyflow:fitFocusedBounds',
1017
1106
  'emit: walkthroughStarted',
1018
1107
  ],
1108
+ exit: enqueueActions(({ enqueue, context, event }) => {
1109
+ enqueue.stopChild('hotkey')
1110
+ if (context.viewportBeforeFocus) {
1111
+ enqueue({ type: 'xyflow:setViewport', params: { viewport: context.viewportBeforeFocus } })
1112
+ } else {
1113
+ enqueue('xyflow:fitDiagram')
1114
+ }
1115
+ // Disable parallel areas highlight
1116
+ if (context.dynamicViewVariant === 'sequence' && context.activeWalkthrough?.parallelPrefix) {
1117
+ enqueue.assign({
1118
+ xynodes: context.xynodes.map(n => {
1119
+ if (n.type === 'seq-parallel') {
1120
+ return Base.setData(n, {
1121
+ color: SeqParallelAreaColor.default,
1122
+ })
1123
+ }
1124
+ return n
1125
+ }),
1126
+ })
1127
+ }
1128
+ enqueue('undim everything')
1129
+ enqueue.assign({
1130
+ activeWalkthrough: null,
1131
+ viewportBeforeFocus: null,
1132
+ })
1133
+
1134
+ enqueue('emit: walkthroughStopped')
1135
+ }),
1019
1136
  on: {
1020
1137
  'key.esc': {
1021
1138
  target: 'idle',
@@ -1089,20 +1206,6 @@ const _diagramMachine = setup({
1089
1206
  target: 'idle',
1090
1207
  },
1091
1208
  },
1092
- exit: enqueueActions(({ enqueue, context, event }) => {
1093
- enqueue.stopChild('hotkey')
1094
- if (context.viewportBeforeFocus) {
1095
- enqueue({ type: 'xyflow:setViewport', params: { viewport: context.viewportBeforeFocus } })
1096
- } else {
1097
- enqueue('xyflow:fitDiagram')
1098
- }
1099
- enqueue('undim everything')
1100
- enqueue.assign({
1101
- activeWalkthrough: null,
1102
- viewportBeforeFocus: null,
1103
- })
1104
- enqueue('emit: walkthroughStopped')
1105
- }),
1106
1209
  },
1107
1210
  },
1108
1211
  },
@@ -1123,10 +1226,10 @@ const _diagramMachine = setup({
1123
1226
  enqueue({
1124
1227
  type: 'xyflow:alignNodeFromToAfterNavigate',
1125
1228
  params: {
1126
- fromNode: fromNode.id,
1229
+ fromNode: fromNode.id as NodeId,
1127
1230
  toPosition: {
1128
- x: toNode.position[0],
1129
- y: toNode.position[1],
1231
+ x: toNode.data.position[0],
1232
+ y: toNode.data.position[1],
1130
1233
  },
1131
1234
  },
1132
1235
  })
@@ -1208,10 +1311,10 @@ const _diagramMachine = setup({
1208
1311
  enqueue({
1209
1312
  type: 'xyflow:alignNodeFromToAfterNavigate',
1210
1313
  params: {
1211
- fromNode: fromNode.id,
1314
+ fromNode: fromNode.id as NodeId,
1212
1315
  toPosition: {
1213
- x: toNode.position[0],
1214
- y: toNode.position[1],
1316
+ x: toNode.data.position[0],
1317
+ y: toNode.data.position[1],
1215
1318
  },
1216
1319
  },
1217
1320
  })
@@ -1223,6 +1326,19 @@ const _diagramMachine = setup({
1223
1326
  })
1224
1327
  enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
1225
1328
  }
1329
+ } else {
1330
+ // Check if dynamic view mode changed
1331
+ const nextView = event.view
1332
+ if (
1333
+ nextView._type === 'dynamic' &&
1334
+ context.view._type === 'dynamic' &&
1335
+ nextView.variant !== context.view.variant
1336
+ ) {
1337
+ enqueue({
1338
+ type: 'xyflow:setViewportCenter',
1339
+ params: BBox.center(nextView.bounds),
1340
+ })
1341
+ }
1226
1342
  }
1227
1343
  enqueue.assign({
1228
1344
  ...mergeXYNodesEdges({ context, event }),
@@ -1249,6 +1365,9 @@ const _diagramMachine = setup({
1249
1365
  'ensure search actor state',
1250
1366
  ],
1251
1367
  },
1368
+ 'switch.dynamicViewVariant': {
1369
+ actions: 'assign: dynamicViewVariant',
1370
+ },
1252
1371
  },
1253
1372
  exit: [
1254
1373
  'stopSyncLayout',
@@ -1270,14 +1389,27 @@ const _diagramMachine = setup({
1270
1389
  ],
1271
1390
  })
1272
1391
 
1273
- function nodeRef(node: DiagramNode) {
1274
- return node.modelRef ?? node.deploymentRef
1392
+ function nodeRef(node: Types.Node) {
1393
+ switch (node.type) {
1394
+ case 'element':
1395
+ case 'compound-element':
1396
+ case 'seq-actor':
1397
+ return node.data.modelFqn
1398
+ case 'deployment':
1399
+ case 'compound-deployment':
1400
+ return node.data.modelFqn ?? node.data.deploymentFqn
1401
+ case 'seq-parallel':
1402
+ case 'view-group':
1403
+ return null
1404
+ default:
1405
+ nonexhaustive(node)
1406
+ }
1275
1407
  }
1276
- function findCorrespondingNode(context: Context, event: { view: DiagramView }) {
1408
+ function findCorrespondingNode(context: Context, event: { view: DiagramView; xynodes: Types.Node[] }) {
1277
1409
  const fromNodeId = context.lastOnNavigate?.fromNode
1278
- const fromNode = fromNodeId && context.view.nodes.find(n => n.id === fromNodeId)
1410
+ const fromNode = fromNodeId && context.xynodes.find(n => n.id === fromNodeId)
1279
1411
  const fromRef = fromNode && nodeRef(fromNode)
1280
- const toNode = fromRef && event.view.nodes.find(n => nodeRef(n) === fromRef)
1412
+ const toNode = fromRef && event.xynodes.find(n => nodeRef(n) === fromRef)
1281
1413
  return { fromNode, toNode }
1282
1414
  }
1283
1415
 
@@ -1,5 +1,9 @@
1
- import type { BBox, DiagramEdge, DiagramNode, XYPoint } from '@likec4/core/types'
1
+ import { type DiagramEdge, type DiagramNode, type XYPoint, BBox } from '@likec4/core/types'
2
+ import { nonNullable } from '@likec4/core/utils'
3
+ import { getEdgePosition, getNodeDimensions, getNodesBounds } from '@xyflow/system'
2
4
  import type { ActorSystem } from 'xstate'
5
+ import type { XYStoreState } from '../hooks'
6
+ import type { Types } from '../likec4diagram/types'
3
7
  import type { OverlaysActorRef } from '../overlays/overlaysActor'
4
8
  import type { SearchActorRef } from '../search/searchActor'
5
9
  import type { Context } from './diagram-machine'
@@ -47,12 +51,12 @@ export function focusedBounds(params: { context: Context }): { bounds: BBox; dur
47
51
 
48
52
  const width = node.measured?.width ?? node.width ?? node.initialWidth
49
53
  const height = node.measured?.height ?? node.height ?? node.initialHeight
50
- return {
51
- minX: Math.min(acc.minX, position.x),
52
- minY: Math.min(acc.minY, position.y),
53
- maxX: Math.max(acc.maxX, position.x + width),
54
- maxY: Math.max(acc.maxY, position.y + height),
55
- }
54
+
55
+ acc.minX = Math.min(acc.minX, position.x)
56
+ acc.minY = Math.min(acc.minY, position.y)
57
+ acc.maxX = Math.max(acc.maxX, position.x + width)
58
+ acc.maxY = Math.max(acc.maxY, position.y + height)
59
+ return acc
56
60
  }, {
57
61
  minX: Infinity,
58
62
  minY: Infinity,
@@ -76,3 +80,73 @@ export function focusedBounds(params: { context: Context }): { bounds: BBox; dur
76
80
  },
77
81
  }
78
82
  }
83
+
84
+ const MARGIN = 32
85
+ export function activeSequenceBounds(params: { context: Context }): { bounds: BBox; duration?: number } {
86
+ const activeWalkthrough = nonNullable(params.context.activeWalkthrough)
87
+
88
+ const stepEdge = nonNullable(params.context.xyedges.find(e => e.id === activeWalkthrough.stepId))
89
+ const xystate = params.context.xystore.getState()
90
+
91
+ const sourceNode = nonNullable(xystate.nodeLookup.get(stepEdge.source))
92
+ const targetNode = nonNullable(xystate.nodeLookup.get(stepEdge.target))
93
+
94
+ const actorsBounds = getNodesBounds([sourceNode, targetNode], xystate)
95
+
96
+ let stepBounds: BBox | undefined | null
97
+ if (activeWalkthrough.parallelPrefix) {
98
+ const parallelArea = params.context.xynodes.find(n =>
99
+ n.type === 'seq-parallel' && n.data.parallelPrefix === activeWalkthrough.parallelPrefix
100
+ )
101
+ if (parallelArea) {
102
+ stepBounds = {
103
+ x: parallelArea.position.x,
104
+ y: parallelArea.position.y,
105
+ ...getNodeDimensions(parallelArea),
106
+ }
107
+ }
108
+ }
109
+
110
+ stepBounds ??= getEdgeBounds(stepEdge, xystate)
111
+
112
+ if (stepBounds) {
113
+ stepBounds = BBox.merge(stepBounds, actorsBounds)
114
+ } else {
115
+ stepBounds = actorsBounds
116
+ }
117
+
118
+ return {
119
+ duration: 350,
120
+ bounds: BBox.expand(
121
+ stepBounds,
122
+ MARGIN,
123
+ ),
124
+ }
125
+ }
126
+
127
+ function getEdgeBounds(edge: Types.Edge, store: XYStoreState): BBox | null {
128
+ const sourceNode = store.nodeLookup.get(edge.source)
129
+ const targetNode = store.nodeLookup.get(edge.target)
130
+
131
+ if (!sourceNode || !targetNode) {
132
+ return null
133
+ }
134
+
135
+ const edgePosition = getEdgePosition({
136
+ id: edge.id,
137
+ sourceNode,
138
+ targetNode,
139
+ sourceHandle: edge.sourceHandle || null,
140
+ targetHandle: edge.targetHandle || null,
141
+ connectionMode: store.connectionMode,
142
+ })
143
+
144
+ if (!edgePosition) {
145
+ return null
146
+ }
147
+
148
+ return BBox.fromPoints([
149
+ [edgePosition.sourceX, edgePosition.sourceY],
150
+ [edgePosition.targetX, edgePosition.targetY],
151
+ ])
152
+ }
@@ -1,12 +0,0 @@
1
- import { DiagramView, WhereOperator } from '@likec4/core';
2
- import { Types } from './types';
3
- type ViewToNodesEdgeInput = {
4
- view: DiagramView;
5
- where: WhereOperator | null;
6
- nodesSelectable: boolean;
7
- };
8
- export declare function useViewToNodesEdges(params: ViewToNodesEdgeInput): {
9
- xynodes: Types.Node[];
10
- xyedges: Types.Edge[];
11
- };
12
- export {};