@likec4/diagram 1.15.1 → 1.17.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 (47) hide show
  1. package/package.json +7 -7
  2. package/src/LikeC4CustomColors.tsx +24 -21
  3. package/src/LikeC4Diagram.props.ts +9 -9
  4. package/src/LikeC4Diagram.tsx +25 -26
  5. package/src/StaticLikeC4Diagram.tsx +9 -3
  6. package/src/overlays/OverlayContext.tsx +1 -1
  7. package/src/overlays/Overlays.css.ts +28 -23
  8. package/src/overlays/Overlays.tsx +116 -64
  9. package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
  10. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
  11. package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
  12. package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
  13. package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
  14. package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
  15. package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
  16. package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
  17. package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
  18. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
  19. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
  20. package/src/overlays/relationships-of/_types.ts +15 -4
  21. package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
  22. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
  23. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
  24. package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
  25. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
  26. package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
  27. package/src/state/DiagramContext.tsx +15 -8
  28. package/src/state/diagramStore.ts +14 -6
  29. package/src/theme-vars.ts +9 -0
  30. package/src/ui/FramerMotionConfig.tsx +18 -0
  31. package/src/ui/notation/NotationPanel.tsx +3 -3
  32. package/src/xyflow/XYFlow.tsx +18 -35
  33. package/src/xyflow/XYFlowInner.tsx +2 -2
  34. package/src/xyflow/edges/EdgeLabel.tsx +3 -3
  35. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
  36. package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
  37. package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
  38. package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
  39. package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
  40. package/src/xyflow/nodes/element/ElementNode.tsx +110 -116
  41. package/src/xyflow/nodes/element/element.css.ts +38 -18
  42. package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
  43. package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
  44. package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
  45. package/src/xyflow/utils.ts +12 -3
  46. package/src/LikeC4CustomColors.props.ts +0 -5
  47. /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
@@ -1,21 +1,31 @@
1
- import { type Fqn, isAncestor, type Relation } from '@likec4/core'
1
+ import { delay, type DiagramView, type Fqn, isAncestor, type Relation } from '@likec4/core'
2
2
  import { Box, Button, Group, SegmentedControl, Space, Text } from '@mantine/core'
3
- import { useLocalStorage, useStateHistory } from '@mantine/hooks'
4
- import { useDebouncedCallback, useSyncedRef } from '@react-hookz/web'
3
+ import { useId, useLocalStorage, useStateHistory } from '@mantine/hooks'
4
+ import { useDebouncedCallback, useDeepCompareEffect, useSyncedRef } from '@react-hookz/web'
5
5
  import { IconArrowLeft, IconArrowRight } from '@tabler/icons-react'
6
6
  import {
7
+ Background,
8
+ BackgroundVariant,
7
9
  getViewportForBounds,
8
10
  Panel,
9
11
  ReactFlow,
10
12
  type ReactFlowInstance,
13
+ type ReactFlowProps,
14
+ ReactFlowProvider,
15
+ type ReactFlowState,
16
+ useOnViewportChange,
11
17
  useReactFlow,
18
+ useStore,
12
19
  useStoreApi
13
20
  } from '@xyflow/react'
14
21
  import { getNodeDimensions } from '@xyflow/system'
15
- import { memo, useEffect, useRef } from 'react'
16
- import { map, only, prop, unique } from 'remeda'
22
+ import { memo, type PropsWithChildren, useEffect, useRef, useState } from 'react'
23
+ import { isNullish, map, omit, only, prop, setPath, unique } from 'remeda'
24
+ import { useUpdateEffect } from '../../hooks'
17
25
  import { useDiagramStoreApi } from '../../hooks/useDiagramState'
26
+ import { centerXYInternalNode } from '../../xyflow/utils'
18
27
  import { useOverlayDialog } from '../OverlayContext'
28
+ import { cssReactflowMarker } from '../Overlays.css'
19
29
  import type { XYFlowTypes } from './_types'
20
30
  import { SelectElement } from './SelectElement'
21
31
  import { useLayoutedRelationships, ZIndexes } from './use-layouted-relationships'
@@ -33,8 +43,11 @@ const edgeTypes = {
33
43
  relation: RelationshipEdge
34
44
  }
35
45
 
36
- const findSubjectNode = (nodes: XYFlowTypes.Node[]) =>
37
- nodes.find((n): n is XYFlowTypes.ElementNode => n.type === 'element' && n.data.column === 'subject')
46
+ /**
47
+ * Root node in 'subjects' column
48
+ */
49
+ const findRootSubject = (nodes: XYFlowTypes.Node[]) =>
50
+ nodes.find((n): n is XYFlowTypes.ElementNode => n.data.column === 'subjects' && isNullish(n.parentId))
38
51
 
39
52
  const resetDimmedAndHovered = (xyflow: ReactFlowInstance<XYFlowTypes.Node, XYFlowTypes.Edge>) => {
40
53
  xyflow.setEdges(edges =>
@@ -80,59 +93,90 @@ const onlyOneUnique = <T extends keyof Relation>(
80
93
  return only(unique(map(data.relations, prop(property))))
81
94
  }
82
95
 
83
- export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function RelationshipsXYFlow({ subjectId }) {
84
- const diagramStore = useDiagramStoreApi()
85
- const lastClickedNodeRef = useRef<XYFlowTypes.NonEmptyNode | null>(null)
86
- const [_scope, setScope] = useLocalStorage<'global' | 'view'>({
87
- key: 'likec4:scope-relationships-of',
88
- getInitialValueInEffect: false,
89
- defaultValue: 'view'
90
- })
91
- const {
92
- view,
93
- viewIncludesSubject,
94
- edges,
95
- nodes,
96
- subject,
97
- bounds
98
- } = useLayoutedRelationships(subjectId, _scope)
99
- const scope = viewIncludesSubject ? _scope : 'global'
100
- const showSubjectWarning = !viewIncludesSubject && _scope === 'view'
96
+ type RelationshipsXYFlowProps =
97
+ & PropsWithChildren<{
98
+ subjectId: Fqn
99
+ view: DiagramView
100
+ nodes: XYFlowTypes.Node[]
101
+ edges: XYFlowTypes.Edge[]
102
+ bounds: {
103
+ x: number
104
+ y: number
105
+ width: number
106
+ height: number
107
+ }
108
+ viewportPadding?: number | undefined
109
+ }>
110
+ & Pick<
111
+ ReactFlowProps<XYFlowTypes.Node, XYFlowTypes.Edge>,
112
+ | 'onNodeClick'
113
+ | 'elementsSelectable'
114
+ | 'maxZoom'
115
+ | 'minZoom'
116
+ >
117
+ const selectContainerViewport = (s: ReactFlowState) => `${s.width}x${s.height}`
101
118
 
102
- const [historySubjectId, historyOps, { history, current }] = useStateHistory(subject.id)
119
+ function RelationshipsXYFlowWrapped({
120
+ subjectId,
121
+ view,
122
+ nodes,
123
+ edges,
124
+ bounds,
125
+ children,
126
+ maxZoom = 2,
127
+ minZoom = 0.05,
128
+ viewportPadding = 0.1,
129
+ ...rest
130
+ }: RelationshipsXYFlowProps) {
131
+ const id = useId()
132
+
133
+ const lastClickedNodeRef = useRef<XYFlowTypes.NonEmptyNode | null>(null)
103
134
 
104
135
  const xyflow = useReactFlow<XYFlowTypes.Node, XYFlowTypes.Edge>()
105
136
  const xystore = useStoreApi<XYFlowTypes.Node, XYFlowTypes.Edge>()
106
- const overlay = useOverlayDialog()
107
137
 
108
- useEffect(() => {
109
- if (historySubjectId !== subject.id) {
110
- historyOps.set(subject.id)
111
- }
112
- }, [subject.id])
138
+ const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(true)
113
139
 
114
- useEffect(() => {
115
- if (historySubjectId !== subject.id) {
116
- overlay.openOverlay({
117
- relationshipsOf: historySubjectId
118
- })
140
+ const containerviewport = useStore(selectContainerViewport)
141
+
142
+ /**
143
+ * WORKAROUND - Called on viewport change
144
+ * Viewport transform is not rounded to integers which results in blurry nodes on some resolution
145
+ * https://github.com/xyflow/xyflow/issues/3282
146
+ * https://github.com/likec4/likec4/issues/734
147
+ */
148
+ useOnViewportChange({
149
+ onEnd: ({ x, y, zoom }) => {
150
+ const roundedX = Math.round(x),
151
+ roundedY = Math.round(y)
152
+ if (x !== roundedX || y !== roundedY) {
153
+ xystore.setState({ transform: [roundedX, roundedY, zoom] })
154
+ }
155
+ setZoomOnDoubleClick(zoom < 0.7)
119
156
  }
120
- }, [historySubjectId])
157
+ })
158
+
159
+ const initialFitviewFlagRef = useRef(false)
160
+ const viewBoundsRef = useSyncedRef(bounds)
121
161
 
122
162
  const fitview = useDebouncedCallback(
123
163
  () => {
124
- xyflow.fitView({
125
- padding: 0.2,
126
- includeHiddenNodes: true,
127
- maxZoom: 1,
128
- duration: 500
129
- })
164
+ const { width, height } = xystore.getState()
165
+ xyflow.setViewport(
166
+ getViewportForBounds(viewBoundsRef.current, width, height, minZoom, maxZoom, viewportPadding),
167
+ initialFitviewFlagRef.current ? { duration: 500 } : undefined
168
+ )
169
+ initialFitviewFlagRef.current = true
130
170
  },
131
- [xyflow],
132
- 150
171
+ [xyflow, minZoom, maxZoom, viewportPadding],
172
+ 100
133
173
  )
134
174
 
135
- useEffect(() => {
175
+ useUpdateEffect(() => {
176
+ fitview()
177
+ }, [containerviewport])
178
+
179
+ useDeepCompareEffect(() => {
136
180
  const {
137
181
  nodes: _nodes,
138
182
  edges: _edges,
@@ -142,23 +186,24 @@ export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function Relationshi
142
186
  height
143
187
  } = xystore.getState()
144
188
 
145
- const nextSubjectNode = findSubjectNode(nodes)
146
- const currentSubjectNode = findSubjectNode(_nodes)
189
+ const nextSubjectNode = findRootSubject(nodes)
190
+ const currentSubjectNode = findRootSubject(_nodes)
147
191
 
148
192
  // If subject node is the same, don't animate
149
- if (nextSubjectNode?.data.fqn === currentSubjectNode?.data.fqn) {
150
- setNodes(nodes)
193
+ if (currentSubjectNode && nextSubjectNode?.data.fqn === currentSubjectNode.data.fqn) {
194
+ setNodes(map(nodes, setPath(['data', 'initialAnimation'], false)))
151
195
  setEdges(edges)
196
+ fitview()
152
197
  return
153
198
  }
154
199
 
155
200
  if (!nextSubjectNode) {
156
201
  console.error('Subject node not found')
157
- } else if (nextSubjectNode.data.fqn !== subject.id) {
158
- console.error(`Subject node mismatch, expected: ${subject.id} got: ${nextSubjectNode.data.fqn}`)
202
+ } else if (nextSubjectNode.data.fqn !== subjectId) {
203
+ console.error(`Subject node mismatch, expected: ${subjectId} got: ${nextSubjectNode.data.fqn}`)
159
204
  }
160
205
 
161
- const nextzoom = getViewportForBounds(bounds, width, height, 0.2, 1, 0.2).zoom
206
+ const nextzoom = getViewportForBounds(bounds, width, height, minZoom, maxZoom, viewportPadding).zoom
162
207
 
163
208
  const nextSubjectCenter = nextSubjectNode && {
164
209
  x: nextSubjectNode.position.x + (nextSubjectNode.width ?? 0) / 2,
@@ -166,17 +211,44 @@ export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function Relationshi
166
211
  }
167
212
 
168
213
  const existingNode = lastClickedNodeRef.current
169
- ?? xyflow.getNodes().find(n => n.type !== 'empty' && n.data.column !== 'subject' && n.data.fqn === subject.id)
214
+ ?? xyflow.getNodes().find(n => n.type !== 'empty' && n.data.column !== 'subjects' && n.data.fqn === subjectId)
170
215
  lastClickedNodeRef.current = null
171
216
  // Animate from existing node to next subject node
172
- if (nextSubjectCenter && existingNode) {
217
+ if (nextSubjectCenter && existingNode && currentSubjectNode) {
218
+ // Center of current subject
219
+ const currentSubjectInternalNode = xyflow.getInternalNode(currentSubjectNode.id)!
220
+ const currentSubjectCenter = centerXYInternalNode(currentSubjectInternalNode)
221
+
222
+ // Move to center of existing node
223
+ const existingInternalNode = xyflow.getInternalNode(existingNode.id)!
224
+ const existingDimensions = getNodeDimensions(existingInternalNode)
225
+ nextSubjectNode.data.layoutId = existingNode.id
226
+
173
227
  // Dim all nodes except the existing node
174
228
  setNodes(_nodes.map(n => {
229
+ if (n.id !== existingNode.id) {
230
+ return {
231
+ ...n,
232
+ data: {
233
+ ...n.data,
234
+ leaving: n.data.column === 'subjects',
235
+ dimmed: n.data.column === 'subjects' ? 'immediate' : false
236
+ }
237
+ // hidden: n.data.column === 'subjects'
238
+ } as XYFlowTypes.Node
239
+ }
240
+ // Move existing node
175
241
  return {
176
- ...n,
242
+ ...omit(n, ['parentId']),
243
+ position: {
244
+ x: currentSubjectCenter.x - existingDimensions.width / 2,
245
+ y: currentSubjectCenter.y - existingDimensions.height / 2
246
+ },
247
+ zIndex: ZIndexes.max,
177
248
  data: {
178
249
  ...n.data,
179
- dimmed: n.id !== existingNode.id ? 'immediate' : false
250
+ leaving: false,
251
+ dimmed: false
180
252
  }
181
253
  } as XYFlowTypes.Node
182
254
  }))
@@ -184,63 +256,65 @@ export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function Relationshi
184
256
  ...e,
185
257
  data: {
186
258
  ...e.data,
187
- dimmed: true
188
- }
259
+ dimmed: 'immediate' as const
260
+ },
261
+ hidden: e.source === existingNode.id
262
+ || e.target === existingNode.id
263
+ || isAncestor(existingNode.id, e.source)
264
+ || isAncestor(existingNode.id, e.target)
189
265
  })))
190
266
 
191
- // Move to center of existing node
192
- const existingInternalNode = xyflow.getInternalNode(existingNode.id)!
193
- const existingDimensions = getNodeDimensions(existingInternalNode)
194
- const existingCenter = {
195
- x: existingInternalNode.internals.positionAbsolute.x + existingDimensions.width / 2,
196
- y: existingInternalNode.internals.positionAbsolute.y + existingDimensions.height / 2
197
- }
198
-
199
267
  // Pick the smaller zoom level
200
268
  const zoom = Math.min(
201
269
  xyflow.getViewport().zoom,
202
- nextzoom,
203
- nextSubjectNode.width! / existingDimensions.width,
204
- nextSubjectNode.height! / existingDimensions.height
270
+ nextzoom
205
271
  )
206
272
 
207
273
  let isCancelled = false
208
274
 
209
- xyflow.setCenter(existingCenter.x, existingCenter.y, { zoom, duration: 350 }).then(() => {
210
- // If the component is unmounted, we don't want to update the state
275
+ const layout = async () => {
276
+ // allow frameer to render
277
+ await delay(150)
211
278
  if (isCancelled) return
212
- setNodes(nodes)
213
- setEdges(edges)
214
- xyflow.setCenter(nextSubjectCenter.x, nextSubjectCenter.y, { zoom: nextzoom })
215
- fitview()
216
- })
279
+ await xyflow.setCenter(currentSubjectCenter.x, currentSubjectCenter.y, { zoom, duration: 250 })
280
+ requestAnimationFrame(() => {
281
+ if (isCancelled) return
282
+ xyflow.setCenter(nextSubjectCenter.x, nextSubjectCenter.y, { zoom })
283
+ setNodes(nodes)
284
+ setEdges(edges)
285
+ fitview()
286
+ })
287
+ }
288
+ requestAnimationFrame(layout)
217
289
  return () => {
218
290
  isCancelled = true
219
291
  }
220
292
  }
221
- setNodes(nodes)
293
+ setNodes(map(nodes, setPath(['data', 'initialAnimation'], false)))
222
294
  setEdges(edges)
223
295
  fitview()
224
296
  return undefined
225
- }, [nodes, edges])
297
+ }, [nodes, edges, subjectId])
226
298
 
227
299
  return (
228
300
  <ReactFlow
301
+ id={id}
229
302
  defaultEdges={[] as XYFlowTypes.Edge[]}
230
303
  defaultNodes={[] as XYFlowTypes.Node[]}
231
304
  nodeTypes={nodeTypes}
232
305
  edgeTypes={edgeTypes}
233
- defaultMarkerColor="var(--xy-edge-stroke)"
306
+ defaultMarkerColor="var(--likec4-relation-lineColor)"
307
+ className={cssReactflowMarker}
234
308
  zoomOnPinch={true}
235
309
  zoomOnScroll={false}
236
- zoomOnDoubleClick={false}
237
- maxZoom={1.5}
238
- minZoom={0.1}
310
+ zoomOnDoubleClick={zoomOnDoubleClick}
311
+ maxZoom={maxZoom}
312
+ minZoom={minZoom}
239
313
  fitView
240
314
  fitViewOptions={{
241
- padding: 0.2,
242
- maxZoom: 1,
243
- minZoom: 0.1,
315
+ padding: viewportPadding,
316
+ maxZoom,
317
+ minZoom,
244
318
  includeHiddenNodes: true
245
319
  }}
246
320
  preventScrolling={true}
@@ -248,7 +322,6 @@ export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function Relationshi
248
322
  noPanClassName="nopan"
249
323
  panOnScroll
250
324
  panOnDrag
251
- elementsSelectable
252
325
  nodesFocusable={false}
253
326
  edgesFocusable={false}
254
327
  nodesDraggable={false}
@@ -285,136 +358,29 @@ export const RelationshipsXYFlow = memo<{ subjectId: Fqn }>(function Relationshi
285
358
  onEdgeMouseLeave={() => {
286
359
  resetDimmedAndHovered(xyflow)
287
360
  }}
288
- onNodeClick={(e, node) => {
289
- e.stopPropagation()
290
- if (node.type === 'empty') return
291
- lastClickedNodeRef.current = node
292
- overlay.openOverlay({
293
- relationshipsOf: node.data.fqn
294
- })
295
- }}
296
361
  onNodeDoubleClick={(e) => {
297
362
  e.stopPropagation()
298
363
  }}
299
- onDoubleClick={e => {
300
- e.stopPropagation()
301
- fitview()
302
- }}
303
- onEdgeClick={(e, edge) => {
304
- e.stopPropagation()
305
- // One relation in edge
306
- const relationId = only(edge.data.relations)?.id
307
- if (relationId) {
308
- diagramStore.getState().onOpenSourceRelation?.(relationId)
309
- return
310
- }
311
-
312
- const nodeId = onlyOneUnique(edge.data, 'source') ? edge.source : edge.target
313
- const next = xyflow.getNode(nodeId)
314
- if (next && next.type !== 'empty') {
315
- lastClickedNodeRef.current = next
316
- overlay.openOverlay({
317
- relationshipsOf: next.data.fqn
318
- })
364
+ {...!zoomOnDoubleClick && {
365
+ onDoubleClick: e => {
366
+ e.stopPropagation()
367
+ fitview()
319
368
  }
320
369
  }}
370
+ {...rest}
321
371
  >
322
- <Panel position="top-center">
323
- <Group gap={'xs'} wrap={'nowrap'}>
324
- <Button
325
- leftSection={<IconArrowLeft stroke={3} size={14} />}
326
- color="dimmed"
327
- variant="subtle"
328
- style={{
329
- visibility: current > 0 ? 'visible' : 'hidden',
330
- padding: '0.25rem 0.75rem'
331
- }}
332
- styles={{
333
- label: {
334
- fontWeight: 400
335
- },
336
- section: {
337
- marginInlineEnd: 6
338
- }
339
- }}
340
- size="sm"
341
- onClick={(e) => {
342
- e.stopPropagation()
343
- historyOps.back()
344
- }}>
345
- Back
346
- </Button>
347
- <Space w={2} />
348
- <Group gap={'xs'} pos={'relative'} wrap="nowrap" flex={'1 0 auto'}>
349
- <Box fz={'sm'} fw={'400'} style={{ whiteSpace: 'nowrap', userSelect: 'none' }}>Relationships of</Box>
350
- <Box flex={'1 0 auto'}>
351
- <SelectElement
352
- scope={scope}
353
- subject={subject.element}
354
- onSelect={fqn =>
355
- overlay.openOverlay({
356
- relationshipsOf: fqn
357
- })}
358
- viewId={view.id} />
359
- </Box>
360
- <SegmentedControl
361
- flex={'1 0 auto'}
362
- size="xs"
363
- withItemsBorders={false}
364
- value={scope}
365
- onChange={setScope as any}
366
- data={[
367
- { label: 'Global', value: 'global' },
368
- { label: 'Current view', value: 'view', disabled: !viewIncludesSubject }
369
- ]}
370
- />
371
- {showSubjectWarning && (
372
- <Box
373
- pos={'absolute'}
374
- top={'calc(100% + .5rem)'}
375
- left={'50%'}
376
- w={'max-content'}
377
- style={{
378
- transform: 'translateX(-50%)',
379
- textAlign: 'center',
380
- cursor: 'pointer'
381
- }}
382
- onClick={(e) => {
383
- e.stopPropagation()
384
- setScope('global')
385
- }}
386
- >
387
- <Text fw={500} size="xs" c="orange" component="div">
388
- Current view doesn't include this element, switched to Global
389
- </Text>
390
- </Box>
391
- )}
392
- </Group>
393
- <Button
394
- rightSection={<IconArrowRight stroke={3} size={14} />}
395
- color="dimmed"
396
- variant="subtle"
397
- style={{
398
- visibility: current + 1 < history.length ? 'visible' : 'hidden',
399
- padding: '0.25rem 0.75rem'
400
- }}
401
- styles={{
402
- label: {
403
- fontWeight: 400
404
- },
405
- section: {
406
- marginInlineStart: 4
407
- }
408
- }}
409
- size="sm"
410
- onClick={(e) => {
411
- e.stopPropagation()
412
- historyOps.forward()
413
- }}>
414
- Forward
415
- </Button>
416
- </Group>
417
- </Panel>
372
+ {/* <Background variant={BackgroundVariant.Dots} size={2} gap={20} /> */}
373
+ {children}
418
374
  </ReactFlow>
419
375
  )
420
- })
376
+ }
377
+
378
+ export function RelationshipsXYFlow(props: RelationshipsXYFlowProps) {
379
+ return (
380
+ <ReactFlowProvider
381
+ defaultNodes={[]}
382
+ defaultEdges={[]}>
383
+ <RelationshipsXYFlowWrapped {...props} />
384
+ </ReactFlowProvider>
385
+ )
386
+ }
@@ -17,8 +17,8 @@ export namespace XYFlowTypes {
17
17
  }
18
18
 
19
19
  type NodeProps = {
20
- // level: number
21
- column: 'incomers' | 'subject' | 'outgoers'
20
+ depth?: number
21
+ column: 'incomers' | 'subjects' | 'outgoers'
22
22
  fqn: Fqn
23
23
  existsInCurrentView: boolean
24
24
  element: Pick<ComputedNode, 'color' | 'title' | 'description' | 'shape' | 'kind'>
@@ -28,6 +28,12 @@ export namespace XYFlowTypes {
28
28
  }
29
29
  navigateTo: ViewID | null
30
30
  hovered?: boolean
31
+ layoutId?: string
32
+ leaving?: boolean
33
+ /**
34
+ * @default true
35
+ */
36
+ initialAnimation?: boolean
31
37
  /**
32
38
  * Whether the node is dimmed
33
39
  * 'immediate' means that the node is dimmed without delay
@@ -43,9 +49,13 @@ export namespace XYFlowTypes {
43
49
 
44
50
  export type EmptyNode = SetRequired<
45
51
  ReactFlowNode<{
46
- column: 'incomers' | 'outgoers'
52
+ column: 'incomers' | 'subjects' | 'outgoers'
47
53
  hovered?: boolean
48
54
  dimmed?: boolean
55
+ /**
56
+ * @default true
57
+ */
58
+ initialAnimation?: boolean
49
59
  }, 'empty'>,
50
60
  'type'
51
61
  >
@@ -55,8 +65,9 @@ export namespace XYFlowTypes {
55
65
  export type Edge = Omit<ReactFlowEdge, 'data' | 'type'> & {
56
66
  data: {
57
67
  relations: [Relation, ...Relation[]]
68
+ includedInCurrentView: boolean
58
69
  hovered?: boolean
59
- dimmed?: boolean
70
+ dimmed?: 'immediate' | boolean
60
71
  }
61
72
  type: 'relation'
62
73
  }