@likec4/diagram 1.9.0 → 1.12.1

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 (113) hide show
  1. package/package.json +20 -43
  2. package/src/LikeC4CustomColors.tsx +5 -2
  3. package/src/LikeC4Diagram.css.ts +1 -1
  4. package/src/LikeC4Diagram.props.ts +40 -94
  5. package/src/LikeC4Diagram.tsx +14 -4
  6. package/src/LikeC4ModelProvider.tsx +21 -0
  7. package/src/StaticLikeC4Diagram.tsx +1 -0
  8. package/src/globals.css.ts +2 -3
  9. package/src/hooks/index.ts +1 -0
  10. package/src/hooks/useMantinePortalProps.ts +8 -0
  11. package/src/hooks/useXYFlow.ts +1 -2
  12. package/src/index.ts +9 -0
  13. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  14. package/src/likec4model/index.ts +1 -0
  15. package/src/likec4model/useLikeC4Model.ts +58 -0
  16. package/src/state/diagram-to-xyflow.ts +1 -2
  17. package/src/state/diagramStore.ts +185 -66
  18. package/src/{theme.css.ts → theme-vars.ts} +11 -4
  19. package/src/ui/DiagramTitlePanel.css.ts +2 -3
  20. package/src/ui/DiagramTitlePanel.tsx +25 -3
  21. package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
  22. package/src/ui/DynamicViewWalkthrough.tsx +97 -52
  23. package/src/ui/OptionsPanel.css.ts +1 -2
  24. package/src/ui/OptionsPanel.tsx +5 -2
  25. package/src/ui/notation/NotationPanel.css.ts +2 -3
  26. package/src/ui/notation/NotationPanel.tsx +10 -11
  27. package/src/ui/options/NodeOptions.tsx +4 -1
  28. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
  29. package/src/ui/top-left/TopLeftPanel.tsx +128 -117
  30. package/src/ui/top-left/_shared.ts +7 -6
  31. package/src/ui/top-left/styles.css.ts +48 -8
  32. package/src/utils/vector.ts +57 -0
  33. package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
  34. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
  35. package/src/xyflow/XYFlow.tsx +13 -17
  36. package/src/xyflow/XYFlowEvents.tsx +58 -58
  37. package/src/xyflow/XYFlowInner.tsx +4 -8
  38. package/src/xyflow/edges/EdgeLabel.tsx +185 -0
  39. package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
  40. package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
  41. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
  42. package/src/xyflow/edges/edges.css.ts +75 -7
  43. package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
  44. package/src/xyflow/nodes/compound/CompoundNode.tsx +25 -8
  45. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  46. package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
  47. package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
  48. package/src/xyflow/nodes/element/element.css.ts +1 -2
  49. package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
  50. package/src/xyflow/types.ts +1 -24
  51. package/src/xyflow/useLayoutConstraints.ts +10 -6
  52. package/dist/LikeC4CustomColors.d.ts +0 -3
  53. package/dist/LikeC4CustomColors.props.d.ts +0 -5
  54. package/dist/LikeC4Diagram.css.d.ts +0 -4
  55. package/dist/LikeC4Diagram.d.ts +0 -7
  56. package/dist/LikeC4Diagram.props.d.ts +0 -154
  57. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  58. package/dist/globals.css.d.ts +0 -1
  59. package/dist/hooks/index.d.ts +0 -4
  60. package/dist/hooks/useDiagramState.d.ts +0 -6
  61. package/dist/hooks/useSetState.d.ts +0 -3
  62. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  63. package/dist/hooks/useXYFlow.d.ts +0 -267
  64. package/dist/index.d.ts +0 -9
  65. package/dist/index.mjs +0 -3684
  66. package/dist/mantine/EnsureMantine.d.ts +0 -8
  67. package/dist/mantine.css.d.ts +0 -3
  68. package/dist/state/DiagramContext.d.ts +0 -1192
  69. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  70. package/dist/state/diagramStore.d.ts +0 -1268
  71. package/dist/style.css +0 -2139
  72. package/dist/theme.css.d.ts +0 -63
  73. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  74. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  75. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  76. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  77. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  78. package/dist/ui/OptionsPanel.d.ts +0 -1
  79. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  80. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  81. package/dist/ui/notation/index.d.ts +0 -1
  82. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  83. package/dist/ui/options/NodeOptions.d.ts +0 -19
  84. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  85. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  86. package/dist/ui/top-left/_shared.d.ts +0 -5
  87. package/dist/ui/top-left/styles.css.d.ts +0 -3
  88. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  89. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  90. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  91. package/dist/xyflow/XYFlow.d.ts +0 -12
  92. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  93. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  94. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  95. package/dist/xyflow/const.d.ts +0 -7
  96. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  97. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  98. package/dist/xyflow/edges/utils.d.ts +0 -17
  99. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  100. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  101. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  102. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  103. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  104. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  105. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  106. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  107. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  108. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  109. package/dist/xyflow/types.d.ts +0 -50
  110. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  111. package/dist/xyflow/utils.d.ts +0 -18
  112. package/src/mantine.css.ts +0 -7
  113. package/src/reset.d.ts +0 -2
@@ -1,4 +1,3 @@
1
- import { extractStep } from '@likec4/core'
2
1
  import type { ReactFlowProps } from '@xyflow/react'
3
2
  import { useMemo, useRef } from 'react'
4
3
  import { isNonNullish, isTruthy } from 'remeda'
@@ -46,12 +45,17 @@ export function useXYFlowEvents() {
46
45
  }
47
46
  dblclickTimeout.current = window.setTimeout(() => {
48
47
  dblclickTimeout.current = undefined
49
- }, 250)
48
+ }, 300)
50
49
  return false
51
50
  }
52
51
 
53
- const lastClickWasRecent = (ms = 1500) => {
54
- return lastClickTimestamp.current && (Date.now() - lastClickTimestamp.current) < ms
52
+ const lastClickWasRecent = (ms = 2000) => {
53
+ const prevTimestamp = lastClickTimestamp.current
54
+ lastClickTimestamp.current = Date.now()
55
+ if (prevTimestamp === undefined) {
56
+ return false
57
+ }
58
+ return prevTimestamp + ms > Date.now()
55
59
  }
56
60
 
57
61
  return ({
@@ -59,13 +63,11 @@ export function useXYFlowEvents() {
59
63
  const {
60
64
  onCanvasDblClick,
61
65
  zoomable,
62
- xystore,
63
66
  viewportChanged,
64
67
  fitDiagram,
65
- resetLastClicked
68
+ resetFocusAndLastClicked
66
69
  } = diagramApi.getState()
67
- resetLastClicked()
68
- xystore.getState().resetSelectedElements()
70
+ resetFocusAndLastClicked()
69
71
  if (!onCanvasDblClick) {
70
72
  event.stopPropagation()
71
73
  }
@@ -75,46 +77,43 @@ export function useXYFlowEvents() {
75
77
  onCanvasDblClick?.(event)
76
78
  },
77
79
  onPaneClick: (event) => {
80
+ console.log('onPaneClick')
78
81
  if (dbclickLock()) {
79
82
  return
80
83
  }
81
84
  const {
82
85
  focusedNodeId,
83
- activeDynamicViewStep,
86
+ activeWalkthrough,
84
87
  fitDiagram,
85
88
  onCanvasClick,
86
- xystore,
87
- resetLastClicked
89
+ resetFocusAndLastClicked
88
90
  } = diagramApi.getState()
89
- if ((focusedNodeId ?? activeDynamicViewStep) !== null) {
91
+ if ((focusedNodeId ?? activeWalkthrough) !== null) {
90
92
  fitDiagram()
91
93
  if (!onCanvasClick) {
92
94
  event.stopPropagation()
93
95
  }
94
96
  }
95
- resetLastClicked()
96
- xystore.getState().resetSelectedElements()
97
+ resetFocusAndLastClicked()
97
98
  onCanvasClick?.(event)
98
99
  },
99
100
  onNodeContextMenu: (event, xynode) => {
100
101
  diagramApi.getState().setLastClickedNode(xynode.id)
101
- diagramApi.getState().onNodeContextMenu?.({
102
- element: xynode.data.element,
103
- xynode,
102
+ diagramApi.getState().onNodeContextMenu?.(
103
+ xynode.data.element,
104
104
  event
105
- })
105
+ )
106
106
  },
107
107
  onPaneContextMenu: (event) => {
108
- diagramApi.getState().resetLastClicked()
108
+ diagramApi.getState().resetFocusAndLastClicked()
109
109
  diagramApi.getState().onCanvasContextMenu?.(event as any)
110
110
  },
111
111
  onEdgeContextMenu: (event, xyedge) => {
112
112
  diagramApi.getState().setLastClickedEdge(xyedge.id)
113
- diagramApi.getState().onEdgeContextMenu?.({
114
- edge: xyedge.data.edge,
115
- xyedge,
113
+ diagramApi.getState().onEdgeContextMenu?.(
114
+ xyedge.data.edge,
116
115
  event
117
- })
116
+ )
118
117
  },
119
118
  onNodeClick: (event, xynode) => {
120
119
  const {
@@ -137,39 +136,36 @@ export function useXYFlowEvents() {
137
136
 
138
137
  if (clickedRecently && !!onOpenSourceElement) {
139
138
  onOpenSourceElement(xynode.data.element.id)
139
+ if (!onNodeClick) {
140
+ event.stopPropagation()
141
+ }
140
142
  }
141
143
 
142
144
  if (enableFocusMode || nodesSelectable) {
143
- let stopPropagation = true
144
145
  switch (true) {
145
- case enableFocusMode && !focusedNodeId && clickedRecently:
146
- case enableFocusMode && shallChangeFocus: {
147
- focusOnNode(xynode.id)
146
+ case enableFocusMode && clickedRecently && focusedNodeId === xynode.id: {
147
+ focusOnNode(false)
148
+ fitDiagram()
148
149
  break
149
150
  }
150
- case enableFocusMode && focusedNodeId === xynode.id && clickedRecently: {
151
- fitDiagram()
151
+ case enableFocusMode && shallChangeFocus:
152
+ case enableFocusMode && clickedRecently && focusedNodeId !== xynode.id: {
153
+ focusOnNode(xynode.id)
152
154
  break
153
155
  }
154
156
  case nodesSelectable: {
155
157
  xystore.getState().addSelectedNodes([xynode.id])
156
158
  break
157
159
  }
158
- default: {
159
- stopPropagation = false
160
- }
161
160
  }
162
- if (!onNodeClick && stopPropagation) {
163
- // user did not provide a custom handler, stop propagation
161
+ if (!onNodeClick) {
164
162
  event.stopPropagation()
165
163
  }
166
164
  }
167
- lastClickTimestamp.current = Date.now()
168
- onNodeClick?.({
169
- element: xynode.data.element,
170
- xynode,
165
+ onNodeClick?.(
166
+ xynode.data.element,
171
167
  event
172
- })
168
+ )
173
169
  },
174
170
  onNodeDoubleClick: (event, xynode) => {
175
171
  const {
@@ -196,8 +192,8 @@ export function useXYFlowEvents() {
196
192
  lastClickedEdgeId,
197
193
  isDynamicView,
198
194
  enableDynamicViewWalkthrough,
199
- activateDynamicStep,
200
- activeDynamicViewStep,
195
+ activateWalkthrough,
196
+ activeWalkthrough,
201
197
  focusedNodeId,
202
198
  xystore,
203
199
  nodesSelectable,
@@ -209,22 +205,27 @@ export function useXYFlowEvents() {
209
205
  setLastClickedEdge(xyedge.id)
210
206
  }
211
207
  const isNotAFirstClick = lastClickedEdgeId === xyedge.id
212
- const isEdgeOfFocusedNode = isTruthy(focusedNodeId)
208
+ const connectedToFocusedNode = isTruthy(focusedNodeId)
213
209
  && (focusedNodeId === xyedge.source || focusedNodeId === xyedge.target)
214
210
  if (
215
211
  isDynamicView && enableDynamicViewWalkthrough
216
- && (isEdgeOfFocusedNode || isNotAFirstClick || isNonNullish(activeDynamicViewStep))
212
+ && (connectedToFocusedNode || isNotAFirstClick || isNonNullish(activeWalkthrough))
217
213
  ) {
218
- const nextStep = extractStep(xyedge.data.edge.id)
219
- if (activeDynamicViewStep !== nextStep) {
220
- activateDynamicStep(nextStep)
214
+ const nextStep = xyedge.data.edge.id
215
+ if (activeWalkthrough?.stepId !== nextStep) {
216
+ activateWalkthrough(nextStep)
221
217
  event.stopPropagation()
222
218
  return
223
219
  }
224
220
  }
225
- // if we focused on a node, and clicked on an edge connected to it - focus on the other node
226
- if (isEdgeOfFocusedNode) {
227
- focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source)
221
+ // if we are in focus mode
222
+ if (isTruthy(focusedNodeId)) {
223
+ // if we focused on a node, and clicked on an edge connected to it - focus on the other node
224
+ if (connectedToFocusedNode) {
225
+ focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source)
226
+ } else {
227
+ focusOnNode(xyedge.source)
228
+ }
228
229
  if (!onEdgeClick) {
229
230
  event.stopPropagation()
230
231
  }
@@ -234,11 +235,10 @@ export function useXYFlowEvents() {
234
235
  event.stopPropagation()
235
236
  }
236
237
  }
237
- onEdgeClick?.({
238
- edge: xyedge.data.edge,
239
- xyedge,
238
+ onEdgeClick?.(
239
+ xyedge.data.edge,
240
240
  event
241
- })
241
+ )
242
242
  },
243
243
  onEdgeDoubleClick: (event, xyedge) => {
244
244
  diagramApi.getState().setLastClickedEdge(xyedge.id)
@@ -247,14 +247,14 @@ export function useXYFlowEvents() {
247
247
  isDynamicView,
248
248
  enableDynamicViewWalkthrough,
249
249
  focusOnNode,
250
- activateDynamicStep,
251
- activeDynamicViewStep
250
+ activeWalkthrough,
251
+ activateWalkthrough
252
252
  } = diagramApi.getState()
253
253
  // if we are in dynamic view, and clicked on an edge, activate the step
254
254
  if (isDynamicView && enableDynamicViewWalkthrough) {
255
- const nextStep = extractStep(xyedge.data.edge.id)
256
- if (activeDynamicViewStep !== nextStep) {
257
- activateDynamicStep(extractStep(xyedge.data.edge.id))
255
+ const nextStep = xyedge.data.edge.id
256
+ if (activeWalkthrough?.stepId !== nextStep) {
257
+ activateWalkthrough(nextStep)
258
258
  event.stopPropagation()
259
259
  }
260
260
  return
@@ -7,7 +7,6 @@ import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
7
7
  import DiagramTitlePanel from '../ui/DiagramTitlePanel'
8
8
  import { DynamicViewWalkthrough } from '../ui/DynamicViewWalkthrough'
9
9
  import NotationPanel from '../ui/notation'
10
- import OptionsPanel from '../ui/OptionsPanel'
11
10
  import { TopLeftPanel } from '../ui/top-left/TopLeftPanel'
12
11
  import { XYFlowBackground } from './XYFlowBackground'
13
12
 
@@ -17,7 +16,7 @@ type XYFlowInnerProps = PropsWithChildren<{
17
16
  showDiagramTitle: boolean
18
17
  }>
19
18
 
20
- export const XYFlowInner = memo(function XYFlowInnerR({
19
+ export const XYFlowInner = /* @__PURE__ */ memo(function XYFlowInnerR({
21
20
  children,
22
21
  background,
23
22
  controls,
@@ -27,25 +26,22 @@ export const XYFlowInner = memo(function XYFlowInnerR({
27
26
  showNotations,
28
27
  isDynamicView,
29
28
  isDynamicViewActive,
30
- readonly,
31
29
  enableDynamicViewWalkthrough
32
30
  } = useDiagramState(s => ({
33
31
  showNotations: s.showNotations,
34
32
  isDynamicView: s.isDynamicView,
35
- readonly: s.readonly,
36
33
  enableDynamicViewWalkthrough: s.enableDynamicViewWalkthrough,
37
- isDynamicViewActive: isNonNullish(s.activeDynamicViewStep)
34
+ isDynamicViewActive: isNonNullish(s.activeWalkthrough)
38
35
  }))
39
36
 
40
37
  const isBgWithPattern = background !== 'transparent' && background !== 'solid'
41
38
 
42
- const isDiagramTitleVisible = showDiagramTitle === true && !isDynamicViewActive
39
+ const isDiagramTitleVisible = showDiagramTitle && !isDynamicViewActive
43
40
  return (
44
41
  <>
45
42
  {isBgWithPattern && <XYFlowBackground background={background} />}
46
- {controls && <Controls position={'bottom-right'} />}
43
+ {controls && <Controls showInteractive={false} position={'top-center'} />}
47
44
  <TopLeftPanel />
48
- {readonly !== true && <OptionsPanel />}
49
45
  {isDiagramTitleVisible && <DiagramTitlePanel />}
50
46
  {isDynamicView && enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
51
47
  {showNotations && <NotationPanel />}
@@ -0,0 +1,185 @@
1
+ import { extractStep, isStepEdgeId, type ViewID } from '@likec4/core'
2
+ import {
3
+ ActionIcon,
4
+ Box,
5
+ type BoxProps,
6
+ Button,
7
+ CloseButton,
8
+ Group,
9
+ Popover,
10
+ PopoverDropdown,
11
+ PopoverTarget,
12
+ ScrollAreaAutosize,
13
+ Stack,
14
+ Text
15
+ } from '@mantine/core'
16
+ import { useDebouncedEffect } from '@react-hookz/web'
17
+ import { IconZoomScan } from '@tabler/icons-react'
18
+ import { EdgeLabelRenderer } from '@xyflow/react'
19
+ import clsx from 'clsx'
20
+ import { type PropsWithChildren, type ReactNode, useState } from 'react'
21
+ import { isTruthy } from 'remeda'
22
+ import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from '../../hooks'
23
+ import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
24
+ import type { RelationshipData } from '../types'
25
+ import * as edgesCss from './edges.css'
26
+ import { RelationshipsDropdownMenu } from './RelationshipsDropdownMenu'
27
+
28
+ export interface EdgeLabelProps extends Omit<BoxProps, 'label'> {
29
+ isDimmed: boolean
30
+ edgeData: RelationshipData
31
+ }
32
+
33
+ export const EdgeLabel = ({
34
+ isDimmed,
35
+ edgeData: {
36
+ label,
37
+ edge
38
+ },
39
+ ...props
40
+ }: EdgeLabelProps) => {
41
+ const likec4model = useLikeC4Model()
42
+ const {
43
+ showRelationshipDetails,
44
+ isActiveWalkthroughStep,
45
+ hasOnNavigateTo
46
+ } = useDiagramState(s => ({
47
+ showRelationshipDetails: s.showRelationshipDetails,
48
+ isActiveWalkthroughStep: s.activeWalkthrough?.stepId === edge.id,
49
+ hasOnNavigateTo: !!s.onNavigateTo
50
+ }))
51
+ const notes = isActiveWalkthroughStep ? (edge.notes ?? null) : null
52
+ const stepNum = isStepEdgeId(edge.id) ? extractStep(edge.id) : null
53
+ const navigateTo = hasOnNavigateTo ? edge.navigateTo ?? null : null
54
+
55
+ const wrapWithPopover = (node: ReactNode) => {
56
+ if (isTruthy(notes)) {
57
+ return <NotePopover notes={notes}>{node}</NotePopover>
58
+ }
59
+ if (showRelationshipDetails && likec4model && edge.relations.length > 0) {
60
+ return (
61
+ <RelationshipsDropdownMenu
62
+ disabled={isDimmed}
63
+ likec4model={likec4model}
64
+ edge={edge}>
65
+ {node}
66
+ </RelationshipsDropdownMenu>
67
+ )
68
+ }
69
+ return node
70
+ }
71
+
72
+ return (
73
+ <EdgeLabelRenderer>
74
+ {wrapWithPopover(
75
+ <Box {...props}>
76
+ {stepNum !== null && (
77
+ <Box className={edgesCss.stepEdgeNumber}>
78
+ {stepNum}
79
+ </Box>
80
+ )}
81
+ {isTruthy(label?.text) && (
82
+ <Box className={edgesCss.edgeLabelText}>
83
+ {label.text}
84
+ </Box>
85
+ )}
86
+ {navigateTo && <NavigateToBtn viewId={navigateTo} />}
87
+ </Box>
88
+ )}
89
+ </EdgeLabelRenderer>
90
+ )
91
+ }
92
+
93
+ const NotePopover = ({ notes, children }: PropsWithChildren<{ notes: string }>) => {
94
+ const {
95
+ nextDynamicStep,
96
+ hasNext,
97
+ hasPrevious
98
+ } = useDiagramState(s => ({
99
+ nextDynamicStep: s.nextDynamicStep,
100
+ hasNext: s.activeWalkthrough?.hasNext ?? false,
101
+ hasPrevious: s.activeWalkthrough?.hasPrevious ?? false
102
+ }))
103
+
104
+ const [isOpened, setIsOpened] = useState(false)
105
+ const portalProps = useMantinePortalProps()
106
+
107
+ useDebouncedEffect(
108
+ () => {
109
+ setIsOpened(true)
110
+ },
111
+ [],
112
+ 300
113
+ )
114
+
115
+ return (
116
+ <Popover
117
+ shadow="xs"
118
+ offset={16}
119
+ opened={isOpened}
120
+ closeOnClickOutside={false}
121
+ {...portalProps}>
122
+ <PopoverTarget>
123
+ {children}
124
+ </PopoverTarget>
125
+ <PopoverDropdown
126
+ component={Stack}
127
+ p={'xs'}
128
+ onPointerDownCapture={e => e.stopPropagation()}
129
+ onClick={e => e.stopPropagation()}
130
+ onDoubleClick={e => e.stopPropagation()}
131
+ >
132
+ <ScrollAreaAutosize maw={450} mah={350} type="scroll" mx={'auto'} mt={2}>
133
+ <Text component="div" className={edgesCss.edgeNoteText} p={4}>{notes}</Text>
134
+ </ScrollAreaAutosize>
135
+ <CloseButton
136
+ size={'xs'}
137
+ className={edgesCss.edgeNoteCloseButton}
138
+ onClick={() => setIsOpened(false)}
139
+ />
140
+ {(hasPrevious || hasNext) && (
141
+ <Group gap={0} justify={hasPrevious ? 'flex-start' : 'flex-end'}>
142
+ {hasPrevious && (
143
+ <Button
144
+ variant="subtle"
145
+ radius={'xs'}
146
+ size="compact-xs"
147
+ onClick={() => nextDynamicStep(-1)}>
148
+ back
149
+ </Button>
150
+ )}
151
+ {hasNext && (
152
+ <Button variant="subtle" radius={'xs'} size="compact-xs" onClick={() => nextDynamicStep()}>
153
+ next
154
+ </Button>
155
+ )}
156
+ </Group>
157
+ )}
158
+ </PopoverDropdown>
159
+ </Popover>
160
+ )
161
+ }
162
+
163
+ type NavigateToBtnProps = {
164
+ viewId: ViewID
165
+ }
166
+
167
+ function NavigateToBtn({ viewId }: NavigateToBtnProps) {
168
+ const diagramApi = useDiagramStoreApi()
169
+ return (
170
+ <ActionIcon
171
+ className={clsx('nodrag nopan', edgesCss.cssNavigateBtn)}
172
+ size={'sm'}
173
+ radius="sm"
174
+ onPointerDownCapture={e => e.stopPropagation()}
175
+ onClick={event => {
176
+ event.stopPropagation()
177
+ diagramApi.getState().onNavigateTo?.(viewId, event)
178
+ }}
179
+ role="button"
180
+ onDoubleClick={event => event.stopPropagation()}
181
+ >
182
+ <IconZoomScan className={edgesCss.cssNavigateBtnIcon} />
183
+ </ActionIcon>
184
+ )
185
+ }