@likec4/diagram 1.24.0 → 1.24.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 (116) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +5 -0
  2. package/dist/LikeC4Diagram.css.js +1 -0
  3. package/dist/LikeC4Diagram.d.ts +1 -1
  4. package/dist/LikeC4Diagram.js +6 -2
  5. package/dist/LikeC4Diagram.props.d.ts +6 -0
  6. package/dist/base/BaseXYFlow.d.ts +1 -1
  7. package/dist/base/BaseXYFlow.js +12 -3
  8. package/dist/base/const.d.ts +1 -1
  9. package/dist/base/const.js +1 -1
  10. package/dist/base/primitives/compound/CompoundActionButton.css.js +2 -0
  11. package/dist/base/primitives/compound/CompoundDetailsButton.css.js +2 -0
  12. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
  13. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -4
  14. package/dist/base/primitives/compound/CompoundNodeContainer.js +35 -22
  15. package/dist/base/primitives/compound/CompoundTitle.css.js +3 -1
  16. package/dist/base/primitives/customNode.js +1 -1
  17. package/dist/base/primitives/edge/EdgeContainer.js +5 -4
  18. package/dist/base/primitives/edge/EdgeLabel.css.d.ts +0 -1
  19. package/dist/base/primitives/edge/EdgeLabel.css.js +2 -5
  20. package/dist/base/primitives/edge/EdgeLabel.js +5 -5
  21. package/dist/base/primitives/edge/edge.css.d.ts +1 -1
  22. package/dist/base/primitives/edge/edge.css.js +4 -5
  23. package/dist/base/primitives/element/ElementActionButtons.css.js +2 -0
  24. package/dist/base/primitives/element/ElementActionButtons.js +4 -3
  25. package/dist/base/primitives/element/ElementDetailsButton.css.js +2 -0
  26. package/dist/base/primitives/element/ElementNodeContainer.css.js +2 -0
  27. package/dist/base/primitives/element/ElementNodeContainer.js +3 -1
  28. package/dist/base/primitives/element/ElementShape.css.js +4 -2
  29. package/dist/base/primitives/element/ElementShape.js +2 -3
  30. package/dist/base/primitives/element/ElementTitle.css.js +2 -0
  31. package/dist/base/primitives/element/styles.css.js +2 -0
  32. package/dist/components/SearchControl.css.js +2 -0
  33. package/dist/context/FramerMotionConfig.d.ts +9 -1
  34. package/dist/context/FramerMotionConfig.js +5 -2
  35. package/dist/context/RootContainer.js +13 -1
  36. package/dist/diagram.css +1 -1
  37. package/dist/globals.css.d.ts +2 -0
  38. package/dist/hooks/safeContext.js +1 -0
  39. package/dist/hooks/useDiagram.d.ts +554 -864
  40. package/dist/hooks/useDiagram.js +1 -0
  41. package/dist/hooks/useIsReducedGraphics.d.ts +4 -0
  42. package/dist/hooks/useIsReducedGraphics.js +8 -0
  43. package/dist/hooks/useXYFlow.js +1 -1
  44. package/dist/likec4diagram/DiagramUI.d.ts +2 -1
  45. package/dist/likec4diagram/DiagramUI.js +5 -3
  46. package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
  47. package/dist/likec4diagram/ui/controls/styles.css.js +2 -0
  48. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.js +2 -0
  49. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +3 -4
  50. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +2 -0
  51. package/dist/overlays/element-details/ElementDetailsCard.js +12 -8
  52. package/dist/overlays/element-details/actor.d.ts +9084 -2569
  53. package/dist/overlays/overlay/Overlay.js +6 -3
  54. package/dist/overlays/relationship-details/RelationshipDetails.js +31 -17
  55. package/dist/overlays/relationship-details/actor.d.ts +228 -3
  56. package/dist/overlays/relationship-details/actor.js +86 -14
  57. package/dist/overlays/relationship-details/hooks.d.ts +3 -0
  58. package/dist/overlays/relationship-details/hooks.js +6 -2
  59. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -3
  60. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +12 -3
  61. package/dist/overlays/relationships-browser/actor.d.ts +271 -54
  62. package/dist/overlays/relationships-browser/actor.js +86 -23
  63. package/dist/state/DiagramActorProvider.js +12 -2
  64. package/dist/state/diagram-machine.d.ts +563 -867
  65. package/dist/state/diagram-machine.js +82 -81
  66. package/dist/state/persistence.d.ts +5 -0
  67. package/dist/state/persistence.js +23 -0
  68. package/package.json +9 -9
  69. package/src/LikeC4Diagram.css.ts +13 -5
  70. package/src/LikeC4Diagram.props.ts +7 -0
  71. package/src/LikeC4Diagram.tsx +33 -24
  72. package/src/base/BaseXYFlow.tsx +14 -9
  73. package/src/base/const.ts +1 -1
  74. package/src/base/primitives/compound/CompoundActionButton.css.ts +6 -0
  75. package/src/base/primitives/compound/CompoundDetailsButton.css.ts +4 -0
  76. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +41 -59
  77. package/src/base/primitives/compound/CompoundNodeContainer.tsx +42 -22
  78. package/src/base/primitives/compound/CompoundTitle.css.ts +14 -13
  79. package/src/base/primitives/customNode.ts +3 -2
  80. package/src/base/primitives/edge/EdgeContainer.tsx +4 -3
  81. package/src/base/primitives/edge/EdgeLabel.css.ts +17 -11
  82. package/src/base/primitives/edge/EdgeLabel.tsx +4 -4
  83. package/src/base/primitives/edge/edge.css.ts +47 -27
  84. package/src/base/primitives/element/ElementActionButtons.css.ts +4 -0
  85. package/src/base/primitives/element/ElementActionButtons.tsx +4 -2
  86. package/src/base/primitives/element/ElementDetailsButton.css.ts +6 -8
  87. package/src/base/primitives/element/ElementNodeContainer.css.ts +18 -21
  88. package/src/base/primitives/element/ElementNodeContainer.tsx +3 -1
  89. package/src/base/primitives/element/ElementShape.css.ts +10 -6
  90. package/src/base/primitives/element/ElementShape.tsx +2 -2
  91. package/src/components/SearchControl.css.ts +11 -4
  92. package/src/context/FramerMotionConfig.tsx +13 -2
  93. package/src/context/RootContainer.tsx +9 -2
  94. package/src/globals.css.ts +3 -0
  95. package/src/hooks/safeContext.tsx +2 -1
  96. package/src/hooks/useDiagram.ts +1 -0
  97. package/src/hooks/useIsReducedGraphics.ts +5 -0
  98. package/src/hooks/useXYFlow.ts +1 -1
  99. package/src/likec4diagram/DiagramUI.tsx +5 -3
  100. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  101. package/src/likec4diagram/ui/controls/styles.css.ts +11 -1
  102. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.ts +10 -3
  103. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +6 -3
  104. package/src/likec4diagram/ui/search/LikeC4Search.css.ts +1 -0
  105. package/src/overlays/element-details/ElementDetailsCard.tsx +238 -231
  106. package/src/overlays/overlay/Overlay.css.ts +1 -9
  107. package/src/overlays/overlay/Overlay.tsx +6 -3
  108. package/src/overlays/relationship-details/RelationshipDetails.tsx +83 -66
  109. package/src/overlays/relationship-details/actor.ts +106 -13
  110. package/src/overlays/relationship-details/hooks.ts +6 -2
  111. package/src/overlays/relationships-browser/-useRelationshipsView.ts +10 -0
  112. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +16 -8
  113. package/src/overlays/relationships-browser/actor.ts +92 -22
  114. package/src/state/DiagramActorProvider.tsx +13 -2
  115. package/src/state/diagram-machine.ts +80 -79
  116. package/src/state/persistence.ts +22 -0
@@ -7,6 +7,7 @@ import clsx from 'clsx'
7
7
  import { AnimatePresence, LayoutGroup, m } from 'framer-motion'
8
8
  import { memo, useEffect, useRef } from 'react'
9
9
  import { BaseXYFlow } from '../../base/BaseXYFlow'
10
+ import { Base } from '../../base/types'
10
11
  import { updateEdges } from '../../base/updateEdges'
11
12
  import { updateNodes } from '../../base/updateNodes'
12
13
  import { useUpdateEffect } from '../../hooks'
@@ -45,7 +46,11 @@ export function RelationshipDetails({ actorRef }: RelationshipDetailsProps) {
45
46
  return (
46
47
  <RelationshipDetailsActorContext.Provider value={actorRef}>
47
48
  <ReactFlowProvider {...initialRef.current}>
48
- <RelationshipDetailsInner />
49
+ <LayoutGroup id={actorRef.sessionId} inherit={false}>
50
+ <AnimatePresence>
51
+ <RelationshipDetailsInner />
52
+ </AnimatePresence>
53
+ </LayoutGroup>
49
54
  </ReactFlowProvider>
50
55
  </RelationshipDetailsActorContext.Provider>
51
56
  )
@@ -55,6 +60,8 @@ const selector = (state: RelationshipDetailsSnapshot) => ({
55
60
  edgeId: state.context.edgeId,
56
61
  view: state.context.view,
57
62
  initialized: state.context.initialized,
63
+ nodes: state.context.xynodes,
64
+ edges: state.context.xyedges,
58
65
  })
59
66
 
60
67
  const RelationshipDetailsInner = memo(() => {
@@ -63,6 +70,8 @@ const RelationshipDetailsInner = memo(() => {
63
70
  edgeId,
64
71
  view,
65
72
  initialized,
73
+ nodes,
74
+ edges,
66
75
  } = useRelationshipDetailsState(selector)
67
76
 
68
77
  const {
@@ -71,12 +80,9 @@ const RelationshipDetailsInner = memo(() => {
71
80
  xyedges,
72
81
  bounds,
73
82
  } = useLayoutedDetails(edgeId, view)
74
- const [nodes, setNodes, onNodesChange] = useNodesState(xynodes)
75
- const [edges, setEdges, onEdgesChange] = useEdgesState(xyedges)
76
83
 
77
- useUpdateEffect(() => {
78
- setNodes(updateNodes(xynodes))
79
- setEdges(updateEdges(xyedges))
84
+ useEffect(() => {
85
+ browser.send({ type: 'update.xydata', xynodes, xyedges })
80
86
  }, [xynodes, xyedges])
81
87
 
82
88
  const [historyEdgeId, historyOps, { history, current }] = useStateHistory(edgeId)
@@ -89,7 +95,7 @@ const RelationshipDetailsInner = memo(() => {
89
95
 
90
96
  useEffect(() => {
91
97
  if (historyEdgeId !== edgeId) {
92
- // browser.navigateTo(historyEdgeId)
98
+ browser.navigateTo(historyEdgeId)
93
99
  }
94
100
  }, [historyEdgeId])
95
101
 
@@ -97,65 +103,76 @@ const RelationshipDetailsInner = memo(() => {
97
103
  const hasStepForward = current + 1 < history.length
98
104
 
99
105
  return (
100
- <LayoutGroup>
101
- <BaseXYFlow<Types.Node, Types.Edge>
102
- id="relationship-details"
103
- nodes={nodes}
104
- edges={edges}
105
- className={clsx(initialized ? 'initialized' : 'not-initialized')}
106
- nodeTypes={nodeTypes}
107
- edgeTypes={edgeTypes}
108
- onEdgesChange={onEdgesChange}
109
- onNodesChange={onNodesChange}
110
- fitViewPadding={0.05}
111
- onInit={useCallbackRef((instance) => {
112
- browser.send({ type: 'xyflow.init', instance })
113
- })}
114
- onNodeClick={useCallbackRef((e, node) => {
115
- e.stopPropagation()
116
- browser.send({ type: 'xyflow.nodeClick', node })
117
- })}
118
- onEdgeClick={useCallbackRef((e, edge) => {
119
- e.stopPropagation()
120
- browser.send({ type: 'xyflow.edgeClick', edge })
121
- })}
122
- onPaneClick={useCallbackRef((e) => {
123
- e.stopPropagation()
124
- browser.send({ type: 'xyflow.paneClick' })
125
- })}
126
- onViewportResize={useCallbackRef(() => {
127
- browser.send({ type: 'xyflow.resized' })
128
- })}
129
- nodesDraggable={false}
130
- fitView={false}
131
- pannable
132
- zoomable
133
- >
134
- <TopLeftPanel
135
- edge={edge}
136
- view={view}
137
- hasStepBack={hasStepBack}
138
- hasStepForward={hasStepForward}
139
- onStepBack={() => historyOps.back()}
140
- onStepForward={() => historyOps.forward()}
141
- />
142
- <Panel position="top-right">
143
- <ActionIcon
144
- variant="default"
145
- color="gray"
146
- // color="gray"
147
- // size={'lg'}
148
- // data-autofocus
149
- // autoFocus
150
- onClick={(e) => {
151
- e.stopPropagation()
152
- browser.close()
153
- }}>
154
- <IconX />
155
- </ActionIcon>
156
- </Panel>
157
- </BaseXYFlow>
158
- </LayoutGroup>
106
+ <BaseXYFlow<Types.Node, Types.Edge>
107
+ id={`relationships-details-${browser.actor.sessionId}`}
108
+ nodes={nodes}
109
+ edges={edges}
110
+ className={clsx(initialized ? 'initialized' : 'not-initialized')}
111
+ nodeTypes={nodeTypes}
112
+ edgeTypes={edgeTypes}
113
+ onNodesChange={useCallbackRef((changes) => {
114
+ browser.send({ type: 'xyflow.applyNodeChanges', changes })
115
+ })}
116
+ onEdgesChange={useCallbackRef((changes) => {
117
+ browser.send({ type: 'xyflow.applyEdgeChanges', changes })
118
+ })}
119
+ fitViewPadding={0.05}
120
+ onInit={useCallbackRef((instance) => {
121
+ browser.send({ type: 'xyflow.init', instance })
122
+ })}
123
+ onNodeClick={useCallbackRef((e, node) => {
124
+ e.stopPropagation()
125
+ browser.send({ type: 'xyflow.nodeClick', node })
126
+ })}
127
+ onEdgeClick={useCallbackRef((e, edge) => {
128
+ e.stopPropagation()
129
+ browser.send({ type: 'xyflow.edgeClick', edge })
130
+ })}
131
+ onPaneClick={useCallbackRef((e) => {
132
+ e.stopPropagation()
133
+ browser.send({ type: 'xyflow.paneClick' })
134
+ })}
135
+ onViewportResize={useCallbackRef(() => {
136
+ browser.send({ type: 'xyflow.resized' })
137
+ })}
138
+ onEdgeMouseEnter={useCallbackRef((_event, edge) => {
139
+ browser.send({ type: 'xyflow.edgeMouseEnter', edge })
140
+ })}
141
+ onEdgeMouseLeave={useCallbackRef((_event, edge) => {
142
+ browser.send({ type: 'xyflow.edgeMouseLeave', edge })
143
+ })}
144
+ onSelectionChange={useCallbackRef((params) => {
145
+ browser.send({ type: 'xyflow.selectionChange', ...params })
146
+ })}
147
+ nodesDraggable={false}
148
+ fitView={false}
149
+ pannable
150
+ zoomable
151
+ >
152
+ <TopLeftPanel
153
+ edge={edge}
154
+ view={view}
155
+ hasStepBack={hasStepBack}
156
+ hasStepForward={hasStepForward}
157
+ onStepBack={() => historyOps.back()}
158
+ onStepForward={() => historyOps.forward()}
159
+ />
160
+ <Panel position="top-right">
161
+ <ActionIcon
162
+ variant="default"
163
+ color="gray"
164
+ // color="gray"
165
+ // size={'lg'}
166
+ // data-autofocus
167
+ // autoFocus
168
+ onClick={(e) => {
169
+ e.stopPropagation()
170
+ browser.close()
171
+ }}>
172
+ <IconX />
173
+ </ActionIcon>
174
+ </Panel>
175
+ </BaseXYFlow>
159
176
  )
160
177
  })
161
178
 
@@ -1,19 +1,23 @@
1
1
  import { type BBox, type DiagramView, type EdgeId, invariant } from '@likec4/core'
2
2
  import {
3
+ type EdgeChange,
4
+ type NodeChange,
3
5
  type ReactFlowInstance,
6
+ applyEdgeChanges,
7
+ applyNodeChanges,
4
8
  } from '@xyflow/react'
5
9
  import { prop } from 'remeda'
6
10
  import {
7
- type ActorLogic,
8
11
  type ActorLogicFrom,
9
12
  type ActorRefFromLogic,
10
- type MachineSnapshot,
11
13
  type SnapshotFrom,
12
14
  assign,
13
15
  cancel,
16
+ enqueueActions,
14
17
  raise,
15
18
  setup,
16
19
  } from 'xstate'
20
+ import { Base } from '../../base'
17
21
  import { MinZoom } from '../../base/const'
18
22
  import type { RelationshipDetailsTypes } from './_types'
19
23
 
@@ -31,6 +35,8 @@ export type Context = Readonly<
31
35
  // parentRef: AnyActorRef | null
32
36
  xyflow: XYFLowInstance | null
33
37
  initialized: boolean
38
+ xynodes: RelationshipDetailsTypes.Node[]
39
+ xyedges: RelationshipDetailsTypes.Edge[]
34
40
  // bounds: BBox | null
35
41
  }
36
42
  >
@@ -39,8 +45,20 @@ export type Events =
39
45
  | { type: 'xyflow.init'; instance: XYFLowInstance }
40
46
  | { type: 'xyflow.nodeClick'; node: RelationshipDetailsTypes.Node }
41
47
  | { type: 'xyflow.edgeClick'; edge: RelationshipDetailsTypes.Edge }
48
+ | { type: 'xyflow.edgeMouseEnter'; edge: RelationshipDetailsTypes.Edge }
49
+ | { type: 'xyflow.edgeMouseLeave'; edge: RelationshipDetailsTypes.Edge }
50
+ | { type: 'dim.nonhovered.edges' }
51
+ | { type: 'undim.edges' }
52
+ | { type: 'xyflow.selectionChange'; nodes: RelationshipDetailsTypes.Node[]; edges: RelationshipDetailsTypes.Edge[] }
53
+ | { type: 'xyflow.applyNodeChanges'; changes: NodeChange<RelationshipDetailsTypes.Node>[] }
54
+ | { type: 'xyflow.applyEdgeChanges'; changes: EdgeChange<RelationshipDetailsTypes.Edge>[] }
42
55
  | { type: 'xyflow.paneClick' }
43
56
  | { type: 'xyflow.resized' }
57
+ | {
58
+ type: 'update.xydata'
59
+ xynodes: RelationshipDetailsTypes.Node[]
60
+ xyedges: RelationshipDetailsTypes.Edge[]
61
+ }
44
62
  // | { type: 'update.bounds'; bounds: BBox }
45
63
  | { type: 'fitDiagram'; duration?: number; bounds?: BBox }
46
64
  | { type: 'navigate.to'; edgeId: EdgeId }
@@ -82,6 +100,8 @@ export const relationshipDetailsLogic = setup({
82
100
  ...input,
83
101
  initialized: false,
84
102
  xyflow: null,
103
+ xynodes: [],
104
+ xyedges: [],
85
105
  }),
86
106
  states: {
87
107
  'opening': {
@@ -104,17 +124,70 @@ export const relationshipDetailsLogic = setup({
104
124
  params: { duration: 0 },
105
125
  },
106
126
  on: {
107
- // 'xyflow.nodeClick': {
108
- // actions: enqueueActions(({ event, enqueue }) => {
109
- // if ('fqn' in event.node.data) {
110
- // const fqn = event.node.data.fqn
111
- // enqueue.assign({
112
- // subject: fqn,
113
- // })
114
- // enqueue.raise({ type: 'fitDiagram' }, { delay: 50 })
115
- // }
116
- // }),
117
- // },
127
+ 'xyflow.edgeMouseEnter': {
128
+ actions: [
129
+ assign({
130
+ xyedges: ({ context, event }) => {
131
+ const hasDimmed = context.xyedges.some(edge => edge.data.dimmed === true)
132
+ return context.xyedges.map(edge => {
133
+ if (edge.id === event.edge.id) {
134
+ return Base.setData(edge, {
135
+ hovered: true,
136
+ dimmed: false,
137
+ })
138
+ }
139
+ return hasDimmed && !edge.data.dimmed && !edge.selected ? Base.setDimmed(edge, 'immediate') : edge
140
+ })
141
+ },
142
+ }),
143
+ cancel('undim.edges'),
144
+ cancel('dim.nonhovered.edges'),
145
+ raise({ type: 'dim.nonhovered.edges' }, { id: 'dim.nonhovered.edges', delay: 100 }),
146
+ ],
147
+ },
148
+ 'xyflow.edgeMouseLeave': {
149
+ actions: [
150
+ assign({
151
+ xyedges: ({ context, event }) =>
152
+ context.xyedges.map(edge => {
153
+ if (edge.id === event.edge.id) {
154
+ return Base.setHovered(edge, false)
155
+ }
156
+ return edge
157
+ }),
158
+ }),
159
+ cancel('dim.nonhovered.edges'),
160
+ raise({ type: 'undim.edges' }, { id: 'undim.edges', delay: 400 }),
161
+ ],
162
+ },
163
+ 'dim.nonhovered.edges': {
164
+ actions: assign({
165
+ xyedges: ({ context }) => context.xyedges.map(edge => Base.setDimmed(edge, edge.data.hovered !== true)),
166
+ }),
167
+ },
168
+ 'undim.edges': {
169
+ actions: assign({
170
+ xyedges: ({ context }) => {
171
+ const hasSelected = context.xyedges.some(edge => edge.selected === true)
172
+ if (hasSelected) {
173
+ return context.xyedges.map(edge =>
174
+ Base.setDimmed(edge, edge.selected !== true ? edge.data.dimmed || 'immediate' : false)
175
+ )
176
+ }
177
+ return context.xyedges.map(Base.setDimmed(false))
178
+ },
179
+ }),
180
+ },
181
+ 'xyflow.selectionChange': {
182
+ actions: enqueueActions(({ event, context, enqueue }) => {
183
+ if (
184
+ event.edges.length === 0 && context.xyedges.some(e => e.data.dimmed) &&
185
+ !context.xyedges.some(e => e.data.hovered)
186
+ ) {
187
+ enqueue.raise({ type: 'undim.edges' })
188
+ }
189
+ }),
190
+ },
118
191
  'navigate.to': {
119
192
  actions: [
120
193
  assign({
@@ -149,6 +222,26 @@ export const relationshipDetailsLogic = setup({
149
222
  raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 200 }),
150
223
  ],
151
224
  },
225
+ 'update.xydata': {
226
+ actions: assign({
227
+ xynodes: ({ event }) => event.xynodes,
228
+ xyedges: ({ event }) => event.xyedges,
229
+ }),
230
+ },
231
+ 'xyflow.applyNodeChanges': {
232
+ actions: assign({
233
+ xynodes: ({ context, event }) => {
234
+ return applyNodeChanges(event.changes, context.xynodes)
235
+ },
236
+ }),
237
+ },
238
+ 'xyflow.applyEdgeChanges': {
239
+ actions: assign({
240
+ xyedges: ({ context, event }) => {
241
+ return applyEdgeChanges(event.changes, context.xyedges)
242
+ },
243
+ }),
244
+ },
152
245
  },
153
246
  })
154
247
 
@@ -3,6 +3,7 @@ import { useCallbackRef } from '@mantine/hooks'
3
3
  import { useSelector } from '@xstate/react'
4
4
  import { shallowEqual } from 'fast-equals'
5
5
  import { createContext, useContext, useMemo, useTransition } from 'react'
6
+ import type { OverlaysActorRef } from '../overlaysActor'
6
7
  import type { RelationshipDetailsActorRef, RelationshipDetailsSnapshot } from './actor'
7
8
 
8
9
  export const RelationshipDetailsActorContext = createContext<RelationshipDetailsActorRef | null>(null)
@@ -24,6 +25,7 @@ export function useRelationshipDetails() {
24
25
  const actor = useRelationshipDetailsActor()
25
26
  const [, startTransition] = useTransition()
26
27
  return useMemo(() => ({
28
+ actor,
27
29
  getState: () => actor.getSnapshot().context,
28
30
  send: actor.send,
29
31
  navigateTo: (edgeId: EdgeId) => {
@@ -40,9 +42,11 @@ export function useRelationshipDetails() {
40
42
  })
41
43
  },
42
44
  close: () => {
43
- startTransition(() => {
45
+ if (actor._parent) {
46
+ ;(actor._parent as OverlaysActorRef)?.send({ type: 'close', actorId: actor.id })
47
+ } else {
44
48
  actor.send({ type: 'close' })
45
- })
49
+ }
46
50
  },
47
51
  }), [actor])
48
52
  }
@@ -391,6 +391,16 @@ export function layoutRelationshipsView(data: RelationshipsViewData): LayoutRela
391
391
  })
392
392
  }
393
393
 
394
+ const edgeCount = g.edgeCount()
395
+ if (edgeCount > 10) {
396
+ for (const edge of g.edges()) {
397
+ g.setEdge(edge, {
398
+ ...Sizes.edgeLabel,
399
+ width: edgeCount > 30 ? 800 : 400,
400
+ })
401
+ }
402
+ }
403
+
394
404
  const dagreBounds = applyDagreLayout(g)
395
405
 
396
406
  // Calculate bounds for all nodes except compounds
@@ -5,8 +5,8 @@ import { IconChevronLeft, IconChevronRight, IconX } from '@tabler/icons-react'
5
5
  import { Panel, ReactFlowProvider, useReactFlow, useStoreApi } from '@xyflow/react'
6
6
  import clsx from 'clsx'
7
7
  import { shallowEqual } from 'fast-equals'
8
- import { AnimatePresence, LayoutGroup, m, useUnmountEffect } from 'framer-motion'
9
- import { memo, useEffect, useId, useRef } from 'react'
8
+ import { AnimatePresence, LayoutGroup, m } from 'framer-motion'
9
+ import { memo, useEffect, useRef } from 'react'
10
10
  import type { SnapshotFrom } from 'xstate'
11
11
  import { BaseXYFlow } from '../../base/BaseXYFlow'
12
12
  import { useRelationshipsView } from './-useRelationshipsView'
@@ -18,7 +18,6 @@ import { nodeTypes } from './custom/nodeTypes'
18
18
  import {
19
19
  RelationshipsBrowserActorContext,
20
20
  useRelationshipsBrowser,
21
- useRelationshipsBrowserActor,
22
21
  useRelationshipsBrowserState,
23
22
  } from './hooks'
24
23
  import { SelectElement } from './SelectElement'
@@ -32,14 +31,14 @@ export function RelationshipsBrowser({ actorRef }: RelationshipsBrowserProps) {
32
31
  // return null
33
32
  // }
34
33
  const initialRef = useRef<{
35
- defaultNodes: Types.Node[]
36
- defaultEdges: Types.Edge[]
34
+ initialNodes: Types.Node[]
35
+ initialEdges: Types.Edge[]
37
36
  }>(null)
38
37
 
39
38
  if (initialRef.current == null) {
40
39
  initialRef.current = {
41
- defaultNodes: [],
42
- defaultEdges: [],
40
+ initialNodes: [],
41
+ initialEdges: [],
43
42
  }
44
43
  }
45
44
 
@@ -85,6 +84,7 @@ const RelationshipsBrowserXYFlow = memo(() => {
85
84
  className={clsx(isActive ? 'initialized' : 'not-initialized')}
86
85
  nodeTypes={nodeTypes}
87
86
  edgeTypes={edgeTypes}
87
+ fitView={false}
88
88
  fitViewPadding={ViewPadding}
89
89
  onNodeClick={useCallbackRef((e, node) => {
90
90
  e.stopPropagation()
@@ -111,8 +111,16 @@ const RelationshipsBrowserXYFlow = memo(() => {
111
111
  onEdgesChange={useCallbackRef((changes) => {
112
112
  browser.send({ type: 'xyflow.applyEdgeChanges', changes })
113
113
  })}
114
+ onEdgeMouseEnter={useCallbackRef((_event, edge) => {
115
+ browser.send({ type: 'xyflow.edgeMouseEnter', edge })
116
+ })}
117
+ onEdgeMouseLeave={useCallbackRef((_event, edge) => {
118
+ browser.send({ type: 'xyflow.edgeMouseLeave', edge })
119
+ })}
120
+ onSelectionChange={useCallbackRef((params) => {
121
+ browser.send({ type: 'xyflow.selectionChange', ...params })
122
+ })}
114
123
  nodesDraggable={false}
115
- fitView={false}
116
124
  pannable
117
125
  zoomable
118
126
  >
@@ -77,6 +77,9 @@ export type Events =
77
77
  | { type: 'xyflow.resized' }
78
78
  | { type: 'xyflow.edgeMouseEnter'; edge: RelationshipsBrowserTypes.Edge }
79
79
  | { type: 'xyflow.edgeMouseLeave'; edge: RelationshipsBrowserTypes.Edge }
80
+ | { type: 'xyflow.selectionChange'; nodes: RelationshipsBrowserTypes.Node[]; edges: RelationshipsBrowserTypes.Edge[] }
81
+ | { type: 'dim.nonhovered.edges' }
82
+ | { type: 'undim.edges' }
80
83
  | { type: 'xyflow.updateNodeInternals' }
81
84
  | { type: 'xyflow.unmount' }
82
85
  | { type: 'fitDiagram'; duration?: number; bounds?: BBox }
@@ -274,7 +277,7 @@ export const relationshipsBrowserLogic = setup({
274
277
  }
275
278
  }
276
279
 
277
- requestAnimationFrame(() => updateNodeInternals(updates, { triggerFitView: false }))
280
+ updateNodeInternals(updates, { triggerFitView: false })
278
281
  },
279
282
  'xyflow:fitDiagram': ({ context }, params?: { duration?: number; bounds?: BBox }) => {
280
283
  let {
@@ -289,16 +292,14 @@ export const relationshipsBrowserLogic = setup({
289
292
  if (bounds) {
290
293
  const { width, height } = xystore.getState()
291
294
  const viewport = getViewportForBounds(bounds, width, height, MinZoom, maxZoom, ViewPadding)
292
- requestAnimationFrame(() => xyflow.setViewport(viewport, duration > 0 ? { duration } : undefined))
295
+ xyflow.setViewport(viewport, duration > 0 ? { duration } : undefined)
293
296
  } else {
294
- requestAnimationFrame(() =>
295
- xyflow.fitView({
296
- minZoom: MinZoom,
297
- maxZoom,
298
- padding: ViewPadding,
299
- ...(duration > 0 && { duration }),
300
- })
301
- )
297
+ xyflow.fitView({
298
+ minZoom: MinZoom,
299
+ maxZoom,
300
+ padding: ViewPadding,
301
+ ...(duration > 0 && { duration }),
302
+ })
302
303
  }
303
304
  },
304
305
  },
@@ -350,7 +351,10 @@ export const relationshipsBrowserLogic = setup({
350
351
  'isReady': {
351
352
  always: [{
352
353
  guard: 'isReady',
353
- actions: raise({ type: 'fitDiagram', duration: 0 }),
354
+ actions: [
355
+ raise({ type: 'fitDiagram', duration: 0 }),
356
+ raise({ type: 'xyflow.updateNodeInternals' }, { delay: 100 }),
357
+ ],
354
358
  target: 'active',
355
359
  }, {
356
360
  target: 'initializing',
@@ -360,7 +364,74 @@ export const relationshipsBrowserLogic = setup({
360
364
  initial: 'idle',
361
365
  tags: ['active'],
362
366
  states: {
363
- 'idle': {},
367
+ 'idle': {
368
+ on: {
369
+ 'xyflow.edgeMouseEnter': {
370
+ actions: [
371
+ assign({
372
+ xyedges: ({ context, event }) => {
373
+ const hasDimmed = context.xyedges.some(edge => edge.data.dimmed === true)
374
+ return context.xyedges.map(edge => {
375
+ if (edge.id === event.edge.id) {
376
+ return Base.setData(edge, {
377
+ hovered: true,
378
+ dimmed: false,
379
+ })
380
+ }
381
+ return hasDimmed && !edge.data.dimmed && !edge.selected ? Base.setDimmed(edge, 'immediate') : edge
382
+ })
383
+ },
384
+ }),
385
+ cancel('undim.edges'),
386
+ cancel('dim.nonhovered.edges'),
387
+ raise({ type: 'dim.nonhovered.edges' }, { id: 'dim.nonhovered.edges', delay: 100 }),
388
+ ],
389
+ },
390
+ 'xyflow.edgeMouseLeave': {
391
+ actions: [
392
+ assign({
393
+ xyedges: ({ context, event }) =>
394
+ context.xyedges.map(edge => {
395
+ if (edge.id === event.edge.id) {
396
+ return Base.setHovered(edge, false)
397
+ }
398
+ return edge
399
+ }),
400
+ }),
401
+ cancel('dim.nonhovered.edges'),
402
+ raise({ type: 'undim.edges' }, { id: 'undim.edges', delay: 400 }),
403
+ ],
404
+ },
405
+ 'dim.nonhovered.edges': {
406
+ actions: assign({
407
+ xyedges: ({ context }) => context.xyedges.map(edge => Base.setDimmed(edge, edge.data.hovered !== true)),
408
+ }),
409
+ },
410
+ 'undim.edges': {
411
+ actions: assign({
412
+ xyedges: ({ context }) => {
413
+ const hasSelected = context.xyedges.some(edge => edge.selected === true)
414
+ if (hasSelected) {
415
+ return context.xyedges.map(edge =>
416
+ Base.setDimmed(edge, edge.selected !== true ? edge.data.dimmed || 'immediate' : false)
417
+ )
418
+ }
419
+ return context.xyedges.map(Base.setDimmed(false))
420
+ },
421
+ }),
422
+ },
423
+ 'xyflow.selectionChange': {
424
+ actions: enqueueActions(({ event, context, enqueue }) => {
425
+ if (
426
+ event.edges.length === 0 && context.xyedges.some(e => e.data.dimmed) &&
427
+ !context.xyedges.some(e => e.data.hovered)
428
+ ) {
429
+ enqueue.raise({ type: 'undim.edges' })
430
+ }
431
+ }),
432
+ },
433
+ },
434
+ },
364
435
  'layouting': {
365
436
  invoke: {
366
437
  id: 'layouter',
@@ -376,17 +447,16 @@ export const relationshipsBrowserLogic = setup({
376
447
  },
377
448
  onDone: {
378
449
  target: 'idle',
379
- actions: [
380
- assign({
381
- xynodes: ({ event }) => event.output.xynodes,
382
- xyedges: ({ event }) => event.output.xyedges,
450
+ actions: enqueueActions(({ enqueue, event }) => {
451
+ enqueue.assign({
452
+ xynodes: event.output.xynodes,
453
+ xyedges: event.output.xyedges,
383
454
  navigateFromNode: null,
384
- }),
385
- raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 80 }),
386
- raise({ type: 'xyflow.updateNodeInternals' }, { delay: 100 }),
387
- raise({ type: 'xyflow.updateNodeInternals' }, { delay: 250 }),
388
- raise({ type: 'xyflow.updateNodeInternals' }, { delay: 500 }),
389
- ],
455
+ }), enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 50 })
456
+ for (let i = 0; i <= 10; i++) {
457
+ enqueue.raise({ type: 'xyflow.updateNodeInternals' }, { delay: 100 + i * 50 })
458
+ }
459
+ }),
390
460
  },
391
461
  },
392
462
  on: {
@@ -3,7 +3,7 @@ import { useCallbackRef } from '@mantine/hooks'
3
3
  import { useActorRef, useSelector } from '@xstate/react'
4
4
  import { useStoreApi } from '@xyflow/react'
5
5
  import { shallowEqual } from 'fast-equals'
6
- import { type PropsWithChildren, useEffect } from 'react'
6
+ import { type PropsWithChildren, useEffect, useRef } from 'react'
7
7
  import { useDiagramEventHandlersRef } from '../context/DiagramEventHandlers'
8
8
  import { DiagramFeatures, useEnabledFeatures } from '../context/DiagramFeatures'
9
9
  import { DiagramActorSafeContext } from '../hooks/safeContext'
@@ -80,8 +80,19 @@ const SyncStore = (
80
80
  actorRef.send({ type: 'update.features', features })
81
81
  }, [actorRef, features])
82
82
 
83
+ const frameReq = useRef<number>(null)
84
+
83
85
  useUpdateEffect(() => {
84
- actorRef.send({ type: 'update.view', view, xyedges, xynodes })
86
+ frameReq.current = requestAnimationFrame(() => {
87
+ frameReq.current = null
88
+ actorRef.send({ type: 'update.view', view, xyedges, xynodes })
89
+ })
90
+ return () => {
91
+ if (frameReq.current != null) {
92
+ cancelAnimationFrame(frameReq.current)
93
+ }
94
+ frameReq.current = null
95
+ }
85
96
  }, [actorRef, view, xyedges, xynodes])
86
97
 
87
98
  return null