@likec4/diagram 1.36.1 → 1.37.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 (173) hide show
  1. package/bundle/{chunk-DHvr-RGh.js → chunk-DSORWspW.js} +1008 -988
  2. package/bundle/custom.js +64 -56
  3. package/bundle/index.js +3539 -3315
  4. package/dist/LikeC4Diagram.props.d.ts +5 -5
  5. package/dist/LikeC4Styles.js +17 -15
  6. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  7. package/dist/StaticLikeC4Diagram.js +3 -0
  8. package/dist/base/BaseXYFlow.d.ts +1 -1
  9. package/dist/base/BaseXYFlow.js +25 -27
  10. package/dist/base/const.d.ts +4 -4
  11. package/dist/base/const.js +5 -4
  12. package/dist/base/primitives/MarkdownBlock.js +4 -1
  13. package/dist/base/primitives/compound/CompoundActionButton.js +27 -42
  14. package/dist/base/primitives/compound/CompoundDetailsButton.js +23 -48
  15. package/dist/base/primitives/compound/CompoundNodeContainer.js +16 -48
  16. package/dist/base/primitives/compound/CompoundTitle.js +5 -80
  17. package/dist/base/primitives/compound/actionbtns.css.js +1 -13
  18. package/dist/base/primitives/customEdge.d.ts +1 -1
  19. package/dist/base/primitives/customNode.d.ts +4 -4
  20. package/dist/base/primitives/customNode.js +7 -7
  21. package/dist/base/primitives/edge/EdgeLabel.d.ts +5 -4
  22. package/dist/base/primitives/edge/EdgeLabel.js +39 -21
  23. package/dist/base/primitives/edge/EdgeLabelContainer.js +11 -23
  24. package/dist/base/primitives/edge/edge.css.js +19 -11
  25. package/dist/base/primitives/element/ElementShape.js +38 -18
  26. package/dist/base/primitives/element/ElementTags.js +7 -4
  27. package/dist/base/primitives/element/ElementTitle.js +10 -2
  28. package/dist/base/types.d.ts +17 -21
  29. package/dist/bundle/LikeC4View.d.ts +2 -2
  30. package/dist/bundle/LikeC4View.js +4 -1
  31. package/dist/bundle/LikeC4View.props.d.ts +2 -2
  32. package/dist/bundle/ReactLikeC4.d.ts +3 -3
  33. package/dist/bundle/custom.js +2 -1
  34. package/dist/bundle/index.js +2 -1
  35. package/dist/components/Link.js +3 -2
  36. package/dist/context/ReduceGraphics.js +2 -5
  37. package/dist/context/RootContainer.js +1 -5
  38. package/dist/custom/customNodes.d.ts +32 -26
  39. package/dist/custom/customNodes.js +12 -8
  40. package/dist/custom/hooks.d.ts +2 -1
  41. package/dist/custom/index.js +2 -1
  42. package/dist/globals.css.js +1 -1
  43. package/dist/hooks/index.d.ts +1 -1
  44. package/dist/hooks/useDiagram.d.ts +18 -1
  45. package/dist/hooks/useDiagram.js +18 -2
  46. package/dist/hooks/useXYFlow.d.ts +40 -21
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.js +2 -1
  49. package/dist/likec4diagram/DiagramUI.js +5 -2
  50. package/dist/likec4diagram/DiagramXYFlow.js +9 -9
  51. package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
  52. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +27 -44
  53. package/dist/likec4diagram/custom/index.js +3 -3
  54. package/dist/likec4diagram/custom/nodes/nodes.d.ts +8 -6
  55. package/dist/likec4diagram/custom/nodes/nodes.js +13 -14
  56. package/dist/likec4diagram/custom/nodes/toolbar/CompoundToolbar.js +3 -2
  57. package/dist/likec4diagram/custom/nodes/toolbar/_shared.d.ts +2 -2
  58. package/dist/likec4diagram/relationship-popover/RelationshipPopover.d.ts +2 -0
  59. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +441 -0
  60. package/dist/likec4diagram/relationship-popover/actor.d.ts +177 -0
  61. package/dist/likec4diagram/relationship-popover/actor.js +155 -0
  62. package/dist/likec4diagram/relationship-popover/styles.css.d.ts +2 -0
  63. package/dist/likec4diagram/{custom/edges/RelationshipsDropdownMenu.css.js → relationship-popover/styles.css.js} +4 -17
  64. package/dist/likec4diagram/useLayoutConstraints.js +0 -1
  65. package/dist/likec4model/useLikeC4ElementsTree.js +1 -1
  66. package/dist/likec4model/useLikeC4Project.js +15 -1
  67. package/dist/navigationpanel/NavigationPanelDropdown.js +1 -2
  68. package/dist/navigationpanel/actor.js +1 -2
  69. package/dist/navigationpanel/controls/NavigationButtons.js +1 -5
  70. package/dist/navigationpanel/controls/ViewDetailsCard.js +2 -0
  71. package/dist/overlays/element-details/actor.d.ts +564 -8
  72. package/dist/overlays/overlay/Overlay.css.d.ts +1 -1
  73. package/dist/overlays/relationship-details/actor.d.ts +3 -1
  74. package/dist/overlays/relationship-details/hooks.d.ts +19 -1
  75. package/dist/overlays/relationship-details/layout-to-xyflow.js +0 -1
  76. package/dist/overlays/relationship-details/layout.js +1 -2
  77. package/dist/overlays/relationships-browser/actor.d.ts +26 -2
  78. package/dist/overlays/relationships-browser/custom/nodeTypes.d.ts +16 -0
  79. package/dist/overlays/relationships-browser/custom/nodeTypes.js +18 -8
  80. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +0 -1
  81. package/dist/search/components/_shared.css.d.ts +1 -1
  82. package/dist/state/DiagramActorProvider.js +2 -19
  83. package/dist/state/assign.js +24 -10
  84. package/dist/state/diagram-machine.d.ts +149 -474
  85. package/dist/state/diagram-machine.js +94 -54
  86. package/dist/state/types.d.ts +1 -1
  87. package/dist/styles-min.css +1 -1
  88. package/dist/styles-xyflow.css +1 -1
  89. package/dist/styles.css +1 -1
  90. package/dist/styles.css2.js +1 -1
  91. package/dist/utils/index.d.ts +2 -1
  92. package/dist/utils/roundDpr.d.ts +1 -0
  93. package/dist/utils/roundDpr.js +20 -0
  94. package/dist/utils/xyflow.d.ts +0 -1
  95. package/dist/utils/xyflow.js +1 -7
  96. package/package.json +11 -10
  97. package/panda.buildinfo.json +90 -155
  98. package/src/LikeC4Diagram.props.ts +5 -5
  99. package/src/LikeC4Styles.tsx +22 -14
  100. package/src/StaticLikeC4Diagram.tsx +3 -0
  101. package/src/base/BaseXYFlow.tsx +26 -29
  102. package/src/base/const.ts +5 -4
  103. package/src/base/primitives/MarkdownBlock.tsx +6 -1
  104. package/src/base/primitives/compound/CompoundActionButton.tsx +31 -43
  105. package/src/base/primitives/compound/CompoundDetailsButton.tsx +26 -48
  106. package/src/base/primitives/compound/CompoundNodeContainer.tsx +15 -43
  107. package/src/base/primitives/compound/CompoundTitle.tsx +4 -76
  108. package/src/base/primitives/compound/actionbtns.css.ts +0 -12
  109. package/src/base/primitives/customEdge.ts +1 -1
  110. package/src/base/primitives/customNode.tsx +20 -24
  111. package/src/base/primitives/edge/EdgeLabel.tsx +23 -8
  112. package/src/base/primitives/edge/EdgeLabelContainer.tsx +9 -21
  113. package/src/base/primitives/edge/edge.css.ts +19 -11
  114. package/src/base/primitives/element/ElementShape.tsx +40 -18
  115. package/src/base/primitives/element/ElementTags.tsx +7 -4
  116. package/src/base/primitives/element/ElementTitle.tsx +23 -11
  117. package/src/base/types.ts +21 -25
  118. package/src/bundle/LikeC4View.props.ts +2 -2
  119. package/src/bundle/LikeC4View.tsx +8 -5
  120. package/src/bundle/ReactLikeC4.tsx +3 -3
  121. package/src/components/Link.tsx +3 -2
  122. package/src/context/RootContainer.tsx +1 -5
  123. package/src/context/TagStylesContext.tsx +2 -0
  124. package/src/custom/{customNodes.tsx → customNodes.ts} +58 -52
  125. package/src/custom/hooks.ts +7 -0
  126. package/src/globals.css +58 -5
  127. package/src/hooks/index.ts +1 -1
  128. package/src/hooks/useDiagram.ts +39 -5
  129. package/src/hooks/useXYFlow.ts +3 -1
  130. package/src/index.ts +1 -0
  131. package/src/likec4diagram/DiagramUI.tsx +3 -0
  132. package/src/likec4diagram/DiagramXYFlow.tsx +9 -11
  133. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +25 -49
  134. package/src/likec4diagram/custom/index.ts +5 -4
  135. package/src/likec4diagram/custom/nodes/nodes.tsx +13 -11
  136. package/src/likec4diagram/custom/nodes/toolbar/CompoundToolbar.tsx +3 -2
  137. package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +535 -0
  138. package/src/likec4diagram/relationship-popover/actor.ts +192 -0
  139. package/src/likec4diagram/{custom/edges/RelationshipsDropdownMenu.css.ts → relationship-popover/styles.css.ts} +3 -19
  140. package/src/likec4diagram/useLayoutConstraints.ts +0 -1
  141. package/src/likec4model/useLikeC4ElementsTree.ts +1 -1
  142. package/src/navigationpanel/NavigationPanelDropdown.tsx +1 -2
  143. package/src/navigationpanel/actor.ts +2 -2
  144. package/src/navigationpanel/controls/NavigationButtons.tsx +1 -5
  145. package/src/navigationpanel/controls/ViewDetailsCard.tsx +2 -0
  146. package/src/overlays/relationship-details/layout-to-xyflow.ts +0 -1
  147. package/src/overlays/relationship-details/layout.ts +4 -3
  148. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +22 -8
  149. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +0 -1
  150. package/src/state/DiagramActorProvider.tsx +2 -20
  151. package/src/state/assign.ts +24 -10
  152. package/src/state/diagram-machine.ts +101 -53
  153. package/src/state/types.ts +4 -1
  154. package/src/utils/index.ts +2 -1
  155. package/src/utils/roundDpr.ts +27 -0
  156. package/src/utils/xyflow.ts +0 -7
  157. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -35
  158. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +0 -132
  159. package/dist/base/primitives/edge/EdgeLabel.css.d.ts +0 -7
  160. package/dist/base/primitives/edge/EdgeLabel.css.js +0 -57
  161. package/dist/base/primitives/element/ElementShape.css.d.ts +0 -6
  162. package/dist/base/primitives/element/ElementShape.css.js +0 -107
  163. package/dist/likec4diagram/custom/edges/NotePopover.css.js +0 -21
  164. package/dist/likec4diagram/custom/edges/NotePopover.js +0 -91
  165. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.css.d.ts +0 -5
  166. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.d.ts +0 -13
  167. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +0 -215
  168. package/dist/likec4diagram/custom/index.d.ts +0 -62
  169. package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +0 -10
  170. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +0 -138
  171. package/src/base/primitives/edge/EdgeLabel.css.ts +0 -64
  172. package/src/base/primitives/element/ElementShape.css.ts +0 -112
  173. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +0 -281
@@ -1,39 +1,43 @@
1
1
  import type { DeployedInstanceModel, DeploymentNodeModel, NodeModel } from '@likec4/core/model'
2
2
  import type { Any } from '@likec4/core/types'
3
- import { DEV } from 'esm-env'
4
- import type { ReactNode } from 'react'
3
+ import type { FunctionComponent } from 'react'
4
+ import { jsx } from 'react/jsx-runtime'
5
5
  import { customNode } from '../base/primitives'
6
6
  import type { NodeProps } from '../base/types'
7
7
  import type { Types } from '../likec4diagram/types'
8
8
  import { useLikeC4ViewModel } from '../likec4model/useLikeC4Model'
9
9
 
10
- function customDiagramNode<
11
- P extends {
12
- nodeProps: NodeProps<Types.NodeData>
13
- nodeModel: NodeModel
14
- },
15
- >(
16
- Node: (props: P) => ReactNode,
17
- ): (props: P['nodeProps']) => ReactNode {
18
- return customNode((props) => {
19
- const viewModel = useLikeC4ViewModel(props.data.viewId)
10
+ type CustomDiagramNodeProps = {
11
+ nodeProps: NodeProps<Types.NodeData, string>
12
+ nodeModel: NodeModel
13
+ }
14
+ type CustomNodeComponent<P extends CustomDiagramNodeProps> = (
15
+ impl: FunctionComponent<P>,
16
+ ) => FunctionComponent<P['nodeProps']>
17
+
18
+ function customDiagramNode<const P extends CustomDiagramNodeProps>(
19
+ Node: FunctionComponent<P>,
20
+ ): FunctionComponent<P['nodeProps']> {
21
+ return customNode((props: NodeProps<Types.NodeData, string>) => {
22
+ // @ts-ignore because dts-bundler fails to infer types
23
+ const viewId = props.data.viewId
24
+ const viewModel = useLikeC4ViewModel(viewId)
20
25
  if (!viewModel) {
21
- if (DEV) {
22
- throw new Error(`View "${props.data.viewId}" not found, requested by customNode "${props.id}"`)
23
- }
24
- console.error(`View "${props.data.viewId}" not found in likec4model`, { props })
26
+ console.error(`View "${viewId}" not found, requested by customNode "${props.data.id}"`, { props })
27
+ return null
28
+ }
29
+ const model = viewModel.findNode(props.data.id)
30
+ if (!model) {
31
+ console.error(
32
+ `Node "${props.id}" not found in view "${viewId}", requested by customNode "${props.data.id}"`,
33
+ { props },
34
+ )
25
35
  return null
26
36
  }
27
- const model = viewModel.node(props.id)
28
- // @ts-ignore
29
- return <Node nodeProps={props} nodeModel={model} />
30
- }) as (props: P['nodeProps']) => ReactNode
37
+ return jsx(Node, { nodeProps: props, nodeModel: model })
38
+ }) as any
31
39
  }
32
40
 
33
- export type CustomElementNodeProps<M extends Any = Any> = {
34
- nodeProps: NodeProps<Types.ElementNodeData, 'element'>
35
- nodeModel: NodeModel.WithElement<M>
36
- }
37
41
  /**
38
42
  * Node that will be used to render the element from the model.
39
43
  * It is a leaf node, i.e. it does not have children.
@@ -87,12 +91,12 @@ export type CustomElementNodeProps<M extends Any = Any> = {
87
91
  * ))
88
92
  * ```
89
93
  */
90
- export const elementNode = customDiagramNode<CustomElementNodeProps>
91
-
92
- export type CustomDeploymentNodeProps<M extends Any = Any> = {
93
- nodeProps: NodeProps<Types.DeploymentElementNodeData, 'deployment'>
94
- nodeModel: NodeModel.WithDeploymentElement<M>
94
+ export const elementNode: CustomNodeComponent<CustomElementNodeProps> = customDiagramNode as any
95
+ export type CustomElementNodeProps = {
96
+ nodeProps: NodeProps<Types.ElementNodeData, 'element'>
97
+ nodeModel: NodeModel.WithElement<Any>
95
98
  }
99
+
96
100
  /**
97
101
  * Node that will be used to render the element from deployment model.
98
102
  * It is a leaf node, i.e. it does not have children.
@@ -141,12 +145,12 @@ export type CustomDeploymentNodeProps<M extends Any = Any> = {
141
145
  * ))
142
146
  * ```
143
147
  */
144
- export const deploymentNode = customDiagramNode<CustomDeploymentNodeProps>
145
-
146
- export type CustomCompoundElementNodeProps<M extends Any = Any> = {
147
- nodeProps: NodeProps<Types.CompoundElementNodeData, 'compound-element'>
148
- nodeModel: NodeModel.WithElement<M>
148
+ export const deploymentNode: CustomNodeComponent<CustomDeploymentNodeProps> = customDiagramNode as any
149
+ export type CustomDeploymentNodeProps = {
150
+ nodeProps: NodeProps<Types.DeploymentElementNodeData, 'deployment'>
151
+ nodeModel: NodeModel.WithDeploymentElement<Any>
149
152
  }
153
+
150
154
  /**
151
155
  * Node that will be used to render the compound element from the model.
152
156
  * It is a container node, i.e. it has children.
@@ -185,12 +189,12 @@ export type CustomCompoundElementNodeProps<M extends Any = Any> = {
185
189
  * ))
186
190
  * ```
187
191
  */
188
- export const compoundElementNode = customDiagramNode<CustomCompoundElementNodeProps>
189
-
190
- export type CustomCompoundDeploymentNodeProps<M extends Any = Any> = {
191
- nodeProps: NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>
192
- nodeModel: NodeModel.WithDeploymentElement<M>
192
+ export const compoundElementNode: CustomNodeComponent<CustomCompoundElementNodeProps> = customDiagramNode as any
193
+ export type CustomCompoundElementNodeProps = {
194
+ nodeProps: NodeProps<Types.CompoundElementNodeData, 'compound-element'>
195
+ nodeModel: NodeModel.WithElement<Any>
193
196
  }
197
+
194
198
  /**
195
199
  * Node that will be used to render the compound from the deployment model.
196
200
  * It is a container node, i.e. it has children.
@@ -223,12 +227,12 @@ export type CustomCompoundDeploymentNodeProps<M extends Any = Any> = {
223
227
  * </CompoundNodeContainer>
224
228
  * ```
225
229
  */
226
- export const compoundDeploymentNode = customDiagramNode<CustomCompoundDeploymentNodeProps>
227
-
228
- export type CustomViewGroupNodeProps<M extends Any = Any> = {
229
- nodeProps: NodeProps<Types.ViewGroupNodeData, 'view-group'>
230
- nodeModel: NodeModel.IsGroup<M>
230
+ export const compoundDeploymentNode: CustomNodeComponent<CustomCompoundDeploymentNodeProps> = customDiagramNode as any
231
+ export type CustomCompoundDeploymentNodeProps = {
232
+ nodeProps: NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>
233
+ nodeModel: NodeModel.WithDeploymentElement<Any>
231
234
  }
235
+
232
236
  /**
233
237
  * Node that will be used to render the view group from the model.
234
238
  *
@@ -254,14 +258,16 @@ export type CustomViewGroupNodeProps<M extends Any = Any> = {
254
258
  * </CompoundNodeContainer>
255
259
  * ```
256
260
  */
257
- export const viewGroupNode = customDiagramNode<CustomViewGroupNodeProps>
261
+ export const viewGroupNode: CustomNodeComponent<CustomViewGroupNodeProps> = customDiagramNode as any
262
+ export type CustomViewGroupNodeProps = {
263
+ nodeProps: NodeProps<Types.ViewGroupNodeData, 'view-group'>
264
+ nodeModel: NodeModel.IsGroup<Any>
265
+ }
258
266
 
259
267
  export interface CustomNodes {
260
- element?: undefined | ((props: NodeProps<Types.ElementNodeData, 'element'>) => ReactNode)
261
- deployment?: undefined | ((props: NodeProps<Types.DeploymentElementNodeData, 'deployment'>) => ReactNode)
262
- compoundElement?: undefined | ((props: NodeProps<Types.CompoundElementNodeData, 'compound-element'>) => ReactNode)
263
- compoundDeployment?:
264
- | undefined
265
- | ((props: NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>) => ReactNode)
266
- viewGroup?: undefined | ((props: NodeProps<Types.ViewGroupNodeData, 'view-group'>) => ReactNode)
268
+ element?: FunctionComponent<NodeProps<Types.ElementNodeData, 'element'>>
269
+ deployment?: FunctionComponent<NodeProps<Types.DeploymentElementNodeData, 'deployment'>>
270
+ compoundElement?: FunctionComponent<NodeProps<Types.CompoundElementNodeData, 'compound-element'>>
271
+ compoundDeployment?: FunctionComponent<NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>>
272
+ viewGroup?: FunctionComponent<NodeProps<Types.ViewGroupNodeData, 'view-group'>>
267
273
  }
@@ -21,6 +21,7 @@ export {
21
21
  useDiagram,
22
22
  useDiagramActorRef,
23
23
  useDiagramContext,
24
+ useOnDiagramEvent,
24
25
  } from '../hooks/useDiagram'
25
26
  export {
26
27
  useXYFlow,
@@ -39,6 +40,12 @@ export {
39
40
  useLikeC4Specification,
40
41
  useLikeC4ViewModel,
41
42
  } from '../likec4model/useLikeC4Model'
43
+ export {
44
+ useHasProjects,
45
+ useLikeC4ProjectId,
46
+ useLikeC4Projects,
47
+ useLikeC4ProjectsContext,
48
+ } from '../likec4model/useLikeC4Project'
42
49
 
43
50
  export { useMantinePortalProps } from '../hooks/useMantinePortalProps'
44
51
  export { useSetState } from '../hooks/useSetState'
package/src/globals.css CHANGED
@@ -1,3 +1,7 @@
1
+ .likec4-shadow-root dialog {
2
+ color: var(--mantine-color-text);
3
+ }
4
+
1
5
  .likec4-root {
2
6
  overflow: hidden;
3
7
  position: relative;
@@ -12,38 +16,87 @@
12
16
  .likec4-root .react-flow:is(.not-initialized) {
13
17
  opacity: 0;
14
18
  }
19
+
20
+ .likec4-root .react-flow {
21
+ contain: paint;
22
+ --xy-background-color: var(--colors-likec4-background);
23
+ --xy-background-pattern-color: var(--colors-likec4-background-pattern, var(--colors-likec4-background));
24
+ }
25
+
26
+ .likec4-root .react-flow:is(.bg-transparent) {
27
+ background: transparent !important;
28
+ --xy-background-color: transparent !important;
29
+ }
30
+
31
+ .likec4-root .react-flow .react-flow__pane {
32
+ user-select: none;
33
+ --webkit-user-select: none;
34
+ }
35
+
36
+ .likec4-static-view .react-flow .react-flow__attribution {
37
+ display: none;
38
+ }
39
+
15
40
  :where(.likec4-root .mantine-ActionIcon-icon) .tabler-icon {
16
41
  width: 75%;
17
42
  height: 75%;
18
43
  }
19
44
 
20
- .likec4-root :where(.react-flow__node, .react-flow__edge, .likec4-edge-label-container):has([data-likec4-dimmed]) {
45
+ .likec4-root .likec4-edge-label-container {
46
+ top: 0;
47
+ left: 0;
48
+ position: absolute;
49
+ width: auto;
50
+ height: auto;
51
+ }
52
+
53
+ .likec4-root :where(.react-flow__node, .react-flow__edge):has([data-likec4-dimmed]) {
54
+ opacity: 0.25;
55
+ }
56
+ .likec4-root .likec4-edge-label-container:is([data-likec4-dimmed]) {
21
57
  opacity: 0.25;
22
58
  }
23
59
 
24
60
  .likec4-root:not([data-likec4-reduced-graphics])
25
- :where(.react-flow__node, .react-flow__edge, .likec4-edge-label-container):has([data-likec4-dimmed]) {
61
+ :where(.react-flow__node, .react-flow__edge):has([data-likec4-dimmed]) {
62
+ filter: grayscale(85%) blur(3px);
63
+ }
64
+ .likec4-root:not([data-likec4-reduced-graphics]) .likec4-edge-label-container:is([data-likec4-dimmed]) {
26
65
  filter: grayscale(85%) blur(3px);
27
66
  }
28
67
 
29
68
  .likec4-root:not([data-likec4-reduced-graphics])
30
- :where(.react-flow__node, .react-flow__edge, .likec4-edge-label-container):has([data-likec4-dimmed="true"]) {
69
+ :where(.react-flow__node, .react-flow__edge):has([data-likec4-dimmed="true"]) {
31
70
  transition-property: opacity, filter;
32
71
  transition-timing-function: cubic-bezier(0.50, 0, 0.2, 1);
33
72
  transition-duration: 600ms;
34
73
  }
35
74
 
75
+ .likec4-root:not([data-likec4-reduced-graphics]) .likec4-edge-label-container:is([data-likec4-dimmed="true"]) {
76
+ transition-property: opacity, filter;
77
+ transition-timing-function: cubic-bezier(0.50, 0, 0.2, 1);
78
+ transition-duration: 600ms;
79
+ }
80
+
81
+ .likec4-root .react-flow :where(.react-flow__nodes, .react-flow__edges, .react-flow__edgelabel-renderer) {
82
+ display: contents;
83
+ }
84
+
85
+ .likec4-root .react-flow__node.draggable:has(.likec4-compound-node) {
86
+ cursor: default;
87
+ }
88
+
36
89
  [data-mantine-color-scheme="dark"]
37
90
  .likec4-root:not([data-likec4-reduced-graphics])
38
91
  :where(.react-flow__edges, .react-flow__edgelabel-renderer)
39
- > svg {
92
+ > :where(svg, .likec4-edge-label-container) {
40
93
  mix-blend-mode: plus-lighter;
41
94
  }
42
95
 
43
96
  [data-mantine-color-scheme="light"]
44
97
  .likec4-root:not([data-likec4-reduced-graphics])
45
98
  :where(.react-flow__edges, .react-flow__edgelabel-renderer)
46
- > svg {
99
+ > :where(svg, .likec4-edge-label-container) {
47
100
  mix-blend-mode: screen;
48
101
  }
49
102
 
@@ -2,4 +2,4 @@ export { useMantinePortalProps } from './useMantinePortalProps'
2
2
  export { useSetState } from './useSetState'
3
3
  export { useUpdateEffect } from './useUpdateEffect'
4
4
  export { useCurrentZoom, useIsZoomTooSmall, useXYFlow, useXYInternalNode, useXYStore, useXYStoreApi } from './useXYFlow'
5
- export type { XYStoreApi } from './useXYFlow'
5
+ export type { XYFlowInstance, XYStoreApi, XYStoreState } from './useXYFlow'
@@ -3,8 +3,6 @@ import {
3
3
  type DiagramEdge,
4
4
  type DiagramNode,
5
5
  type DiagramView,
6
- type EdgeId,
7
- type Fqn,
8
6
  type NodeId,
9
7
  type NodeNotation as ElementNotation,
10
8
  type scalar,
@@ -13,7 +11,7 @@ import {
13
11
  import { useCallbackRef } from '@mantine/hooks'
14
12
  import { useSelector as useXstateSelector } from '@xstate/react'
15
13
  import { shallowEqual } from 'fast-equals'
16
- import { type DependencyList, useCallback, useMemo } from 'react'
14
+ import { type DependencyList, useCallback, useEffect, useMemo, useRef } from 'react'
17
15
  import type { PartialDeep } from 'type-fest'
18
16
  import type { FeatureName } from '../context/DiagramFeatures'
19
17
  import type { OpenSourceParams } from '../LikeC4Diagram.props'
@@ -24,8 +22,7 @@ import type {
24
22
  DiagramActorRef,
25
23
  DiagramActorSnapshot,
26
24
  DiagramContext,
27
- SyncLayoutActorRef,
28
- SyncLayoutActorSnapshot,
25
+ DiagramEmittedEvents,
29
26
  } from '../state/types'
30
27
  import { findDiagramEdge, findDiagramNode } from '../state/utils'
31
28
  import { useDiagramActorRef } from './safeContext'
@@ -102,10 +99,15 @@ export interface DiagramApi {
102
99
  * @warning Do not use in render phase
103
100
  */
104
101
  findDiagramNode(xynodeId: string): DiagramNode | null
102
+ /**
103
+ * @warning Do not use in render phase
104
+ */
105
+ findEdge(xyedgeId: string): Types.Edge | null
105
106
  /**
106
107
  * @warning Do not use in render phase
107
108
  */
108
109
  findDiagramEdge(xyedgeId: string): DiagramEdge | null
110
+
109
111
  startWalkthrough(): void
110
112
  walkthroughStep(direction?: 'next' | 'previous'): void
111
113
  stopWalkthrough(): void
@@ -208,6 +210,9 @@ export function useDiagram(): DiagramApi {
208
210
  findDiagramNode: (xynodeId: string): DiagramNode | null => {
209
211
  return findDiagramNode(actor.getSnapshot().context, xynodeId)
210
212
  },
213
+ findEdge: (xyedgeId: string): Types.Edge | null => {
214
+ return actor.getSnapshot().context.xyedges.find(e => e.data.id === xyedgeId) ?? null
215
+ },
211
216
  /**
212
217
  * @warning Do not use in render phase
213
218
  */
@@ -264,3 +269,32 @@ export function useDiagramContext<T = unknown>(
264
269
  const select = useCallback((s: DiagramActorSnapshot) => selector(s.context), deps ?? [])
265
270
  return useXstateSelector(actorRef, select, compare)
266
271
  }
272
+
273
+ type PickEmittedEvent<T extends DiagramEmittedEvents['type']> = DiagramEmittedEvents & { type: T }
274
+
275
+ /**
276
+ * Subscribe to diagram emitted events
277
+ * @example
278
+ * ```tsx
279
+ * useOnDiagramEvent('navigateTo', ({viewId}) => {
280
+ * console.log('Navigating to view', viewId)
281
+ * })
282
+ * ```
283
+ */
284
+ export function useOnDiagramEvent<T extends DiagramEmittedEvents['type']>(
285
+ event: T,
286
+ callback: (event: PickEmittedEvent<T>) => void,
287
+ ): void {
288
+ const actorRef = useDiagramActorRef()
289
+ const callbackRef = useRef(callback)
290
+ callbackRef.current = callback
291
+
292
+ useEffect(() => {
293
+ const subscription = actorRef.on(event, (event) => {
294
+ callbackRef.current(event as DiagramEmittedEvents & { type: T })
295
+ })
296
+ return () => {
297
+ subscription.unsubscribe()
298
+ }
299
+ }, [actorRef])
300
+ }
@@ -13,8 +13,10 @@ import type { Types } from '../likec4diagram/types'
13
13
  export const useXYFlow = useReactFlow<Types.Node, Types.Edge>
14
14
  export type XYFlowInstance = ReactFlowInstance<Types.Node, Types.Edge>
15
15
 
16
+ export type XYStoreState = ReactFlowState<Types.Node, Types.Edge>
17
+
16
18
  export function useXYStore<StateSlice = unknown>(
17
- selector: (state: ReactFlowState<Types.Node, Types.Edge>) => StateSlice,
19
+ selector: (state: XYStoreState) => StateSlice,
18
20
  equalityFn?: (a: NoInfer<StateSlice>, b: NoInfer<StateSlice>) => boolean,
19
21
  ): StateSlice {
20
22
  return useStore(
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export type {
29
29
  CustomNodes,
30
30
  DiagramNodeWithNavigate,
31
31
  ElementIconRenderer,
32
+ ElementIconRendererProps,
32
33
  LikeC4ColorScheme,
33
34
  LikeC4DiagramEventHandlers,
34
35
  LikeC4DiagramProperties,
@@ -6,6 +6,7 @@ import { EnsureCurrentViewModel } from '../likec4model/LikeC4ModelContext'
6
6
  import { NavigationPanel } from '../navigationpanel'
7
7
  import { Overlays } from '../overlays/Overlays'
8
8
  import { Search } from '../search/Search'
9
+ import { RelationshipPopover } from './relationship-popover/RelationshipPopover'
9
10
  import { Controls, DiagramTitlePanel, DynamicViewWalkthrough, NotationPanel } from './ui'
10
11
 
11
12
  export const DiagramUI = memo(() => {
@@ -15,6 +16,7 @@ export const DiagramUI = memo(() => {
15
16
  enableNotations,
16
17
  enableDynamicViewWalkthrough,
17
18
  enableSearch,
19
+ enableRelationshipDetails,
18
20
  } = useEnabledFeatures()
19
21
  const overlaysActorRef = useOverlaysActorRef()
20
22
  const searchActorRef = useSearchActorRef()
@@ -37,6 +39,7 @@ export const DiagramUI = memo(() => {
37
39
  </>
38
40
  )}
39
41
  {enableSearch && searchActorRef && <Search searchActorRef={searchActorRef} />}
42
+ {enableRelationshipDetails && <RelationshipPopover />}
40
43
  </>
41
44
  )
42
45
  })
@@ -122,11 +122,9 @@ export function LikeC4DiagramXYFlow({
122
122
  return {
123
123
  element: renderNodes?.element ?? defaultNodeTypes.element,
124
124
  deployment: renderNodes?.deployment ?? defaultNodeTypes.deployment,
125
- 'compound-element': renderNodes?.compoundElement ?? defaultNodeTypes['compound-element'],
126
- 'compound-deployment': renderNodes?.compoundDeployment ?? defaultNodeTypes['compound-deployment'],
127
- 'view-group': renderNodes?.viewGroup ?? defaultNodeTypes['view-group'],
128
- } satisfies {
129
- [key in Types.Node['type']]: any
125
+ 'compound-element': renderNodes?.compoundElement ?? defaultNodeTypes.compoundElement,
126
+ 'compound-deployment': renderNodes?.compoundDeployment ?? defaultNodeTypes.compoundDeployment,
127
+ 'view-group': renderNodes?.viewGroup ?? defaultNodeTypes.viewGroup,
130
128
  }
131
129
  },
132
130
  [renderNodes],
@@ -187,16 +185,16 @@ export function LikeC4DiagramXYFlow({
187
185
  diagram.send({ type: 'xyflow.nodeMouseLeave', node })
188
186
  }
189
187
  })}
190
- onEdgeMouseEnter={useCallbackRef((_event, edge) => {
191
- _event.stopPropagation()
188
+ onEdgeMouseEnter={useCallbackRef((event, edge) => {
189
+ event.stopPropagation()
192
190
  if (!edge.data.hovered) {
193
- diagram.send({ type: 'xyflow.edgeMouseEnter', edge })
191
+ diagram.send({ type: 'xyflow.edgeMouseEnter', edge, event })
194
192
  }
195
193
  })}
196
- onEdgeMouseLeave={useCallbackRef((_event, edge) => {
197
- _event.stopPropagation()
194
+ onEdgeMouseLeave={useCallbackRef((event, edge) => {
195
+ event.stopPropagation()
198
196
  if (edge.data.hovered) {
199
- diagram.send({ type: 'xyflow.edgeMouseLeave', edge })
197
+ diagram.send({ type: 'xyflow.edgeMouseLeave', edge, event })
200
198
  }
201
199
  })}
202
200
  {...props.pannable && {
@@ -19,16 +19,13 @@ import {
19
19
  EdgePath,
20
20
  } from '../../../base/primitives'
21
21
  import { useEnabledFeatures } from '../../../context/DiagramFeatures'
22
- import { useDiagram, useDiagramActorSnapshot } from '../../../hooks/useDiagram'
23
- import { useIsPanning } from '../../../hooks/useReducedGraphics'
22
+ import { useDiagram } from '../../../hooks/useDiagram'
24
23
  import { useXYFlow, useXYInternalNode, useXYStoreApi } from '../../../hooks/useXYFlow'
25
- import type { DiagramActorSnapshot } from '../../../state/types'
24
+ import { roundDpr } from '../../../utils/roundDpr'
26
25
  import { vector, VectorImpl } from '../../../utils/vector'
27
26
  import { bezierControlPoints, bezierPath, isSamePoint } from '../../../utils/xyflow'
28
27
  import type { Types } from '../../types'
29
28
  import * as edgesCss from './edges.css'
30
- import { NotePopover } from './NotePopover'
31
- import { RelationshipsDropdownMenu } from './RelationshipsDropdownMenu'
32
29
  import { getNodeIntersectionFromCenterToPoint } from './utils'
33
30
 
34
31
  const curve = d3line<XYPosition>()
@@ -36,15 +33,15 @@ const curve = d3line<XYPosition>()
36
33
  .x(d => d.x)
37
34
  .y(d => d.y)
38
35
 
39
- const selectActiveStepId = (s: DiagramActorSnapshot) => s.context.activeWalkthrough?.stepId ?? null
40
36
  export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) => {
41
- const isPanning = useIsPanning()
42
37
  const [isControlPointDragging, setIsControlPointDragging] = useState(false)
43
38
  const xyflowStore = useXYStoreApi()
44
39
  const xyflow = useXYFlow()
45
40
  const diagram = useDiagram()
46
- const activeWalkthroughStep = useDiagramActorSnapshot(selectActiveStepId)
47
- const { enableNavigateTo, enableEdgeEditing, enableRelationshipDetails } = useEnabledFeatures()
41
+ const {
42
+ enableNavigateTo,
43
+ enableEdgeEditing,
44
+ } = useEnabledFeatures()
48
45
  const {
49
46
  id,
50
47
  source,
@@ -120,8 +117,8 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
120
117
  if (!path) return
121
118
  const dompoint = path.getPointAtLength(path.getTotalLength() * 0.5)
122
119
  const point = {
123
- x: Math.round(dompoint.x),
124
- y: Math.round(dompoint.y),
120
+ x: roundDpr(dompoint.x),
121
+ y: roundDpr(dompoint.y),
125
122
  }
126
123
  setLabelPos(current => isSamePoint(current, point) ? current : point)
127
124
  }, [edgePath])
@@ -166,13 +163,16 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
166
163
  }
167
164
  if (!isSamePoint(pointer, clientPoint)) {
168
165
  setIsControlPointDragging(true)
169
- hasMoved = true
166
+ if (!hasMoved) {
167
+ diagram.send({ type: 'xyflow.edgeEditingStarted', edge: props.data })
168
+ hasMoved = true
169
+ }
170
170
  pointer = clientPoint
171
171
  const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: false })
172
172
  const cp = controlPoints.slice()
173
173
  cp[index] = {
174
- x: Math.round(x),
175
- y: Math.round(y),
174
+ x: roundDpr(x),
175
+ y: roundDpr(y),
176
176
  }
177
177
  diagram.updateEdgeData(id as EdgeId, {
178
178
  controlPoints: cp,
@@ -193,6 +193,7 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
193
193
  }
194
194
  setIsControlPointDragging(false)
195
195
  }
196
+
196
197
  const onClick = (e: MouseEvent) => {
197
198
  e.stopPropagation()
198
199
  e.preventDefault()
@@ -307,40 +308,6 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
307
308
  zIndex = ZIndexes.Element + 1
308
309
  }
309
310
 
310
- let edgeLabel = (
311
- <EdgeLabel edgeProps={props}>
312
- {!isControlPointDragging && navigateTo && (
313
- <EdgeActionButton
314
- {...props}
315
- onClick={e => {
316
- e.stopPropagation()
317
- diagram.navigateTo(navigateTo)
318
- }} />
319
- )}
320
- </EdgeLabel>
321
- )
322
-
323
- if (!isControlPointDragging && !isPanning) {
324
- const notes = props.data.notes
325
- if (notes && activeWalkthroughStep === props.id) {
326
- edgeLabel = (
327
- <NotePopover notes={notes.txt ?? notes.md}>
328
- {edgeLabel}
329
- </NotePopover>
330
- )
331
- } else if (enableRelationshipDetails) {
332
- edgeLabel = (
333
- <RelationshipsDropdownMenu
334
- disabled={!!dimmed}
335
- source={source}
336
- target={target}
337
- edgeId={edgeId}>
338
- {edgeLabel}
339
- </RelationshipsDropdownMenu>
340
- )
341
- }
342
- }
343
-
344
311
  return (
345
312
  <>
346
313
  <EdgeContainer {...props} className={clsx(isControlPointDragging && edgesCss.controlDragging)}>
@@ -359,7 +326,16 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
359
326
  translate: isModified ? 'translate(-50%, 0)' : undefined,
360
327
  }}
361
328
  >
362
- {edgeLabel}
329
+ <EdgeLabel edgeProps={props}>
330
+ {navigateTo && (
331
+ <EdgeActionButton
332
+ {...props}
333
+ onClick={e => {
334
+ e.stopPropagation()
335
+ diagram.navigateTo(navigateTo)
336
+ }} />
337
+ )}
338
+ </EdgeLabel>
363
339
  </EdgeLabelContainer>
364
340
  </EdgeContainer>
365
341
  {/* Render control points above edge label */}
@@ -1,3 +1,4 @@
1
+ import type { CustomNodes } from '../../custom/customNodes'
1
2
  import type { Types } from '../types'
2
3
  import { RelationshipEdge } from './edges/RelationshipEdge'
3
4
  import {
@@ -11,10 +12,10 @@ import {
11
12
  export const BuiltinNodes = {
12
13
  element: ElementNode,
13
14
  deployment: DeploymentNode,
14
- 'compound-element': CompoundElementNode,
15
- 'compound-deployment': CompoundDeploymentNode,
16
- 'view-group': ViewGroupNode,
17
- } satisfies { [key in Types.Node['type']]: any }
15
+ compoundElement: CompoundElementNode,
16
+ compoundDeployment: CompoundDeploymentNode,
17
+ viewGroup: ViewGroupNode,
18
+ } satisfies Required<CustomNodes>
18
19
 
19
20
  export const edgeTypes = {
20
21
  relationship: RelationshipEdge,