@likec4/diagram 1.0.1 → 1.1.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 (33) hide show
  1. package/dist/KeepAspectRatioContainer.d.ts +9 -0
  2. package/dist/LikeC4Diagram.css.d.ts +1 -0
  3. package/dist/index.mjs +243 -678
  4. package/dist/state/DiagramContext.d.ts +36 -9
  5. package/dist/state/diagramStore.d.ts +42 -12
  6. package/dist/state/useDiagramStore.d.ts +36 -9
  7. package/dist/style.css +600 -53
  8. package/dist/ui/index.d.ts +0 -1
  9. package/dist/xyflow/{XYFlowWrapper.d.ts → XYFlow.d.ts} +2 -2
  10. package/dist/xyflow/XYFlowEvents.d.ts +127 -13
  11. package/dist/xyflow/edges/RelationshipEdge.d.ts +2 -2
  12. package/dist/xyflow/hooks/useXYFlow.d.ts +20 -20
  13. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +7 -5
  14. package/dist/xyflow/types.d.ts +2 -2
  15. package/package.json +9 -9
  16. package/src/{ui/KeepAspectRatio.tsx → KeepAspectRatioContainer.tsx} +7 -6
  17. package/src/LikeC4Diagram.css.ts +6 -0
  18. package/src/LikeC4Diagram.tsx +6 -6
  19. package/src/globals.css.ts +42 -37
  20. package/src/state/diagramStore.ts +26 -7
  21. package/src/ui/index.ts +0 -1
  22. package/src/xyflow/FitviewOnDiagramChange.tsx +24 -18
  23. package/src/xyflow/SyncWithDiagram.tsx +16 -10
  24. package/src/xyflow/{XYFlowWrapper.tsx → XYFlow.tsx} +17 -24
  25. package/src/xyflow/XYFlowEvents.tsx +96 -43
  26. package/src/xyflow/diagram-to-xyflow.ts +4 -4
  27. package/src/xyflow/nodes/compound/CompoundNode.css.ts +85 -39
  28. package/src/xyflow/nodes/compound/CompoundNode.tsx +29 -31
  29. package/src/xyflow/nodes/element/ElementNode.tsx +2 -2
  30. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +2 -1
  31. package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
  32. package/dist/ui/KeepAspectRatio.d.ts +0 -9
  33. package/src/ui/KeepAspectRatio.css.ts +0 -7
@@ -11,7 +11,7 @@ import { shallow } from 'zustand/shallow'
11
11
  import { createWithEqualityFn } from 'zustand/traditional'
12
12
  import type { Change, DiagramNodeWithNavigate, LikeC4DiagramEventHandlers } from '../LikeC4Diagram.props'
13
13
  import { MinZoom } from '../xyflow/const'
14
- import type { XYFlowInstance } from '../xyflow/types'
14
+ import type { XYFlowInstance, XYFlowNode } from '../xyflow/types'
15
15
 
16
16
  export type DiagramStore = {
17
17
  // Incoming props
@@ -20,6 +20,8 @@ export type DiagramStore = {
20
20
  showElementLinks: boolean
21
21
  fitViewEnabled: boolean
22
22
  fitViewPadding: number
23
+ zoomable: boolean
24
+ pannable: boolean
23
25
  nodesDraggable: boolean
24
26
  nodesSelectable: boolean
25
27
 
@@ -30,6 +32,7 @@ export type DiagramStore = {
30
32
  // This is XYFlow id's
31
33
  lastClickedNodeId: string | null
32
34
  lastClickedEdgeId: string | null
35
+ focusedNodeId: string | null
33
36
  hoveredNodeId: string | null
34
37
  hoveredEdgeId: string | null
35
38
 
@@ -57,6 +60,8 @@ export type DiagramInitialState = // Required properties
57
60
  | 'showElementLinks'
58
61
  | 'fitViewEnabled'
59
62
  | 'fitViewPadding'
63
+ | 'zoomable'
64
+ | 'pannable'
60
65
  | 'nodesDraggable'
61
66
  | 'nodesSelectable'
62
67
  >
@@ -81,7 +86,7 @@ interface DiagramStoreActions {
81
86
  getElement(id: Fqn): DiagramNode | null
82
87
  triggerOnChange: (changes: NonEmptyArray<Change>) => void
83
88
  triggerOnNavigateTo: (xynodeId: string, event: ReactMouseEvent) => void
84
- fitDiagram: () => void
89
+ fitDiagram: (centerNode?: XYFlowNode) => void
85
90
  }
86
91
 
87
92
  export type DiagramState = Simplify<DiagramStore & DiagramStoreActions>
@@ -90,6 +95,7 @@ const DEFAULT_PROPS: Except<DiagramStore, RequiredKeysOf<DiagramInitialState> |
90
95
  initialized: false,
91
96
  previousViews: [],
92
97
  viewportChanged: false,
98
+ focusedNodeId: null,
93
99
  hoveredNodeId: null,
94
100
  hoveredEdgeId: null,
95
101
  lastClickedNodeId: null,
@@ -133,6 +139,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
133
139
  view: currentView,
134
140
  lastOnNavigate,
135
141
  previousViews,
142
+ focusedNodeId,
136
143
  lastClickedNodeId,
137
144
  lastClickedEdgeId,
138
145
  hoveredEdgeId,
@@ -159,6 +166,9 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
159
166
  if (hoveredNodeId && !containsWithId(view.nodes, hoveredNodeId)) {
160
167
  hoveredNodeId = null
161
168
  }
169
+ if (focusedNodeId && !containsWithId(view.nodes, focusedNodeId)) {
170
+ focusedNodeId = null
171
+ }
162
172
  if (lastClickedEdgeId && !containsWithId(view.edges, lastClickedEdgeId)) {
163
173
  lastClickedEdgeId = null
164
174
  }
@@ -171,6 +181,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
171
181
  lastClickedNodeId = null
172
182
  hoveredEdgeId = null
173
183
  hoveredNodeId = null
184
+ focusedNodeId = null
174
185
 
175
186
  // Update history stack (back button not implemented yet)
176
187
  previousViews = [
@@ -186,6 +197,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
186
197
  previousViews,
187
198
  lastClickedNodeId,
188
199
  lastClickedEdgeId,
200
+ focusedNodeId,
189
201
  hoveredEdgeId,
190
202
  hoveredNodeId
191
203
  },
@@ -336,15 +348,22 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
336
348
  )
337
349
  },
338
350
 
339
- fitDiagram: () => {
340
- const { fitViewPadding, xyflow } = get()
351
+ fitDiagram: (centerNode) => {
352
+ const { fitViewPadding, xyflow, focusedNodeId } = get()
341
353
  xyflow.fitView({
342
354
  includeHiddenNodes: true,
343
- duration: 400,
344
- padding: fitViewPadding,
355
+ duration: (centerNode && focusedNodeId) ? 300 : 400,
356
+ padding: centerNode ? 0 : fitViewPadding,
345
357
  minZoom: MinZoom,
346
- maxZoom: 1
358
+ maxZoom: 1,
359
+ ...(centerNode && { nodes: [centerNode] })
347
360
  })
361
+ if (centerNode && focusedNodeId !== centerNode.id) {
362
+ set({ focusedNodeId: centerNode.id }, noReplace, 'focus node')
363
+ }
364
+ if (!centerNode && !!focusedNodeId) {
365
+ set({ focusedNodeId: null }, noReplace, 'unfocus')
366
+ }
348
367
  }
349
368
  }),
350
369
  {
package/src/ui/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  // export { default as Camera } from './Camera'
2
- export { KeepAspectRatio } from './KeepAspectRatio'
3
2
  export { default as OptionsPanel } from './OptionsPanel'
@@ -1,6 +1,5 @@
1
- import { useDebouncedEffect, useDebouncedState, useRafEffect } from '@react-hookz/web'
1
+ import { useDebouncedCallback, useDebouncedEffect, useRafEffect } from '@react-hookz/web'
2
2
  import { useNodesInitialized } from '@xyflow/react'
3
- import { shallowEqual } from 'fast-equals'
4
3
  import { memo, useEffect, useRef, useState } from 'react'
5
4
  import { type DiagramState, type DiagramStoreApi, useDiagramState, useDiagramStoreApi } from '../state'
6
5
  import { useXYStore } from './hooks'
@@ -38,28 +37,35 @@ function FitViewOnDiagramChanges() {
38
37
  const diagramApi = useDiagramStoreApi()
39
38
  const [viewportMoved, setViewportMoved] = useState(diagramApi.getState().viewportChanged)
40
39
  const [processedChangeId, setProcessed] = useState(diagramChangeId(diagramApi.getState()))
41
- const [pendingChangeId, setPending] = useDebouncedState(processedChangeId, 100)
40
+ const [pendingChangeId, setPending] = useState(processedChangeId)
42
41
 
43
42
  const isReady = useNodesInitialized({
44
43
  includeHiddenNodes: true
45
44
  })
46
45
 
47
- useEffect(
48
- () => {
49
- return diagramApi.subscribe(
50
- s => ({
51
- viewportMoved: s.viewportChanged,
52
- changeid: diagramChangeId(s)
53
- }),
54
- ({ viewportMoved, changeid }) => {
55
- setPending(changeid)
56
- setViewportMoved(viewportMoved)
57
- },
58
- {
59
- equalityFn: shallowEqual
60
- }
61
- )
46
+ const schedulePending = useDebouncedCallback(
47
+ (pendingChangeId: string) => {
48
+ setPending(pendingChangeId)
62
49
  },
50
+ [setPending],
51
+ 75
52
+ )
53
+
54
+ useEffect(
55
+ () =>
56
+ diagramApi.subscribe(
57
+ s => diagramChangeId(s),
58
+ schedulePending
59
+ ),
60
+ [diagramApi, schedulePending]
61
+ )
62
+
63
+ useEffect(
64
+ () =>
65
+ diagramApi.subscribe(
66
+ s => s.viewportChanged,
67
+ setViewportMoved
68
+ ),
63
69
  [diagramApi]
64
70
  )
65
71
 
@@ -2,7 +2,7 @@ import { deepEqual as eq, shallowEqual } from 'fast-equals'
2
2
  import { useXYFlow, useXYStoreApi } from './hooks'
3
3
  import type { XYFlowNode } from './types'
4
4
 
5
- import { memo, useEffect } from 'react'
5
+ import { memo, useEffect, useRef } from 'react'
6
6
  import { useDiagramStoreApi } from '../state'
7
7
  import { diagramViewToXYFlowData } from './diagram-to-xyflow'
8
8
  import { toDomPrecision } from './utils'
@@ -14,7 +14,10 @@ const isSameNode = (a: XYFlowNode) => (b: XYFlowNode) =>
14
14
  * Syncs the diagram state with the XYFlow instance
15
15
  */
16
16
  export const SyncWithDiagram = memo(() => {
17
- const xyflow = useXYFlow()
17
+ const _xyflow = useXYFlow()
18
+ const xyflowRef = useRef(_xyflow)
19
+ xyflowRef.current = _xyflow
20
+
18
21
  const xyflowApi = useXYStoreApi()
19
22
  const diagramStoreApi = useDiagramStoreApi()
20
23
 
@@ -33,7 +36,7 @@ export const SyncWithDiagram = memo(() => {
33
36
  const { lastOnNavigate } = diagramStoreApi.getState()
34
37
  const updates = diagramViewToXYFlowData({ nodes, edges }, opts)
35
38
 
36
- xyflow.setNodes(prev =>
39
+ xyflowRef.current.setNodes(prev =>
37
40
  updates.nodes.map(update => {
38
41
  const existing = prev.find(isSameNode(update))
39
42
  if (existing) {
@@ -49,7 +52,7 @@ export const SyncWithDiagram = memo(() => {
49
52
  })
50
53
  )
51
54
 
52
- xyflow.setEdges(prev =>
55
+ xyflowRef.current.setEdges(prev =>
53
56
  updates.edges.map(update => {
54
57
  const existing = prev.find(e => e.id === update.id)
55
58
  if (existing) {
@@ -62,24 +65,27 @@ export const SyncWithDiagram = memo(() => {
62
65
  })
63
66
  )
64
67
 
65
- if (lastOnNavigate?.toView === viewId && !lastOnNavigate.positionCorrected) {
68
+ if (lastOnNavigate?.toView === viewId && lastOnNavigate.positionCorrected !== true) {
66
69
  const elFrom = lastOnNavigate.element
67
70
  const elTo = nodes.find(n => n.id === elFrom.id)
68
71
  if (elTo) {
69
72
  const centerFrom = lastOnNavigate.elementCenterScreenPosition
70
- const nextZoom = Math.min(elFrom.width / elTo.width, elFrom.height / elTo.height)
73
+ let nextZoom = Math.min(elFrom.width / elTo.width, elFrom.height / elTo.height)
71
74
 
75
+ // TODO: investigate why 2 animation frames are needed
72
76
  requestAnimationFrame(() => {
73
77
  requestAnimationFrame(() => {
74
- const v = xyflow.getViewport()
78
+ const v = xyflowRef.current.getViewport()
75
79
  if (nextZoom < v.zoom) {
76
- xyflow.setViewport({
80
+ nextZoom = Math.max(nextZoom, v.zoom * 0.5)
81
+ xyflowRef.current.setViewport({
77
82
  x: v.x,
78
83
  y: v.y,
79
84
  zoom: nextZoom
80
85
  })
81
86
  }
82
- const centerTo = xyflow.flowToScreenPosition({
87
+
88
+ const centerTo = xyflowRef.current.flowToScreenPosition({
83
89
  x: elTo.position[0] + elTo.width / 2,
84
90
  y: elTo.position[1] + elTo.height / 2
85
91
  }),
@@ -107,7 +113,7 @@ export const SyncWithDiagram = memo(() => {
107
113
  equalityFn: shallowEqual
108
114
  }
109
115
  )
110
- }, [xyflow, xyflowApi, diagramStoreApi])
116
+ }, [xyflowApi, diagramStoreApi])
111
117
 
112
118
  return null
113
119
  })
@@ -26,8 +26,6 @@ type OnlyExpectedProps = Required<
26
26
  LikeC4DiagramProperties,
27
27
  | 'className'
28
28
  | 'controls'
29
- | 'pannable'
30
- | 'zoomable'
31
29
  | 'background'
32
30
  >
33
31
  >
@@ -53,7 +51,8 @@ const selector = (s: DiagramState) => ({
53
51
  hasOnCanvasContextMenu: !!s.onCanvasContextMenu,
54
52
  hasOnEdgeContextMenu: !!s.onEdgeContextMenu,
55
53
  hasOnEdgeClick: !!s.onEdgeClick,
56
- hasOnCanvasClick: !!s.onCanvasClick || !!s.onCanvasDblClick
54
+ zoomable: s.zoomable,
55
+ pannable: s.pannable
57
56
  })
58
57
 
59
58
  function XYFlowWrapper({
@@ -61,8 +60,6 @@ function XYFlowWrapper({
61
60
  children,
62
61
  defaultNodes,
63
62
  defaultEdges,
64
- pannable,
65
- zoomable,
66
63
  controls,
67
64
  background,
68
65
  style
@@ -73,6 +70,8 @@ function XYFlowWrapper({
73
70
  nodesDraggable,
74
71
  fitView,
75
72
  fitViewPadding,
73
+ pannable,
74
+ zoomable,
76
75
  ...editor
77
76
  } = useDiagramState(selector)
78
77
 
@@ -120,33 +119,27 @@ function XYFlowWrapper({
120
119
  selectionKeyCode: null
121
120
  })}
122
121
  nodesDraggable={nodesDraggable}
123
- {...(nodesDraggable && layoutConstraints)}
122
+ {...nodesDraggable && {
123
+ onNodeDragStart: layoutConstraints.onNodeDragStart,
124
+ onNodeDrag: layoutConstraints.onNodeDrag,
125
+ onNodeDragStop: layoutConstraints.onNodeDragStop
126
+ }}
124
127
  zoomOnDoubleClick={false}
125
128
  elevateNodesOnSelect={false} // or edges are not visible after select
126
129
  selectNodesOnDrag={false} // or weird camera movement
130
+ onDoubleClick={handlers.onDoubleClick}
127
131
  onPaneClick={handlers.onPaneClick}
128
- {...(editor.hasOnNodeClick && {
129
- onNodeClick: handlers.onNodeClick
130
- })}
131
- {...(editor.hasOnEdgeClick && {
132
- onEdgeClick: handlers.onEdgeClick
133
- })}
134
132
  onMoveEnd={handlers.onMoveEnd}
133
+ onNodeMouseEnter={handlers.onNodeMouseEnter}
134
+ onNodeMouseLeave={handlers.onNodeMouseLeave}
135
+ onEdgeMouseEnter={handlers.onEdgeMouseEnter}
136
+ onEdgeMouseLeave={handlers.onEdgeMouseLeave}
137
+ onNodeClick={handlers.onNodeClick}
138
+ onNodeDoubleClick={handlers.onNodeDoubleClick}
139
+ onEdgeClick={handlers.onEdgeClick}
135
140
  onInit={() => {
136
141
  diagramApi.setState({ initialized: true }, false, 'initialized')
137
142
  }}
138
- onNodeMouseEnter={(_event, node) => {
139
- diagramApi.getState().setHoveredNode(node.id)
140
- }}
141
- onNodeMouseLeave={() => {
142
- diagramApi.getState().setHoveredNode(null)
143
- }}
144
- onEdgeMouseEnter={(_event, edge) => {
145
- diagramApi.getState().setHoveredEdge(edge.id)
146
- }}
147
- onEdgeMouseLeave={() => {
148
- diagramApi.getState().setHoveredEdge(null)
149
- }}
150
143
  {...(editor.hasOnNodeContextMenu && {
151
144
  onNodeContextMenu: handlers.onNodeContextMenu
152
145
  })}
@@ -1,20 +1,30 @@
1
- import type { EdgeMouseHandler, NodeMouseHandler, OnMoveEnd } from '@xyflow/react'
2
- import type { MouseEvent as ReactMouseEvent } from 'react'
1
+ import type { ReactFlowProps } from '@xyflow/react'
3
2
  import { useMemo, useRef } from 'react'
3
+ import type { Simplify } from 'type-fest'
4
4
  import { useDiagramStoreApi } from '../state'
5
5
  import { useXYStoreApi } from './hooks'
6
6
  import type { XYFlowEdge, XYFlowNode } from './types'
7
7
 
8
- type XYFlowEventHandlers = {
9
- onPaneClick: (event: ReactMouseEvent) => void
10
- onNodeContextMenu: NodeMouseHandler<XYFlowNode>
11
- onEdgeContextMenu: EdgeMouseHandler<XYFlowEdge>
12
- onPaneContextMenu: (event: ReactMouseEvent | MouseEvent) => void
13
- onNodeClick: NodeMouseHandler<XYFlowNode>
14
- onEdgeClick: EdgeMouseHandler<XYFlowEdge>
15
- // onMoveStart: OnMoveStart
16
- onMoveEnd: OnMoveEnd
17
- }
8
+ type XYFlowEventHandlers = Simplify<
9
+ Required<
10
+ Pick<
11
+ ReactFlowProps<XYFlowNode, XYFlowEdge>,
12
+ | 'onDoubleClick'
13
+ | 'onPaneClick'
14
+ | 'onNodeClick'
15
+ | 'onNodeDoubleClick'
16
+ | 'onEdgeClick'
17
+ | 'onMoveEnd'
18
+ | 'onNodeContextMenu'
19
+ | 'onEdgeContextMenu'
20
+ | 'onPaneContextMenu'
21
+ | 'onNodeMouseEnter'
22
+ | 'onNodeMouseLeave'
23
+ | 'onEdgeMouseEnter'
24
+ | 'onEdgeMouseLeave'
25
+ >
26
+ >
27
+ >
18
28
 
19
29
  export function useXYFlowEvents() {
20
30
  const diagramApi = useDiagramStoreApi()
@@ -22,36 +32,47 @@ export function useXYFlowEvents() {
22
32
 
23
33
  const dblclickTimeout = useRef<number>()
24
34
 
35
+ const dbclickGuarg = () => {
36
+ if (dblclickTimeout.current !== undefined) {
37
+ return true
38
+ }
39
+ dblclickTimeout.current = window.setTimeout(() => {
40
+ dblclickTimeout.current = undefined
41
+ }, 250)
42
+ return false
43
+ }
44
+
25
45
  return useMemo<XYFlowEventHandlers>(() => ({
26
- onPaneClick: (event) => {
27
- diagramApi.setState(
28
- {
29
- lastClickedNodeId: null,
30
- lastClickedEdgeId: null
31
- },
32
- false,
33
- 'onPaneClick'
34
- )
46
+ onDoubleClick: (event) => {
47
+ diagramApi.getState().setLastClickedNode(null)
48
+ diagramApi.getState().setLastClickedEdge(null)
35
49
  const diagramState = diagramApi.getState()
36
-
37
- if (dblclickTimeout.current) {
38
- window.clearTimeout(dblclickTimeout.current)
39
- dblclickTimeout.current = undefined
40
- if (diagramState.onCanvasDblClick) {
41
- diagramState.onCanvasDblClick(event)
42
- return
43
- }
44
- diagramState.onCanvasClick?.(event)
45
- if (diagramState.fitViewEnabled && diagramState.viewportChanged) {
46
- diagramState.fitDiagram()
47
- }
50
+ if (diagramState.onCanvasDblClick) {
51
+ diagramState.onCanvasDblClick(event)
48
52
  return
49
53
  }
50
-
51
- dblclickTimeout.current = window.setTimeout(() => {
52
- dblclickTimeout.current = undefined
53
- diagramApi.getState().onCanvasClick?.(event)
54
- }, 300)
54
+ if (diagramState.fitViewEnabled && diagramState.zoomable) {
55
+ diagramState.fitDiagram()
56
+ event.stopPropagation()
57
+ return
58
+ }
59
+ },
60
+ onPaneClick: (event) => {
61
+ if (dbclickGuarg()) {
62
+ return
63
+ }
64
+ diagramApi.getState().setLastClickedNode(null)
65
+ diagramApi.getState().setLastClickedEdge(null)
66
+ const { focusedNodeId, fitDiagram, onCanvasClick } = diagramApi.getState()
67
+ if (onCanvasClick) {
68
+ onCanvasClick(event)
69
+ return
70
+ }
71
+ // reset focus if clicked on empty space
72
+ if (focusedNodeId) {
73
+ fitDiagram()
74
+ event.stopPropagation()
75
+ }
55
76
  },
56
77
  onNodeContextMenu: (event, xynode) => {
57
78
  diagramApi.getState().setLastClickedNode(xynode.id)
@@ -76,11 +97,30 @@ export function useXYFlowEvents() {
76
97
  },
77
98
  onNodeClick: (event, xynode) => {
78
99
  diagramApi.getState().setLastClickedNode(xynode.id)
79
- diagramApi.getState().onNodeClick?.({
80
- element: xynode.data.element,
81
- xynode,
82
- event
83
- })
100
+ const { focusedNodeId, fitDiagram, onNodeClick } = diagramApi.getState()
101
+ if (onNodeClick) {
102
+ onNodeClick({
103
+ element: xynode.data.element,
104
+ xynode,
105
+ event
106
+ })
107
+ return
108
+ }
109
+ // if we clicked on a node that is not focused, fit the diagram
110
+ if (!!focusedNodeId && focusedNodeId !== xynode.id) {
111
+ fitDiagram(xynode)
112
+ event.stopPropagation()
113
+ }
114
+ },
115
+ onNodeDoubleClick: (event, xynode) => {
116
+ const { zoomable, fitDiagram, onNodeClick } = diagramApi.getState()
117
+ if (onNodeClick) {
118
+ return
119
+ }
120
+ if (zoomable) {
121
+ fitDiagram(xynode)
122
+ event.stopPropagation()
123
+ }
84
124
  },
85
125
  onEdgeClick: (event, xyedge) => {
86
126
  diagramApi.getState().setLastClickedEdge(xyedge.id)
@@ -91,10 +131,23 @@ export function useXYFlowEvents() {
91
131
  })
92
132
  },
93
133
  onMoveEnd: (event, _viewport) => {
134
+ // if event is present, the move was triggered by user
94
135
  const viewportChanged = !!event
95
136
  if (viewportChanged !== diagramApi.getState().viewportChanged) {
96
137
  diagramApi.setState({ viewportChanged }, false, `viewport-changed: ${viewportChanged}`)
97
138
  }
139
+ },
140
+ onNodeMouseEnter: (event, xynode) => {
141
+ diagramApi.getState().setHoveredNode(xynode.id)
142
+ },
143
+ onNodeMouseLeave: (event, xynode) => {
144
+ diagramApi.getState().setHoveredNode(null)
145
+ },
146
+ onEdgeMouseEnter: (event, xyedge) => {
147
+ diagramApi.getState().setHoveredEdge(xyedge.id)
148
+ },
149
+ onEdgeMouseLeave: (event, xyedge) => {
150
+ diagramApi.getState().setHoveredEdge(null)
98
151
  }
99
152
  }), [diagramApi, xyflowApi])
100
153
  }
@@ -118,10 +118,10 @@ export function diagramViewToXYFlowData(
118
118
  initialHeight: node.height, */
119
119
  width: node.width,
120
120
  height: node.height,
121
- measured: {
122
- width: node.width,
123
- height: node.height
124
- },
121
+ // measured: {
122
+ // width: node.width,
123
+ // height: node.height
124
+ // },
125
125
  ...(parent && {
126
126
  parentId: ns + parent.id
127
127
  })