@likec4/diagram 1.9.0 → 1.12.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 (112) hide show
  1. package/package.json +18 -41
  2. package/src/LikeC4CustomColors.tsx +1 -1
  3. package/src/LikeC4Diagram.css.ts +1 -1
  4. package/src/LikeC4Diagram.props.ts +40 -94
  5. package/src/LikeC4Diagram.tsx +12 -2
  6. package/src/LikeC4ModelProvider.tsx +49 -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 +3 -0
  13. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  14. package/src/likec4model/index.ts +1 -0
  15. package/src/likec4model/useLikeC4Model.ts +55 -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 +17 -1
  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 +1 -1
  25. package/src/ui/notation/NotationPanel.css.ts +2 -3
  26. package/src/ui/notation/NotationPanel.tsx +9 -10
  27. package/src/ui/options/NodeOptions.tsx +1 -1
  28. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +20 -32
  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 +17 -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 +9 -11
  36. package/src/xyflow/XYFlowEvents.tsx +59 -60
  37. package/src/xyflow/XYFlowInner.tsx +5 -5
  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 +4 -4
  45. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  46. package/src/xyflow/nodes/element/ElementLink.tsx +3 -8
  47. package/src/xyflow/nodes/element/ElementNode.tsx +44 -0
  48. package/src/xyflow/nodes/element/element.css.ts +1 -2
  49. package/src/xyflow/types.ts +1 -24
  50. package/src/xyflow/useLayoutConstraints.ts +10 -6
  51. package/dist/LikeC4CustomColors.d.ts +0 -3
  52. package/dist/LikeC4CustomColors.props.d.ts +0 -5
  53. package/dist/LikeC4Diagram.css.d.ts +0 -4
  54. package/dist/LikeC4Diagram.d.ts +0 -7
  55. package/dist/LikeC4Diagram.props.d.ts +0 -154
  56. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  57. package/dist/globals.css.d.ts +0 -1
  58. package/dist/hooks/index.d.ts +0 -4
  59. package/dist/hooks/useDiagramState.d.ts +0 -6
  60. package/dist/hooks/useSetState.d.ts +0 -3
  61. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  62. package/dist/hooks/useXYFlow.d.ts +0 -267
  63. package/dist/index.d.ts +0 -9
  64. package/dist/index.mjs +0 -3684
  65. package/dist/mantine/EnsureMantine.d.ts +0 -8
  66. package/dist/mantine.css.d.ts +0 -3
  67. package/dist/state/DiagramContext.d.ts +0 -1192
  68. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  69. package/dist/state/diagramStore.d.ts +0 -1268
  70. package/dist/style.css +0 -2139
  71. package/dist/theme.css.d.ts +0 -63
  72. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  73. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  74. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  75. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  76. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  77. package/dist/ui/OptionsPanel.d.ts +0 -1
  78. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  79. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  80. package/dist/ui/notation/index.d.ts +0 -1
  81. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  82. package/dist/ui/options/NodeOptions.d.ts +0 -19
  83. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  84. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  85. package/dist/ui/top-left/_shared.d.ts +0 -5
  86. package/dist/ui/top-left/styles.css.d.ts +0 -3
  87. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  88. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  89. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  90. package/dist/xyflow/XYFlow.d.ts +0 -12
  91. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  92. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  93. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  94. package/dist/xyflow/const.d.ts +0 -7
  95. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  96. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  97. package/dist/xyflow/edges/utils.d.ts +0 -17
  98. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  99. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  100. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  101. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  102. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  103. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  104. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  105. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  106. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  107. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  108. package/dist/xyflow/types.d.ts +0 -50
  109. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  110. package/dist/xyflow/utils.d.ts +0 -18
  111. package/src/mantine.css.ts +0 -7
  112. 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,48 +77,46 @@ 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) => {
119
+ console.log('onNodeClick')
120
120
  const {
121
121
  focusedNodeId,
122
122
  fitDiagram,
@@ -140,36 +140,31 @@ export function useXYFlowEvents() {
140
140
  }
141
141
 
142
142
  if (enableFocusMode || nodesSelectable) {
143
- let stopPropagation = true
144
143
  switch (true) {
145
- case enableFocusMode && !focusedNodeId && clickedRecently:
146
- case enableFocusMode && shallChangeFocus: {
147
- focusOnNode(xynode.id)
144
+ case enableFocusMode && clickedRecently && focusedNodeId === xynode.id: {
145
+ focusOnNode(false)
146
+ fitDiagram()
148
147
  break
149
148
  }
150
- case enableFocusMode && focusedNodeId === xynode.id && clickedRecently: {
151
- fitDiagram()
149
+ case enableFocusMode && shallChangeFocus:
150
+ case enableFocusMode && clickedRecently && focusedNodeId !== xynode.id: {
151
+ focusOnNode(xynode.id)
152
152
  break
153
153
  }
154
154
  case nodesSelectable: {
155
155
  xystore.getState().addSelectedNodes([xynode.id])
156
156
  break
157
157
  }
158
- default: {
159
- stopPropagation = false
160
- }
161
- }
162
- if (!onNodeClick && stopPropagation) {
163
- // user did not provide a custom handler, stop propagation
164
- event.stopPropagation()
165
158
  }
166
159
  }
167
- lastClickTimestamp.current = Date.now()
168
- onNodeClick?.({
169
- element: xynode.data.element,
170
- xynode,
160
+ if (!onNodeClick) {
161
+ // user did not provide a custom handler, stop propagation
162
+ event.stopPropagation()
163
+ }
164
+ onNodeClick?.(
165
+ xynode.data.element,
171
166
  event
172
- })
167
+ )
173
168
  },
174
169
  onNodeDoubleClick: (event, xynode) => {
175
170
  const {
@@ -196,8 +191,8 @@ export function useXYFlowEvents() {
196
191
  lastClickedEdgeId,
197
192
  isDynamicView,
198
193
  enableDynamicViewWalkthrough,
199
- activateDynamicStep,
200
- activeDynamicViewStep,
194
+ activateWalkthrough,
195
+ activeWalkthrough,
201
196
  focusedNodeId,
202
197
  xystore,
203
198
  nodesSelectable,
@@ -209,22 +204,27 @@ export function useXYFlowEvents() {
209
204
  setLastClickedEdge(xyedge.id)
210
205
  }
211
206
  const isNotAFirstClick = lastClickedEdgeId === xyedge.id
212
- const isEdgeOfFocusedNode = isTruthy(focusedNodeId)
207
+ const connectedToFocusedNode = isTruthy(focusedNodeId)
213
208
  && (focusedNodeId === xyedge.source || focusedNodeId === xyedge.target)
214
209
  if (
215
210
  isDynamicView && enableDynamicViewWalkthrough
216
- && (isEdgeOfFocusedNode || isNotAFirstClick || isNonNullish(activeDynamicViewStep))
211
+ && (connectedToFocusedNode || isNotAFirstClick || isNonNullish(activeWalkthrough))
217
212
  ) {
218
- const nextStep = extractStep(xyedge.data.edge.id)
219
- if (activeDynamicViewStep !== nextStep) {
220
- activateDynamicStep(nextStep)
213
+ const nextStep = xyedge.data.edge.id
214
+ if (activeWalkthrough?.stepId !== nextStep) {
215
+ activateWalkthrough(nextStep)
221
216
  event.stopPropagation()
222
217
  return
223
218
  }
224
219
  }
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)
220
+ // if we are in focus mode
221
+ if (isTruthy(focusedNodeId)) {
222
+ // if we focused on a node, and clicked on an edge connected to it - focus on the other node
223
+ if (connectedToFocusedNode) {
224
+ focusOnNode(focusedNodeId === xyedge.source ? xyedge.target : xyedge.source)
225
+ } else {
226
+ focusOnNode(xyedge.source)
227
+ }
228
228
  if (!onEdgeClick) {
229
229
  event.stopPropagation()
230
230
  }
@@ -234,11 +234,10 @@ export function useXYFlowEvents() {
234
234
  event.stopPropagation()
235
235
  }
236
236
  }
237
- onEdgeClick?.({
238
- edge: xyedge.data.edge,
239
- xyedge,
237
+ onEdgeClick?.(
238
+ xyedge.data.edge,
240
239
  event
241
- })
240
+ )
242
241
  },
243
242
  onEdgeDoubleClick: (event, xyedge) => {
244
243
  diagramApi.getState().setLastClickedEdge(xyedge.id)
@@ -247,14 +246,14 @@ export function useXYFlowEvents() {
247
246
  isDynamicView,
248
247
  enableDynamicViewWalkthrough,
249
248
  focusOnNode,
250
- activateDynamicStep,
251
- activeDynamicViewStep
249
+ activeWalkthrough,
250
+ activateWalkthrough
252
251
  } = diagramApi.getState()
253
252
  // if we are in dynamic view, and clicked on an edge, activate the step
254
253
  if (isDynamicView && enableDynamicViewWalkthrough) {
255
- const nextStep = extractStep(xyedge.data.edge.id)
256
- if (activeDynamicViewStep !== nextStep) {
257
- activateDynamicStep(extractStep(xyedge.data.edge.id))
254
+ const nextStep = xyedge.data.edge.id
255
+ if (activeWalkthrough?.stepId !== nextStep) {
256
+ activateWalkthrough(nextStep)
258
257
  event.stopPropagation()
259
258
  }
260
259
  return
@@ -17,7 +17,7 @@ type XYFlowInnerProps = PropsWithChildren<{
17
17
  showDiagramTitle: boolean
18
18
  }>
19
19
 
20
- export const XYFlowInner = memo(function XYFlowInnerR({
20
+ export const XYFlowInner = /* @__PURE__ */ memo(function XYFlowInnerR({
21
21
  children,
22
22
  background,
23
23
  controls,
@@ -34,18 +34,18 @@ export const XYFlowInner = memo(function XYFlowInnerR({
34
34
  isDynamicView: s.isDynamicView,
35
35
  readonly: s.readonly,
36
36
  enableDynamicViewWalkthrough: s.enableDynamicViewWalkthrough,
37
- isDynamicViewActive: isNonNullish(s.activeDynamicViewStep)
37
+ isDynamicViewActive: isNonNullish(s.activeWalkthrough)
38
38
  }))
39
39
 
40
40
  const isBgWithPattern = background !== 'transparent' && background !== 'solid'
41
41
 
42
- const isDiagramTitleVisible = showDiagramTitle === true && !isDynamicViewActive
42
+ const isDiagramTitleVisible = showDiagramTitle && !isDynamicViewActive
43
43
  return (
44
44
  <>
45
45
  {isBgWithPattern && <XYFlowBackground background={background} />}
46
- {controls && <Controls position={'bottom-right'} />}
46
+ {controls && <Controls showInteractive={false} position={'top-center'} />}
47
47
  <TopLeftPanel />
48
- {readonly !== true && <OptionsPanel />}
48
+ {!readonly && <OptionsPanel />}
49
49
  {isDiagramTitleVisible && <DiagramTitlePanel />}
50
50
  {isDynamicView && enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
51
51
  {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
+ }