@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.
- package/dist/KeepAspectRatioContainer.d.ts +9 -0
- package/dist/LikeC4Diagram.css.d.ts +1 -0
- package/dist/index.mjs +243 -678
- package/dist/state/DiagramContext.d.ts +36 -9
- package/dist/state/diagramStore.d.ts +42 -12
- package/dist/state/useDiagramStore.d.ts +36 -9
- package/dist/style.css +600 -53
- package/dist/ui/index.d.ts +0 -1
- package/dist/xyflow/{XYFlowWrapper.d.ts → XYFlow.d.ts} +2 -2
- package/dist/xyflow/XYFlowEvents.d.ts +127 -13
- package/dist/xyflow/edges/RelationshipEdge.d.ts +2 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +20 -20
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +7 -5
- package/dist/xyflow/types.d.ts +2 -2
- package/package.json +9 -9
- package/src/{ui/KeepAspectRatio.tsx → KeepAspectRatioContainer.tsx} +7 -6
- package/src/LikeC4Diagram.css.ts +6 -0
- package/src/LikeC4Diagram.tsx +6 -6
- package/src/globals.css.ts +42 -37
- package/src/state/diagramStore.ts +26 -7
- package/src/ui/index.ts +0 -1
- package/src/xyflow/FitviewOnDiagramChange.tsx +24 -18
- package/src/xyflow/SyncWithDiagram.tsx +16 -10
- package/src/xyflow/{XYFlowWrapper.tsx → XYFlow.tsx} +17 -24
- package/src/xyflow/XYFlowEvents.tsx +96 -43
- package/src/xyflow/diagram-to-xyflow.ts +4 -4
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +85 -39
- package/src/xyflow/nodes/compound/CompoundNode.tsx +29 -31
- package/src/xyflow/nodes/element/ElementNode.tsx +2 -2
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +2 -1
- package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
- package/dist/ui/KeepAspectRatio.d.ts +0 -9
- 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,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
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] =
|
|
40
|
+
const [pendingChangeId, setPending] = useState(processedChangeId)
|
|
42
41
|
|
|
43
42
|
const isReady = useNodesInitialized({
|
|
44
43
|
includeHiddenNodes: true
|
|
45
44
|
})
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
() => {
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
78
|
+
const v = xyflowRef.current.getViewport()
|
|
75
79
|
if (nextZoom < v.zoom) {
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
{...
|
|
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 {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
27
|
-
diagramApi.
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
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()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
},
|
|
121
|
+
// measured: {
|
|
122
|
+
// width: node.width,
|
|
123
|
+
// height: node.height
|
|
124
|
+
// },
|
|
125
125
|
...(parent && {
|
|
126
126
|
parentId: ns + parent.id
|
|
127
127
|
})
|