@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
@@ -5,9 +5,9 @@ import { shallowEqual, useCallbackRef } from '@mantine/hooks'
5
5
  import { IconX } from '@tabler/icons-react'
6
6
  import { memo, useState } from 'react'
7
7
  import { isBoolean } from 'remeda'
8
+ import { FitViewPaddings } from '../base/const'
8
9
  import { FramerMotionConfig } from '../context/FramerMotionConfig'
9
10
  import { LikeC4Diagram } from '../LikeC4Diagram'
10
- import type { ViewPadding } from '../LikeC4Diagram.props'
11
11
  import { useLikeC4Model } from '../likec4model/useLikeC4Model'
12
12
  import { Overlay } from '../overlays/overlay/Overlay'
13
13
  import type { LikeC4ViewProps } from './LikeC4View.props'
@@ -37,13 +37,6 @@ export function LikeC4View<A extends t.aux.Any = t.aux.UnknownLayouted>({
37
37
  return <LikeC4ViewInner view={view.$view} {...props} />
38
38
  }
39
39
 
40
- const DEFAULT_BROWSER_PADDING: ViewPadding = {
41
- top: '50px',
42
- bottom: '16px',
43
- left: '16px',
44
- right: '16px',
45
- }
46
-
47
40
  type LikeC4ViewInnerProps<A extends t.aux.Any> = Omit<LikeC4ViewProps<A>, 'viewId'> & {
48
41
  view: t.DiagramView<A>
49
42
  }
@@ -56,8 +49,6 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
56
49
  colorScheme: explicitColorScheme,
57
50
  injectFontCss = true,
58
51
  controls = false,
59
- fitView = true,
60
- fitViewPadding = '16px',
61
52
  background = 'transparent',
62
53
  browser = true,
63
54
  showDiagramTitle = false,
@@ -129,8 +120,8 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
129
120
  pannable={pannable}
130
121
  zoomable={zoomable}
131
122
  background={background}
132
- fitView={fitView}
133
- fitViewPadding={fitViewPadding}
123
+ fitView={true}
124
+ fitViewPadding={FitViewPaddings.default}
134
125
  showDiagramTitle={showDiagramTitle}
135
126
  showNotations={showNotations && hasNotations}
136
127
  enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
@@ -152,7 +143,6 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
152
143
  // so we don't want enable search and hotkeys
153
144
  enableSearch={false}
154
145
  {...isBrowserEnabled && {
155
- onNavigateTo: onNavigateTo,
156
146
  onCanvasClick: onNavigateToThisView,
157
147
  onNodeClick: onNavigateToThisView,
158
148
  }}
@@ -176,8 +166,8 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
176
166
  controls="next"
177
167
  readonly
178
168
  fitView
179
- fitViewPadding={DEFAULT_BROWSER_PADDING}
180
169
  {...props}
170
+ fitViewPadding={FitViewPaddings.withControls}
181
171
  {...browserProps}
182
172
  showNotations={(browserProps.showNotations ?? true) &&
183
173
  (browserView.notation?.nodes.length ?? 0) > 0}
@@ -1,5 +1,4 @@
1
1
  import type * as t from '@likec4/core/types'
2
- import type { DiagramView } from '@likec4/core/types'
3
2
  import { cx } from '@likec4/styles/css'
4
3
  import { type CSSProperties } from 'react'
5
4
  import { isFunction, isString } from 'remeda'
@@ -65,7 +64,7 @@ export function ReactLikeC4<A extends t.aux.Any = t.aux.UnknownLayouted>({
65
64
  }
66
65
 
67
66
  type ReactLikeC4InnerProps<A extends t.aux.Any> = Omit<ReactLikeC4Props<A>, 'viewId'> & {
68
- view: DiagramView<A>
67
+ view: t.DiagramView<A>
69
68
  }
70
69
  function ReactLikeC4Inner<A extends t.aux.Any>({
71
70
  className,
@@ -266,10 +266,17 @@ export type CustomViewGroupNodeProps = {
266
266
  nodeModel: NodeModel.IsGroup<Any>
267
267
  }
268
268
 
269
+ export const sequenceActorNode: CustomNodeComponent<CustomSequenceActorNodeProps> = customDiagramNode as any
270
+ export type CustomSequenceActorNodeProps = {
271
+ nodeProps: NodeProps<Types.SequenceActorNodeData, 'seq-actor'>
272
+ nodeModel: NodeModel.WithElement<Any>
273
+ }
274
+
269
275
  export interface CustomNodes {
270
276
  element?: FunctionComponent<NodeProps<Types.ElementNodeData, 'element'>>
271
277
  deployment?: FunctionComponent<NodeProps<Types.DeploymentElementNodeData, 'deployment'>>
272
278
  compoundElement?: FunctionComponent<NodeProps<Types.CompoundElementNodeData, 'compound-element'>>
273
279
  compoundDeployment?: FunctionComponent<NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>>
274
280
  viewGroup?: FunctionComponent<NodeProps<Types.ViewGroupNodeData, 'view-group'>>
281
+ sequenceActor?: FunctionComponent<NodeProps<Types.SequenceActorNodeData, 'seq-actor'>>
275
282
  }
@@ -3,6 +3,7 @@ import {
3
3
  type DiagramEdge,
4
4
  type DiagramNode,
5
5
  type DiagramView,
6
+ type DynamicViewDisplayVariant,
6
7
  type NodeId,
7
8
  type NodeNotation as ElementNotation,
8
9
  type scalar,
@@ -115,6 +116,11 @@ export interface DiagramApi {
115
116
  highlightNotation(notation: ElementNotation, kind?: string): void
116
117
  unhighlightNotation(): void
117
118
  openSearch(searchValue?: string): void
119
+
120
+ /**
121
+ * Switch dynamic view display variant
122
+ */
123
+ switchDynamicViewVariant(variant: DynamicViewDisplayVariant): void
118
124
  }
119
125
 
120
126
  /**
@@ -246,6 +252,10 @@ export function useDiagram(): DiagramApi {
246
252
  openSearch: (searchValue?: string) => {
247
253
  actor.send({ type: 'open.search', ...(searchValue && { search: searchValue }) })
248
254
  },
255
+
256
+ switchDynamicViewVariant: (variant: DynamicViewDisplayVariant) => {
257
+ actor.send({ type: 'switch.dynamicViewVariant', variant })
258
+ },
249
259
  }), [actor])
250
260
  }
251
261
 
@@ -32,7 +32,7 @@ export const DiagramUI = memo(() => {
32
32
  : <Controls />}
33
33
  {overlaysActorRef && <Overlays overlaysActorRef={overlaysActorRef} />}
34
34
  {enableNotations && <NotationPanel />}
35
- {enableControls !== 'next' && (
35
+ {enableControls === true && (
36
36
  <>
37
37
  {enableViewTitle && <DiagramTitlePanel />}
38
38
  {enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
@@ -3,7 +3,7 @@ import { cx } from '@likec4/styles/css'
3
3
  import { useCallbackRef, useDebouncedCallback, useTimeout } from '@mantine/hooks'
4
4
  import { useCustomCompareMemo } from '@react-hookz/web'
5
5
  import type { OnMove, OnMoveEnd } from '@xyflow/system'
6
- import { shallowEqual } from 'fast-equals'
6
+ import { deepEqual, shallowEqual } from 'fast-equals'
7
7
  import { type PropsWithChildren } from 'react'
8
8
  import type { Simplify } from 'type-fest'
9
9
  import { BaseXYFlow } from '../base/BaseXYFlow'
@@ -13,7 +13,7 @@ import { useUpdateEffect } from '../hooks'
13
13
  import { useDiagram, useDiagramContext } from '../hooks/useDiagram'
14
14
  import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
15
15
  import type { DiagramContext } from '../state/types'
16
- import { BuiltinNodes as defaultNodeTypes, edgeTypes } from './custom'
16
+ import { BuiltinNodes as defaultNodeTypes, edgeTypes, SequenceParallelArea } from './custom'
17
17
  import { DiagramUI } from './DiagramUI'
18
18
  import type { Types } from './types'
19
19
  import { useLayoutConstraints } from './useLayoutConstraints'
@@ -26,7 +26,9 @@ const selectXYProps = (ctx: DiagramContext) => ({
26
26
  zoomable: ctx.zoomable,
27
27
  fitViewPadding: ctx.fitViewPadding,
28
28
  enableFitView: ctx.features.enableFitView,
29
- enableReadOnly: ctx.features.enableReadOnly || ctx.toggledFeatures.enableReadOnly,
29
+ enableReadOnly: ctx.features.enableReadOnly || ctx.toggledFeatures.enableReadOnly
30
+ // if dynamic view display mode is sequence, enable readonly
31
+ || (ctx.dynamicViewVariant === 'sequence' && ctx.view._type === 'dynamic'),
30
32
  ...(!ctx.features.enableFitView && {
31
33
  viewport: {
32
34
  x: -Math.min(ctx.view.bounds.x, 0),
@@ -41,7 +43,7 @@ const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof
41
43
  a.zoomable === b.zoomable &&
42
44
  a.enableFitView === b.enableFitView &&
43
45
  a.enableReadOnly === b.enableReadOnly &&
44
- shallowEqual(a.fitViewPadding, b.fitViewPadding) &&
46
+ deepEqual(a.fitViewPadding, b.fitViewPadding) &&
45
47
  shallowEqual(a.nodes, b.nodes) &&
46
48
  shallowEqual(a.edges, b.edges) &&
47
49
  shallowEqual(a.viewport ?? null, b.viewport ?? null)
@@ -123,7 +125,9 @@ export function LikeC4DiagramXYFlow({
123
125
  'compound-element': renderNodes?.compoundElement ?? defaultNodeTypes.compoundElement,
124
126
  'compound-deployment': renderNodes?.compoundDeployment ?? defaultNodeTypes.compoundDeployment,
125
127
  'view-group': renderNodes?.viewGroup ?? defaultNodeTypes.viewGroup,
126
- }
128
+ 'seq-actor': renderNodes?.sequenceActor ?? defaultNodeTypes.sequenceActor,
129
+ 'seq-parallel': SequenceParallelArea,
130
+ } satisfies Record<Types.Node['type'], any>
127
131
  },
128
132
  [renderNodes],
129
133
  shallowEqual,
@@ -224,6 +228,7 @@ export function LikeC4DiagramXYFlow({
224
228
  }}
225
229
  nodesDraggable={notReadOnly && nodesDraggable}
226
230
  nodesSelectable={nodesSelectable}
231
+ edgesFocusable={false}
227
232
  {...(notReadOnly && nodesDraggable && layoutConstraints)}
228
233
  {...props}
229
234
  {...reactFlowProps}>
@@ -0,0 +1,45 @@
1
+ import type { DynamicViewDisplayVariant, LayoutedView, WhereOperator } from '@likec4/core/types'
2
+ import type { Types } from './types'
3
+ import { diagramToXY } from './xyflow-diagram/diagram-view'
4
+ import { sequenceViewToXY } from './xyflow-sequence/sequence-view'
5
+
6
+ type ConvertToXYFlowInput = {
7
+ view: LayoutedView
8
+ where: WhereOperator | null
9
+ nodesSelectable: boolean
10
+ dynamicViewVariant: DynamicViewDisplayVariant
11
+ }
12
+
13
+ export function convertToXYFlow({ dynamicViewVariant, ...params }: ConvertToXYFlowInput): {
14
+ view: LayoutedView
15
+ xynodes: Types.Node[]
16
+ xyedges: Types.Edge[]
17
+ } {
18
+ const view = params.view
19
+ const isDynamic = view._type === 'dynamic'
20
+
21
+ const { bounds, xynodes, xyedges } = isDynamic && dynamicViewVariant === 'sequence'
22
+ ? sequenceViewToXY(view)
23
+ : diagramToXY(params)
24
+
25
+ if (isDynamic && view.variant !== dynamicViewVariant) {
26
+ return {
27
+ view: {
28
+ ...view,
29
+ bounds,
30
+ variant: dynamicViewVariant,
31
+ },
32
+ xynodes,
33
+ xyedges,
34
+ }
35
+ }
36
+
37
+ return {
38
+ view: bounds === view.bounds ? view : {
39
+ ...view,
40
+ bounds,
41
+ },
42
+ xynodes,
43
+ xyedges,
44
+ }
45
+ }
@@ -329,7 +329,6 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
329
329
  <EdgeLabel edgeProps={props}>
330
330
  {navigateTo && (
331
331
  <EdgeActionButton
332
- {...props}
333
332
  onClick={e => {
334
333
  e.stopPropagation()
335
334
  diagram.navigateTo(navigateTo)
@@ -0,0 +1,65 @@
1
+ import { getSmoothStepPath } from '@xyflow/system'
2
+ import type { EdgeProps } from '../../../base'
3
+ import { EdgeActionButton, EdgeContainer, EdgeLabel, EdgeLabelContainer, EdgePath } from '../../../base/primitives'
4
+ import { useDiagram } from '../../../hooks/useDiagram'
5
+ import type { Types } from '../../types'
6
+
7
+ const LABEL_OFFSET = 16
8
+ export function SequenceStepEdge(props: EdgeProps<Types.SequenceStepEdgeData>) {
9
+ const diagram = useDiagram()
10
+ const { navigateTo } = props.data
11
+ const isSelfLoop = props.source === props.target
12
+ const isBack = props.sourceX > props.targetX
13
+ const [path] = getSmoothStepPath({
14
+ sourceX: props.sourceX,
15
+ sourceY: props.sourceY,
16
+ sourcePosition: props.sourcePosition,
17
+ targetX: props.targetX,
18
+ targetY: props.targetY,
19
+ targetPosition: props.targetPosition,
20
+ ...(isSelfLoop && {
21
+ offset: 30,
22
+ borderRadius: 16,
23
+ }),
24
+ })
25
+
26
+ let labelX = props.sourceX
27
+ switch (true) {
28
+ case isSelfLoop:
29
+ labelX = props.sourceX + 24 + LABEL_OFFSET
30
+ break
31
+ case isBack:
32
+ labelX = props.sourceX - LABEL_OFFSET
33
+ break
34
+ default:
35
+ labelX = props.sourceX + LABEL_OFFSET
36
+ break
37
+ }
38
+
39
+ return (
40
+ <EdgeContainer {...props}>
41
+ <EdgePath
42
+ edgeProps={props}
43
+ svgPath={path}
44
+ />
45
+ <EdgeLabelContainer
46
+ edgeProps={props}
47
+ labelPosition={{
48
+ x: labelX,
49
+ y: props.sourceY + (!isSelfLoop ? LABEL_OFFSET : 0),
50
+ translate: isBack ? 'translate(-100%, 0)' : undefined,
51
+ }}
52
+ >
53
+ <EdgeLabel edgeProps={props}>
54
+ {navigateTo && (
55
+ <EdgeActionButton
56
+ onClick={e => {
57
+ e.stopPropagation()
58
+ diagram.navigateTo(navigateTo)
59
+ }} />
60
+ )}
61
+ </EdgeLabel>
62
+ </EdgeLabelContainer>
63
+ </EdgeContainer>
64
+ )
65
+ }
@@ -1,13 +1,15 @@
1
1
  import type { CustomNodes } from '../../custom/customNodes'
2
2
  import type { Types } from '../types'
3
3
  import { RelationshipEdge } from './edges/RelationshipEdge'
4
+ import { SequenceStepEdge } from './edges/SequenceStepEdge'
4
5
  import {
5
6
  CompoundDeploymentNode,
6
7
  CompoundElementNode,
7
8
  DeploymentNode,
8
9
  ElementNode,
10
+ SequenceActorNode,
9
11
  ViewGroupNode,
10
- } from './nodes/nodes'
12
+ } from './nodes'
11
13
 
12
14
  export const BuiltinNodes = {
13
15
  element: ElementNode,
@@ -15,8 +17,12 @@ export const BuiltinNodes = {
15
17
  compoundElement: CompoundElementNode,
16
18
  compoundDeployment: CompoundDeploymentNode,
17
19
  viewGroup: ViewGroupNode,
20
+ sequenceActor: SequenceActorNode,
18
21
  } satisfies Required<CustomNodes>
19
22
 
23
+ export { SequenceParallelArea } from './nodes/SequenceActorNode'
24
+
20
25
  export const edgeTypes = {
21
26
  relationship: RelationshipEdge,
27
+ 'seq-step': SequenceStepEdge,
22
28
  } satisfies { [key in Types.Edge['type']]: any }
@@ -1,4 +1,4 @@
1
- import type { NodeId } from '@likec4/core'
1
+ import type { NodeId } from '@likec4/core/types'
2
2
  import { IconTransform, IconZoomScan } from '@tabler/icons-react'
3
3
  import { useMemo } from 'react'
4
4
  import { hasAtLeast } from 'remeda'
@@ -8,6 +8,8 @@ import { useEnabledFeatures } from '../../../context/DiagramFeatures'
8
8
  import { useDiagram } from '../../../hooks/useDiagram'
9
9
  import type { Types } from '../../types'
10
10
 
11
+ type RequiredData = Pick<Types.ElementNodeData, 'modelFqn' | 'navigateTo'>
12
+
11
13
  type WithExtraButtons = {
12
14
  /**
13
15
  * Add extra action buttons
@@ -31,7 +33,7 @@ type WithExtraButtons = {
31
33
  extraButtons?: ElementActionButtons.Item[]
32
34
  }
33
35
 
34
- export type ElementActionsProps = NodeProps<Types.ElementNodeData> & WithExtraButtons
36
+ export type ElementActionsProps = NodeProps<RequiredData> & WithExtraButtons
35
37
 
36
38
  export const ElementActions = ({
37
39
  extraButtons,
@@ -0,0 +1,175 @@
1
+ import { defaultTheme } from '@likec4/core'
2
+ import { css } from '@likec4/styles/css'
3
+ import { Box } from '@likec4/styles/jsx'
4
+ import type { NodeProps } from '@xyflow/react'
5
+ import { Handle } from '@xyflow/react'
6
+ import { Position } from '@xyflow/system'
7
+ import { ElementNodeContainer, ElementShape, ElementTitle } from '../../../custom'
8
+ import { type Types } from '../../types'
9
+ import { ElementActions } from './ElementActions'
10
+ import { ElementDetailsButtonWithHandler } from './nodes'
11
+
12
+ const positionMap: Record<Types.SequenceActorNodePort['position'], Position> = {
13
+ left: Position.Left,
14
+ right: Position.Right,
15
+ top: Position.Top,
16
+ bottom: Position.Bottom,
17
+ }
18
+
19
+ const ActorStepPort = ({
20
+ data,
21
+ port: p,
22
+ }: {
23
+ data: Types.SequenceActorNodeData
24
+ port: Types.SequenceActorNodePort
25
+ }) => {
26
+ return (
27
+ <>
28
+ <Box
29
+ data-likec4-color={data.color}
30
+ className={css({
31
+ position: 'absolute',
32
+ backgroundColor: 'var(--likec4-palette-fill)',
33
+ rounded: 'xs',
34
+ width: {
35
+ base: '5px',
36
+ _whenHovered: '7px',
37
+ _whenSelected: '7px',
38
+ },
39
+ // pointerEvents: 'none',
40
+ transition: 'fast',
41
+ translateX: '-1/2',
42
+ translateY: '-1/2',
43
+ translate: 'auto',
44
+ // transform: 'translate',
45
+ })}
46
+ style={{
47
+ top: p.cy,
48
+ left: p.cx,
49
+ height: p.height,
50
+ // transform: p.type === 'source' ? 'translate(-50%, -16px)' : 'translate(-50%, -50%)',
51
+ // transform: 'translate(-50%, -50%)',
52
+ // zIndex: p.type === 'source' ? 1 : 0,
53
+ }}
54
+ />
55
+ <Handle
56
+ id={p.id}
57
+ type={p.type}
58
+ position={positionMap[p.position]}
59
+ style={{
60
+ top: p.cy - 3,
61
+ left: p.cx - 3,
62
+ width: 6,
63
+ height: 6,
64
+ right: 'unset',
65
+ bottom: 'unset',
66
+ visibility: 'hidden',
67
+ transform: p.position === 'left' ? 'translate(-150%, 0)' : 'translate(100%, 0)',
68
+ }} />
69
+ </>
70
+ )
71
+ }
72
+
73
+ export function SequenceActorNode(props: NodeProps<Types.SequenceActorNode>) {
74
+ const data = props.data
75
+ const {
76
+ positionAbsoluteY,
77
+ data: {
78
+ viewHeight,
79
+ hovered: isHovered = false,
80
+ ports,
81
+ },
82
+ } = props
83
+
84
+ return (
85
+ <>
86
+ <Box
87
+ className={css({
88
+ position: 'absolute',
89
+ rounded: 'xs',
90
+ top: '1',
91
+ pointerEvents: 'none',
92
+ transition: 'fast',
93
+ translateX: '-1/2',
94
+ translate: 'auto',
95
+ // transform: 'translate',
96
+ })}
97
+ style={{
98
+ // position: 'absolute',
99
+ backgroundColor: defaultTheme.elements.gray.fill,
100
+ opacity: isHovered ? 0.5 : 0.4,
101
+ left: '50%',
102
+ width: isHovered ? 3 : 2,
103
+ height: viewHeight - positionAbsoluteY,
104
+ zIndex: -1,
105
+ pointerEvents: 'none',
106
+ // top: p.y,
107
+ // left: p.width / 2,
108
+ // height: p.type === 'source' ? 48 : 32,
109
+ // transform: p.type === 'source' ? 'translate(-50%, -16px)' : 'translate(-50%, -50%)',
110
+ // transform: 'translate(-50%, -50%)',
111
+ // zIndex: p.type === 'source' ? 1 : 0,
112
+ }}
113
+ />
114
+ {
115
+ /* <div
116
+ // css={{
117
+ // backgroundColor: 'var(--likec4-palette-fill)',
118
+ // }}
119
+ style={{
120
+ position: 'absolute',
121
+ backgroundColor: defaultTheme.elements.gray.fill,
122
+ opacity: 0.5,
123
+ top: 8,
124
+ left: 'calc(50% - 1px)',
125
+ width: 2,
126
+ height: viewHeight - positionAbsoluteY,
127
+ zIndex: -1,
128
+ pointerEvents: 'none',
129
+ }}>
130
+ </div> */
131
+ }
132
+ <ElementNodeContainer nodeProps={props}>
133
+ <ElementShape {...props} />
134
+ <ElementTitle {...props} />
135
+ {data.modelFqn && (
136
+ <>
137
+ <ElementActions {...props} data={data as Types.ElementNodeData} />
138
+ <ElementDetailsButtonWithHandler {...props} data={data as Types.ElementNodeData} />
139
+ </>
140
+ )}
141
+ </ElementNodeContainer>
142
+ {ports.map(p => <ActorStepPort key={p.id} port={p} data={props.data} />)}
143
+ </>
144
+ )
145
+ }
146
+
147
+ export function SequenceParallelArea(props: NodeProps<Types.SequenceActorNode>) {
148
+ return (
149
+ <Box
150
+ data-likec4-color={props.data.color}
151
+ css={{
152
+ width: '100%',
153
+ height: '100%',
154
+ border: 'default',
155
+ rounded: 'sm',
156
+ borderWidth: 1,
157
+ '--_color': {
158
+ base: 'var(--likec4-palette-stroke)',
159
+ _dark: '[color-mix(in srgb, var(--likec4-palette-hiContrast) 40%, var(--likec4-palette-fill))]',
160
+ },
161
+ borderColor: '[var(--_color)/30]',
162
+ backgroundColor: 'var(--likec4-palette-fill)/15',
163
+ pointerEvents: 'none',
164
+ paddingLeft: '2',
165
+ paddingTop: '0.5',
166
+ fontSize: 'xs',
167
+ fontWeight: 'bold',
168
+ letterSpacing: '.75px',
169
+ color: '[var(--_color)/75]',
170
+ }}
171
+ >
172
+ PARALLEL
173
+ </Box>
174
+ )
175
+ }
@@ -9,6 +9,7 @@ export {
9
9
  ElementNode,
10
10
  ViewGroupNode,
11
11
  } from './nodes'
12
+ export { SequenceActorNode, SequenceParallelArea } from './SequenceActorNode'
12
13
 
13
14
  export * from './CompoundActions'
14
15
  export * from './ElementActions'
@@ -20,7 +20,7 @@ import { DeploymentElementActions, ElementActions } from './ElementActions'
20
20
  import { CompoundDeploymentToolbar, CompoundElementToolbar } from './toolbar/CompoundToolbar'
21
21
  import { DeploymentElementToolbar, ElementToolbar } from './toolbar/ElementToolbar'
22
22
 
23
- type CustomNodes = Required<CustomNodesType>
23
+ export type CustomNodes = Required<CustomNodesType>
24
24
 
25
25
  export function ElementDetailsButtonWithHandler(
26
26
  props: NodeProps<Types.ElementNodeData | Types.DeploymentElementNodeData>,
@@ -29,7 +29,7 @@ import {
29
29
  useRef,
30
30
  useState,
31
31
  } from 'react'
32
- import { clamp, filter, isTruthy, map, partition, pipe } from 'remeda'
32
+ import { clamp, filter, isEmpty, isTruthy, map, partition, pipe } from 'remeda'
33
33
  import { Link } from '../../components/Link'
34
34
  import { useDiagramEventHandlers } from '../../context/DiagramEventHandlers'
35
35
  import { useEnabledFeatures } from '../../context/DiagramFeatures'
@@ -61,6 +61,7 @@ function selectDiagramContext(c: DiagramContext) {
61
61
  }
62
62
 
63
63
  export const RelationshipPopover = memo(() => {
64
+ const likec4model = useLikeC4Model()
64
65
  const actorRef = useActorRef(RelationshipPopoverActorLogic)
65
66
  const diagram = useDiagram()
66
67
  const { viewId, selected } = useDiagramContext(selectDiagramContext)
@@ -132,13 +133,42 @@ export const RelationshipPopover = memo(() => {
132
133
  [openedEdgeId],
133
134
  )
134
135
 
135
- if (!diagramEdge || !sourceNode || !targetNode) {
136
+ if (!diagramEdge || !sourceNode || !targetNode || isEmpty(diagramEdge.relations)) {
137
+ return null
138
+ }
139
+
140
+ const [direct, nested] = pipe(
141
+ diagramEdge.relations,
142
+ map(id => {
143
+ try {
144
+ return likec4model.relationship(id)
145
+ } catch (e) {
146
+ // View was cached, but likec4model based on new data
147
+ console.error(
148
+ `View is cached and likec4model missing relationship ${id} from ${sourceNode.id} -> ${targetNode.id}`,
149
+ e,
150
+ )
151
+ return null
152
+ }
153
+ }),
154
+ filter(isTruthy),
155
+ partition(r => r.source.id === sourceNode.id && r.target.id === targetNode.id),
156
+ )
157
+
158
+ if (direct.length === 0 && nested.length === 0) {
159
+ console.warn('No relationships found diagram edge', {
160
+ diagramEdge,
161
+ sourceNode,
162
+ targetNode,
163
+ })
136
164
  return null
137
165
  }
138
166
 
139
167
  return (
140
168
  <RelationshipPopoverInternal
141
169
  viewId={viewId}
170
+ direct={direct}
171
+ nested={nested}
142
172
  diagramEdge={diagramEdge}
143
173
  sourceNode={sourceNode}
144
174
  targetNode={targetNode}
@@ -153,6 +183,8 @@ const getEdgeLabelElement = (edgeId: string, container: HTMLDivElement | undefin
153
183
 
154
184
  type RelationshipPopoverInternalProps = {
155
185
  viewId: ViewId
186
+ direct: LikeC4Model.AnyRelation[]
187
+ nested: LikeC4Model.AnyRelation[]
156
188
  diagramEdge: DiagramEdge
157
189
  sourceNode: DiagramNode
158
190
  targetNode: DiagramNode
@@ -163,6 +195,8 @@ const POPOVER_PADDING = 8
163
195
  const RelationshipPopoverInternal = ({
164
196
  viewId,
165
197
  diagramEdge,
198
+ direct,
199
+ nested,
166
200
  sourceNode,
167
201
  targetNode,
168
202
  onMouseEnter,
@@ -240,36 +274,8 @@ const RelationshipPopoverInternal = ({
240
274
  }
241
275
  }, [referenceEl])
242
276
 
243
- const likec4model = useLikeC4Model()
244
277
  const diagram = useDiagram()
245
278
 
246
- const [direct, nested] = pipe(
247
- diagramEdge.relations,
248
- map(id => {
249
- try {
250
- return likec4model.relationship(id)
251
- } catch (e) {
252
- // View was cached, but likec4model based on new data
253
- console.error(
254
- `View is cached and likec4model missing relationship ${id} from ${sourceNode.id} -> ${targetNode.id}`,
255
- e,
256
- )
257
- return null
258
- }
259
- }),
260
- filter(isTruthy),
261
- partition(r => r.source.id === sourceNode.id && r.target.id === targetNode.id),
262
- )
263
-
264
- if (direct.length === 0 && nested.length === 0) {
265
- console.error('No relationships found diagram edge', {
266
- diagramEdge,
267
- sourceNode,
268
- targetNode,
269
- })
270
- return null
271
- }
272
-
273
279
  const navigateTo = enableNavigateTo
274
280
  ? (viewId: ViewId) => {
275
281
  diagram.navigateTo(viewId)