@likec4/diagram 1.27.0 → 1.27.2

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 (151) hide show
  1. package/bundle/index.js +2343 -6880
  2. package/dist/LikeC4Diagram.js +3 -9
  3. package/dist/LikeC4Diagram.props.d.ts +5 -3
  4. package/dist/StaticLikeC4Diagram.js +1 -1
  5. package/dist/_virtual/set.js +5 -0
  6. package/dist/_virtual/set2.js +3 -0
  7. package/dist/base/BaseXYFlow.d.ts +2 -1
  8. package/dist/base/primitives/compound/CompoundNodeContainer.d.ts +1 -1
  9. package/dist/base/primitives/compound/CompoundNodeContainer.js +1 -2
  10. package/dist/base/primitives/edge/EdgeLabel.css.js +1 -4
  11. package/dist/base/primitives/edge/EdgeLabelContainer.js +1 -1
  12. package/dist/base/primitives/edge/EdgePath.js +0 -3
  13. package/dist/base/primitives/edge/edge.css.js +2 -0
  14. package/dist/base/primitives/element/ElementActionButtons.js +5 -10
  15. package/dist/base/primitives/element/ElementNodeContainer.css.js +5 -4
  16. package/dist/base/primitives/element/ElementShape.css.js +1 -12
  17. package/dist/base/primitives/element/ElementTitle.css.js +3 -2
  18. package/dist/base/types.js +2 -2
  19. package/dist/base/updateEdges.js +1 -9
  20. package/dist/base/updateNodes.js +1 -12
  21. package/dist/bundle/LikeC4View.js +2 -2
  22. package/dist/bundle/LikeC4View.props.d.ts +7 -7
  23. package/dist/components/Link.js +1 -1
  24. package/dist/hooks/useCurrentViewId.js +8 -2
  25. package/dist/hooks/useDiagram.d.ts +2659 -1996
  26. package/dist/hooks/useDiagram.js +16 -8
  27. package/dist/likec4diagram/DiagramXYFlow.js +10 -9
  28. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +30 -41
  29. package/dist/likec4diagram/custom/nodes/toolbar/Toolbar.js +1 -1
  30. package/dist/likec4diagram/custom/nodes/toolbar/styles.css.js +6 -1
  31. package/dist/likec4diagram/ui/controls/LayoutDriftNotification.js +13 -5
  32. package/dist/likec4diagram/ui/notation/NotationPanel.css.js +3 -3
  33. package/dist/likec4diagram/ui/notation/NotationPanel.js +10 -4
  34. package/dist/likec4diagram/useLayoutConstraints.d.ts +11 -10
  35. package/dist/likec4diagram/useLayoutConstraints.js +64 -61
  36. package/dist/node_modules/.pnpm/mnemonist@0.40.3_patch_hash_5c12abb43fcff6fd81e53021b89075d53249640d165a2556cc1e58d71201a13a/node_modules/mnemonist/set.js +369 -0
  37. package/dist/overlays/Overlays.js +5 -3
  38. package/dist/overlays/element-details/actor.d.ts +2769 -173
  39. package/dist/overlays/overlay/Overlay.css.d.ts +26 -2
  40. package/dist/overlays/overlay/Overlay.css.js +83 -16
  41. package/dist/overlays/overlay/Overlay.d.ts +7 -6
  42. package/dist/overlays/overlay/Overlay.js +39 -32
  43. package/dist/overlays/overlaysActor.js +2 -2
  44. package/dist/overlays/relationship-details/RelationshipDetails.js +113 -70
  45. package/dist/overlays/relationship-details/SelectEdge.d.ts +1 -1
  46. package/dist/overlays/relationship-details/SelectEdge.js +15 -9
  47. package/dist/overlays/relationship-details/_types.d.ts +4 -1
  48. package/dist/overlays/relationship-details/actor.d.ts +415 -128
  49. package/dist/overlays/relationship-details/actor.js +162 -53
  50. package/dist/overlays/relationship-details/compute.d.ts +12 -0
  51. package/dist/overlays/relationship-details/compute.js +139 -0
  52. package/dist/overlays/relationship-details/custom/ElementActions.js +27 -5
  53. package/dist/overlays/relationship-details/custom/edgeTypes.js +1 -1
  54. package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +2 -1
  55. package/dist/overlays/relationship-details/hooks.d.ts +336 -6
  56. package/dist/overlays/relationship-details/hooks.js +9 -7
  57. package/dist/overlays/relationship-details/layout-to-xyflow.d.ts +12 -0
  58. package/dist/overlays/relationship-details/layout-to-xyflow.js +149 -0
  59. package/dist/overlays/relationship-details/layout.d.ts +22 -0
  60. package/dist/overlays/relationship-details/layout.js +314 -0
  61. package/dist/overlays/relationships-browser/RelationshipsBrowser.d.ts +3 -1
  62. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +2 -2
  63. package/dist/overlays/relationships-browser/SelectElement.js +4 -4
  64. package/dist/overlays/relationships-browser/_types.d.ts +2 -0
  65. package/dist/overlays/relationships-browser/actor.d.ts +130 -14
  66. package/dist/overlays/relationships-browser/actor.js +30 -16
  67. package/dist/overlays/relationships-browser/custom/ElementActions.js +5 -3
  68. package/dist/overlays/relationships-browser/custom/edgeTypes.d.ts +3 -1
  69. package/dist/overlays/relationships-browser/custom/edgeTypes.js +1 -1
  70. package/dist/overlays/relationships-browser/hooks.d.ts +3 -1
  71. package/dist/overlays/relationships-browser/hooks.js +1 -1
  72. package/dist/overlays/relationships-browser/{-useRelationshipsView.d.ts → layout.d.ts} +2 -0
  73. package/dist/overlays/relationships-browser/{-useRelationshipsView.js → layout.js} +19 -2
  74. package/dist/overlays/relationships-browser/useViewToNodesEdges.d.ts +1 -1
  75. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +3 -1
  76. package/dist/packages/core/src/compute-view/relationships-view/compute.js +1 -1
  77. package/dist/packages/core/src/model/connection/model/ConnectionModel.js +121 -0
  78. package/dist/packages/core/src/model/connection/model/find.js +39 -0
  79. package/dist/packages/core/src/model/connection/ops.js +7 -0
  80. package/dist/packages/core/src/utils/const.js +3 -0
  81. package/dist/packages/core/src/utils/fqn.js +9 -1
  82. package/dist/packages/core/src/utils/iterable/find.js +21 -0
  83. package/dist/packages/core/src/utils/iterable/some.js +21 -0
  84. package/dist/packages/core/src/utils/set.js +48 -0
  85. package/dist/packages/core/src/utils/string-hash.js +13 -0
  86. package/dist/state/DiagramActorProvider.d.ts +4 -2
  87. package/dist/state/DiagramActorProvider.js +30 -37
  88. package/dist/state/assign.js +2 -2
  89. package/dist/state/diagram-machine.d.ts +1731 -1040
  90. package/dist/state/diagram-machine.js +126 -60
  91. package/dist/state/syncManualLayoutActor.d.ts +1 -1
  92. package/dist/styles.css.js +1 -1
  93. package/package.json +11 -11
  94. package/src/LikeC4Diagram.props.ts +7 -3
  95. package/src/LikeC4Diagram.tsx +2 -8
  96. package/src/StaticLikeC4Diagram.tsx +1 -1
  97. package/src/base/BaseXYFlow.tsx +2 -1
  98. package/src/base/primitives/compound/CompoundNodeContainer.tsx +1 -3
  99. package/src/base/primitives/edge/EdgeLabel.css.ts +1 -4
  100. package/src/base/primitives/edge/EdgeLabelContainer.tsx +1 -1
  101. package/src/base/primitives/edge/EdgePath.tsx +0 -3
  102. package/src/base/primitives/edge/edge.css.ts +2 -0
  103. package/src/base/primitives/element/ElementActionButtons.tsx +6 -7
  104. package/src/base/primitives/element/ElementNodeContainer.css.ts +5 -4
  105. package/src/base/primitives/element/ElementShape.css.ts +1 -12
  106. package/src/base/primitives/element/ElementTitle.css.ts +3 -2
  107. package/src/base/types.ts +2 -7
  108. package/src/base/updateEdges.ts +8 -8
  109. package/src/base/updateNodes.ts +11 -11
  110. package/src/bundle/LikeC4View.props.ts +7 -7
  111. package/src/bundle/LikeC4View.tsx +2 -2
  112. package/src/components/Link.tsx +3 -1
  113. package/src/hooks/useCurrentViewId.ts +4 -2
  114. package/src/hooks/useDiagram.ts +18 -9
  115. package/src/likec4diagram/DiagramXYFlow.tsx +11 -9
  116. package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +39 -48
  117. package/src/likec4diagram/custom/nodes/toolbar/Toolbar.tsx +2 -2
  118. package/src/likec4diagram/custom/nodes/toolbar/styles.css.ts +5 -0
  119. package/src/likec4diagram/ui/controls/LayoutDriftNotification.tsx +8 -4
  120. package/src/likec4diagram/ui/notation/NotationPanel.css.ts +2 -2
  121. package/src/likec4diagram/ui/notation/NotationPanel.tsx +8 -6
  122. package/src/likec4diagram/useLayoutConstraints.ts +84 -89
  123. package/src/overlays/Overlays.tsx +5 -3
  124. package/src/overlays/overlay/Overlay.css.ts +83 -15
  125. package/src/overlays/overlay/Overlay.tsx +52 -41
  126. package/src/overlays/overlaysActor.ts +1 -1
  127. package/src/overlays/relationship-details/RelationshipDetails.tsx +131 -68
  128. package/src/overlays/relationship-details/SelectEdge.tsx +19 -12
  129. package/src/overlays/relationship-details/_types.ts +3 -1
  130. package/src/overlays/relationship-details/actor.ts +217 -68
  131. package/src/overlays/relationship-details/compute.ts +186 -0
  132. package/src/overlays/relationship-details/custom/ElementActions.tsx +27 -18
  133. package/src/overlays/relationship-details/custom/edgeTypes.tsx +1 -1
  134. package/src/overlays/relationship-details/hooks.ts +10 -8
  135. package/src/overlays/relationship-details/layout-to-xyflow.ts +195 -0
  136. package/src/overlays/relationship-details/layout.ts +470 -0
  137. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +2 -2
  138. package/src/overlays/relationships-browser/SelectElement.tsx +5 -5
  139. package/src/overlays/relationships-browser/_types.ts +2 -0
  140. package/src/overlays/relationships-browser/actor.ts +39 -14
  141. package/src/overlays/relationships-browser/custom/ElementActions.tsx +5 -3
  142. package/src/overlays/relationships-browser/custom/edgeTypes.tsx +1 -1
  143. package/src/overlays/relationships-browser/hooks.ts +2 -2
  144. package/src/overlays/relationships-browser/{-useRelationshipsView.ts → layout.ts} +23 -13
  145. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +3 -1
  146. package/src/state/DiagramActorProvider.tsx +40 -50
  147. package/src/state/assign.ts +2 -2
  148. package/src/state/diagram-machine.ts +151 -75
  149. package/dist/overlays/relationship-details/useLayoutedDetails.d.ts +0 -19
  150. package/dist/overlays/relationship-details/useLayoutedDetails.js +0 -267
  151. package/src/overlays/relationship-details/useLayoutedDetails.ts +0 -376
@@ -132,7 +132,7 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
132
132
  return
133
133
  }
134
134
  // This update stays only in internal xystate, not in diagram xstate
135
- xyflow.updateEdgeData(id, {
135
+ diagram.updateEdgeData(id as EdgeId, {
136
136
  labelXY: {
137
137
  x: labelPos.x,
138
138
  y: labelPos.y,
@@ -169,15 +169,13 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
169
169
  hasMoved = true
170
170
  pointer = clientPoint
171
171
  const { x, y } = xyflow.screenToFlowPosition(pointer, { snapToGrid: false })
172
- xyflow.updateEdgeData(id, xyedge => {
173
- const cp = (xyedge.data.controlPoints ?? controlPoints).slice()
174
- cp[index] = {
175
- x: Math.round(x),
176
- y: Math.round(y),
177
- }
178
- return {
179
- controlPoints: cp,
180
- }
172
+ const cp = controlPoints.slice()
173
+ cp[index] = {
174
+ x: Math.round(x),
175
+ y: Math.round(y),
176
+ }
177
+ diagram.updateEdgeData(id as EdgeId, {
178
+ controlPoints: cp,
181
179
  })
182
180
  }
183
181
  e.stopPropagation()
@@ -216,14 +214,9 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
216
214
  e.stopPropagation()
217
215
  // Defer the update to avoid conflict with the pointerup event
218
216
  setTimeout(() => {
219
- xyflow.updateEdgeData(id as EdgeId, { controlPoints: newControlPoints })
217
+ diagram.updateEdgeData(id as EdgeId, { controlPoints: newControlPoints })
220
218
  diagram.scheduleSaveManualLayout()
221
219
  }, 10)
222
-
223
- domNode.removeEventListener('pointerup', onPointerUp, {
224
- capture: true,
225
- })
226
- e.stopPropagation()
227
220
  }
228
221
 
229
222
  domNode.addEventListener('pointerup', onPointerUp, {
@@ -259,42 +252,40 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
259
252
  if (e.button !== 2) {
260
253
  return
261
254
  }
255
+ const points: VectorImpl[] = [
256
+ new VectorImpl(sourceX, sourceY),
257
+ ...controlPoints.map(vector) || [],
258
+ new VectorImpl(targetX, targetY),
259
+ ]
262
260
 
263
- xyflow.updateEdgeData(id, edge => {
264
- const points: VectorImpl[] = [
265
- new VectorImpl(sourceX, sourceY),
266
- ...controlPoints.map(vector) || [],
267
- new VectorImpl(targetX, targetY),
268
- ]
269
-
270
- let pointer = { x: e.clientX, y: e.clientY }
271
- const newPoint = vector(xyflow.screenToFlowPosition(pointer, { snapToGrid: false }))
272
-
273
- let insertionIndex = 0
274
- let minDistance = Infinity
275
- for (let i = 0; i < points.length - 1; i++) {
276
- const a = points[i]!, b = points[i + 1]!
277
- const fromCurrentToNext = b.sub(a)
278
- const fromCurrentToNew = newPoint.sub(a)
279
- const fromNextToNew = newPoint.sub(b)
280
-
281
- // Is pointer above the current segment?
282
- if (fromCurrentToNext.dot(fromCurrentToNew) * fromCurrentToNext.dot(fromNextToNew) < 0) {
283
- // Calculate distance by approximating edge segment with a staight line
284
- const distanceToEdge = Math.abs(fromCurrentToNext.cross(fromCurrentToNew).abs() / fromCurrentToNext.abs())
285
-
286
- if (distanceToEdge < minDistance) {
287
- minDistance = distanceToEdge
288
- insertionIndex = i
289
- }
261
+ let pointer = { x: e.clientX, y: e.clientY }
262
+ const newPoint = vector(xyflow.screenToFlowPosition(pointer, { snapToGrid: false }))
263
+
264
+ let insertionIndex = 0
265
+ let minDistance = Infinity
266
+ for (let i = 0; i < points.length - 1; i++) {
267
+ const a = points[i]!,
268
+ b = points[i + 1]!,
269
+ fromCurrentToNext = b.sub(a),
270
+ fromCurrentToNew = newPoint.sub(a),
271
+ fromNextToNew = newPoint.sub(b)
272
+
273
+ // Is pointer above the current segment?
274
+ if (fromCurrentToNext.dot(fromCurrentToNew) * fromCurrentToNext.dot(fromNextToNew) < 0) {
275
+ // Calculate distance by approximating edge segment with a staight line
276
+ const distanceToEdge = Math.abs(fromCurrentToNext.cross(fromCurrentToNew).abs() / fromCurrentToNext.abs())
277
+
278
+ if (distanceToEdge < minDistance) {
279
+ minDistance = distanceToEdge
280
+ insertionIndex = i
290
281
  }
291
282
  }
283
+ }
292
284
 
293
- const newControlPoints = edge.data.controlPoints?.slice() || []
294
- newControlPoints.splice(insertionIndex, 0, newPoint)
285
+ const newControlPoints = controlPoints.slice() || []
286
+ newControlPoints.splice(insertionIndex, 0, newPoint)
295
287
 
296
- return { controlPoints: newControlPoints }
297
- })
288
+ diagram.updateEdgeData(id as EdgeId, { controlPoints: newControlPoints })
298
289
 
299
290
  diagram.scheduleSaveManualLayout()
300
291
 
@@ -357,7 +348,7 @@ export const RelationshipEdge = customEdge<Types.RelationshipEdgeData>((props) =
357
348
  </EdgeLabelContainer>
358
349
  </EdgeContainer>
359
350
  {/* Render control points above edge label */}
360
- {enableEdgeEditing && controlPoints.length > 0 && (
351
+ {enableEdgeEditing && controlPoints.length > 0 && (selected || hovered || isControlPointDragging) && (
361
352
  <EdgeLabelRenderer>
362
353
  <EdgeContainer
363
354
  component="svg"
@@ -1,4 +1,4 @@
1
- import { Box, Group, Paper, Stack, Text } from '@mantine/core'
1
+ import { Box, Group, Paper, Stack, Text, Tooltip } from '@mantine/core'
2
2
  import { useDebouncedValue } from '@mantine/hooks'
3
3
  import { type NodeToolbarProps, NodeToolbar } from '@xyflow/react'
4
4
  import clsx from 'clsx'
@@ -69,7 +69,7 @@ export function Toolbar({ title, children, nodeProps, ...props }: ToolbarProps)
69
69
  withBorder>
70
70
  <Stack gap={'6px'}>
71
71
  <Box px={'4px'}>
72
- <Text c="dimmed" className={styles.toolbarTitle}>{title}</Text>
72
+ <Text className={styles.toolbarTitle}>{title}</Text>
73
73
  </Box>
74
74
  <Group gap={4}>
75
75
  {children}
@@ -1,8 +1,13 @@
1
1
  import { css } from '@likec4/styles/css'
2
2
 
3
3
  export const toolbarTitle = css({
4
+ color: 'mantine.colors.dimmed',
4
5
  fontSize: '10px',
5
6
  fontWeight: 600,
7
+ maxWidth: '220px',
6
8
  cursor: 'default',
7
9
  userSelect: 'all',
10
+ textOverflow: 'ellipsis',
11
+ overflow: 'hidden',
12
+ whiteSpace: 'nowrap',
8
13
  })
@@ -5,14 +5,13 @@ import {
5
5
  HoverCardDropdown,
6
6
  HoverCardTarget,
7
7
  Notification,
8
- Overlay,
9
8
  Text,
10
9
  } from '@mantine/core'
11
10
  import {
12
11
  IconAlertTriangle,
13
12
  } from '@tabler/icons-react'
14
13
 
15
- // import { mantine } from '../../../theme-vars'
14
+ import { css } from '@likec4/styles/css'
16
15
  import { ActionIcon } from './_shared'
17
16
 
18
17
  export const LayoutDriftNotification = (props: PopoverProps) => (
@@ -22,8 +21,13 @@ export const LayoutDriftNotification = (props: PopoverProps) => (
22
21
  closeDelay={100}
23
22
  {...props}>
24
23
  <HoverCardTarget>
25
- <ActionIcon c="orange">
26
- <Overlay color={'orange.lightHover'} backgroundOpacity={1} />
24
+ <ActionIcon
25
+ color={'orange'}
26
+ c="orange"
27
+ className={css({
28
+ bg: 'mantine.colors.orange.light',
29
+ })}
30
+ >
27
31
  <IconAlertTriangle />
28
32
  </ActionIcon>
29
33
  </HoverCardTarget>
@@ -98,6 +98,6 @@ export const shapeBadge = css({
98
98
  ['--badge-padding-x']: '3px',
99
99
  ['--badge-height']: '13.5px',
100
100
  ['--badge-lh']: '1',
101
- // ['--badge-bg']: vars.element.fill,
102
- // ['--badge-color']: vars.element.hiContrast
101
+ ['--badge-bg']: '{colors.likec4.palette.fill}',
102
+ ['--badge-color']: '{colors.likec4.palette.hiContrast}',
103
103
  })
@@ -54,13 +54,14 @@ const ElementNotation = ({ value }: { value: ElementNotationData }) => {
54
54
  likec4Palette: color,
55
55
  }),
56
56
  )}
57
+ data-likec4-color={color}
57
58
  onMouseEnter={() => {
58
59
  setOnlyKind(null)
59
- // diagramStore.getState().highlightByElementNotation(value)
60
+ diagram.highlightNotation(value)
60
61
  }}
61
62
  onMouseLeave={() => {
62
63
  setOnlyKind(null)
63
- // diagramStore.setState({ dimmed: new Set() })
64
+ diagram.unhighlightNotation()
64
65
  }}
65
66
  >
66
67
  <Group
@@ -88,17 +89,18 @@ const ElementNotation = ({ value }: { value: ElementNotationData }) => {
88
89
  {value.kinds.map((kind) => (
89
90
  <Badge
90
91
  key={kind}
91
- className={styles.shapeBadge}
92
+ className={cx(
93
+ styles.shapeBadge,
94
+ )}
92
95
  onMouseEnter={() => {
93
96
  setOnlyKind(kind)
94
- // diagramStore.getState().highlightByElementNotation(value, kind)
97
+ diagram.highlightNotation(value, kind)
95
98
  }}
96
99
  onMouseLeave={() => {
97
100
  setOnlyKind(null)
98
- // diagramStore.getState().highlightByElementNotation(value)
101
+ diagram.highlightNotation(value)
99
102
  }}
100
103
  opacity={isNonNullish(onlyKind) && onlyKind !== kind ? 0.25 : 1}
101
- color={isNonNullish(onlyKind) && onlyKind !== kind ? 'gray' : token('colors.likec4.palette.fill')}
102
104
  >
103
105
  {kind}
104
106
  </Badge>
@@ -58,11 +58,11 @@ abstract class Rect {
58
58
  y: positionAbsolute.y - parentPosition.y,
59
59
  }
60
60
  }
61
- protected abstract parent: Compound | null
61
+ protected abstract parent: CompoundRect | null
62
62
 
63
63
  constructor(
64
64
  xynode: InternalNode,
65
- parent: Compound | null = null,
65
+ parent: CompoundRect | null = null,
66
66
  ) {
67
67
  this.id = xynode.id
68
68
 
@@ -82,12 +82,12 @@ abstract class Rect {
82
82
  }
83
83
  }
84
84
 
85
- class Compound extends Rect {
85
+ class CompoundRect extends Rect {
86
86
  public readonly children = [] as Rect[]
87
87
 
88
88
  constructor(
89
89
  xynode: InternalNode,
90
- public readonly parent: Compound | null = null,
90
+ public readonly parent: CompoundRect | null = null,
91
91
  ) {
92
92
  super(xynode, parent)
93
93
  }
@@ -96,22 +96,18 @@ class Compound extends Rect {
96
96
  class Leaf extends Rect {
97
97
  constructor(
98
98
  xynode: InternalNode,
99
- public readonly parent: Compound | null = null,
99
+ public readonly parent: CompoundRect | null = null,
100
100
  ) {
101
101
  super(xynode, parent)
102
102
  }
103
103
  }
104
104
 
105
- type NodePositionUpdater = (
106
- nodes: Array<{ rect: Rect | Compound; node: InternalNode }>,
107
- ) => void
108
-
109
105
  export function createLayoutConstraints(
110
106
  xyflowApi: XYStoreApi,
111
107
  editingNodeIds: NonEmptyArray<string>,
112
108
  ) {
113
109
  const { parentLookup, nodeLookup } = xyflowApi.getState()
114
- const rects = new Map<string, Leaf | Compound>()
110
+ const rects = new Map<string, Leaf | CompoundRect>()
115
111
 
116
112
  const ancestorsOf = (nodeId: string) => {
117
113
  const ancestors = [] as string[]
@@ -129,36 +125,31 @@ export function createLayoutConstraints(
129
125
  editingNodeIds.flatMap(ancestorsOf),
130
126
  )
131
127
 
132
- const traverse = new Array<{ xynode: InternalNode; parent: Compound | null }>()
133
-
134
- for (const [, xynode] of nodeLookup) {
135
- if (isNullish(xynode.parentId)) {
136
- traverse.push({
137
- xynode,
138
- parent: null,
139
- })
140
- }
141
- }
128
+ const traverse: Array<{ xynode: InternalNode; parent: CompoundRect | null }> = [...nodeLookup.values()].flatMap(x =>
129
+ !x.parentId ? { xynode: x, parent: null } : []
130
+ )
142
131
 
143
132
  while (traverse.length > 0) {
144
133
  const { xynode, parent } = traverse.shift()!
145
134
  const isEditing = editingNodeIds.includes(xynode.id)
146
135
 
147
136
  // Traverse children if the node is a compound, not dragging, and is an ancestor of the dragging node
148
- const shouldTraverse = !isEditing &&
149
- (xynode.type === 'compound-element' || xynode.type === 'compound-deployment' || xynode.type === 'view-group')
137
+ const shouldTraverse = !isEditing
138
+ && xynode.type !== 'element'
139
+ && xynode.type !== 'deployment'
150
140
  && ancestorsOfDraggingNodes.has(xynode.id)
151
141
 
152
- const rect = shouldTraverse ? new Compound(xynode, parent) : new Leaf(xynode, parent)
153
- rects.set(xynode.id, rect)
154
-
155
142
  if (shouldTraverse) {
143
+ const rect = new CompoundRect(xynode, parent)
144
+ rects.set(xynode.id, rect)
156
145
  parentLookup.get(xynode.id)?.forEach(child => {
157
146
  traverse.push({
158
147
  xynode: child,
159
- parent: rect as Compound,
148
+ parent: rect as CompoundRect,
160
149
  })
161
150
  })
151
+ } else {
152
+ rects.set(xynode.id, new Leaf(xynode, parent))
162
153
  }
163
154
  }
164
155
 
@@ -166,28 +157,32 @@ export function createLayoutConstraints(
166
157
  applyConstraints(rectsToUpdate)
167
158
 
168
159
  function applyConstraints(targets: Rect[]) {
169
- targets
170
- .filter(x => x instanceof Compound)
171
- .forEach((r) => {
172
- applyConstraints(r.children)
173
-
174
- const childrenBB = r.children.reduce((acc, r) => ({
175
- minX: Math.min(acc.minX, r.minX),
176
- minY: Math.min(acc.minY, r.minY),
177
- maxX: Math.max(acc.maxX, r.maxX),
178
- maxY: Math.max(acc.maxY, r.maxY),
179
- }), {
180
- minX: Infinity,
181
- minY: Infinity,
182
- maxX: -Infinity,
183
- maxY: -Infinity,
184
- })
160
+ for (const r of targets) {
161
+ if (!(r instanceof CompoundRect)) {
162
+ continue
163
+ }
185
164
 
186
- r.minX = childrenBB.minX - Rect.LeftPadding
187
- r.minY = childrenBB.minY - Rect.TopPadding
188
- r.maxX = childrenBB.maxX + Rect.RightPadding
189
- r.maxY = childrenBB.maxY + Rect.BottomPadding
190
- })
165
+ applyConstraints(r.children)
166
+
167
+ const childrenBB = {
168
+ minX: Infinity,
169
+ minY: Infinity,
170
+ maxX: -Infinity,
171
+ maxY: -Infinity,
172
+ }
173
+
174
+ for (const child of r.children) {
175
+ childrenBB.minX = Math.min(childrenBB.minX, child.minX)
176
+ childrenBB.minY = Math.min(childrenBB.minY, child.minY)
177
+ childrenBB.maxX = Math.max(childrenBB.maxX, child.maxX)
178
+ childrenBB.maxY = Math.max(childrenBB.maxY, child.maxY)
179
+ }
180
+
181
+ r.minX = childrenBB.minX - Rect.LeftPadding
182
+ r.minY = childrenBB.minY - Rect.TopPadding
183
+ r.maxX = childrenBB.maxX + Rect.RightPadding
184
+ r.maxY = childrenBB.maxY + Rect.BottomPadding
185
+ }
191
186
  }
192
187
 
193
188
  function updateXYFlowNodes() {
@@ -201,7 +196,7 @@ export function createLayoutConstraints(
201
196
  position: r.position,
202
197
  positionAbsolute: r.positionAbsolute,
203
198
  })
204
- if (r instanceof Compound) {
199
+ if (r instanceof CompoundRect) {
205
200
  acc.push({
206
201
  id: r.id,
207
202
  type: 'dimensions',
@@ -216,29 +211,24 @@ export function createLayoutConstraints(
216
211
 
217
212
  let animationFrameId: number | null = null
218
213
 
219
- function onMove(
220
- updater: NodePositionUpdater,
221
- ) {
214
+ function onMove() {
222
215
  if (rectsToUpdate.length === 0) {
223
216
  return
224
217
  }
225
218
  animationFrameId ??= requestAnimationFrame(() => {
226
219
  animationFrameId = null
227
- updater(
228
- pipe(
229
- editingNodeIds,
230
- filter(id => rects.has(id) && nodeLookup.has(id)),
231
- map(id => ({
232
- rect: nonNullable(rects.get(id)),
233
- node: nonNullable(nodeLookup.get(id)),
234
- })),
235
- ),
236
- )
220
+ const { nodeLookup } = xyflowApi.getState()
221
+ for (const id of editingNodeIds) {
222
+ const rect = nonNullable(rects.get(id))
223
+ const node = nonNullable(nodeLookup.get(id))
224
+ rect.positionAbsolute = node.internals.positionAbsolute
225
+ }
237
226
  updateXYFlowNodes()
238
227
  })
239
228
  }
240
229
 
241
230
  return {
231
+ rects: rects as ReadonlyMap<string, Leaf | CompoundRect>,
242
232
  updateXYFlowNodes,
243
233
  onMove,
244
234
  }
@@ -256,32 +246,37 @@ export function useLayoutConstraints(): LayoutConstraints {
256
246
  const xystore = useXYStoreApi()
257
247
  const diagram = useDiagram()
258
248
  const solverRef = useRef<ReturnType<typeof createLayoutConstraints>>(undefined)
259
- return useMemo((): LayoutConstraints => ({
260
- onNodeDragStart: (_event, xynode) => {
261
- diagram.cancelSaveManualLayout()
262
- const { nodeLookup } = xystore.getState()
263
- const draggingNodes = pipe(
264
- Array.from(nodeLookup.values()),
265
- filter(n => n.dragging === true || n.id === xynode.id || n.selected === true),
266
- filter(n => n.draggable !== false),
267
- map(x => x.id),
268
- )
269
- if (hasAtLeast(draggingNodes, 1)) {
270
- solverRef.current = createLayoutConstraints(xystore, draggingNodes)
271
- }
272
- },
273
- onNodeDrag: () => {
274
- solverRef.current?.onMove((nodes) => {
275
- nodes.forEach(({ rect, node }) => {
276
- rect.positionAbsolute = node.internals.positionAbsolute
277
- })
278
- })
279
- },
280
- onNodeDragStop: () => {
281
- solverRef.current?.updateXYFlowNodes()
282
- diagram.scheduleSaveManualLayout()
283
- // diagramApi.getState().scheduleSaveManualLayout()
284
- solverRef.current = undefined
285
- },
286
- }), [xystore, diagram])
249
+ return useMemo((): LayoutConstraints => {
250
+ let wasPending = false
251
+ const initial = { x: 0, y: 0 }
252
+ let moved = false
253
+ return ({
254
+ onNodeDragStart: (_event, xynode) => {
255
+ wasPending = diagram.cancelSaveManualLayout()
256
+ const { nodeLookup } = xystore.getState()
257
+ const draggingNodes = pipe(
258
+ Array.from(nodeLookup.values()),
259
+ filter(n => n.draggable !== false && (n.dragging === true || n.id === xynode.id || n.selected === true)),
260
+ )
261
+ if (hasAtLeast(draggingNodes, 1)) {
262
+ solverRef.current = createLayoutConstraints(xystore, map(draggingNodes, x => x.id))
263
+ }
264
+ initial.x = _event.clientX
265
+ initial.y = _event.clientY
266
+ moved = false
267
+ },
268
+ onNodeDrag: (_event) => {
269
+ moved = Math.abs(_event.clientX - initial.x) > 4 || Math.abs(_event.clientY - initial.y) > 4
270
+ solverRef.current?.onMove()
271
+ },
272
+ onNodeDragStop: (_event) => {
273
+ moved = Math.abs(_event.clientX - initial.x) > 4 || Math.abs(_event.clientY - initial.y) > 4
274
+ console.log('moved', moved)
275
+ if (wasPending || moved) {
276
+ diagram.scheduleSaveManualLayout()
277
+ }
278
+ solverRef.current = undefined
279
+ },
280
+ })
281
+ }, [xystore, diagram])
287
282
  }
@@ -2,7 +2,7 @@ import { nonexhaustive } from '@likec4/core'
2
2
  import { Box, Button, Code, Group, Notification, ScrollAreaAutosize } from '@mantine/core'
3
3
  import { IconX } from '@tabler/icons-react'
4
4
  import { useSelector } from '@xstate/react'
5
- import { AnimatePresence, LayoutGroup, useReducedMotion } from 'framer-motion'
5
+ import { AnimatePresence, LayoutGroup, useReducedMotion, useReducedMotionConfig } from 'framer-motion'
6
6
  import { animate } from 'framer-motion/dom'
7
7
  import { memo, useEffect, useMemo } from 'react'
8
8
  import { type FallbackProps, ErrorBoundary } from 'react-error-boundary'
@@ -83,7 +83,7 @@ export const Overlays = memo(({ overlaysActorRef }: { overlaysActorRef: Overlays
83
83
  xyflowDomNode,
84
84
  ])
85
85
  const overlays = useSelector(overlaysActorRef, selectOverlays, compareSelectOverlays)
86
- const isMotionReduced = useReducedMotion() ?? false
86
+ const isMotionReduced = useReducedMotionConfig() ?? false
87
87
 
88
88
  const isActiveOverlay = overlays.length > 0
89
89
 
@@ -102,12 +102,13 @@ export const Overlays = memo(({ overlaysActorRef }: { overlaysActorRef: Overlays
102
102
  overlaysActorRef.send({ type: 'close', actorId: actorRef.id })
103
103
  }
104
104
 
105
- const overlaysReact = overlays.map((overlay) => {
105
+ const overlaysReact = overlays.map((overlay, index) => {
106
106
  switch (overlay.type) {
107
107
  case 'relationshipsBrowser':
108
108
  return (
109
109
  <Overlay
110
110
  key={overlay.actorRef.sessionId}
111
+ overlayLevel={Math.min(index, 3) as 0 | 1 | 2 | 3}
111
112
  onClose={() => close(overlay.actorRef)}>
112
113
  <RelationshipsBrowser actorRef={overlay.actorRef} />
113
114
  </Overlay>
@@ -115,6 +116,7 @@ export const Overlays = memo(({ overlaysActorRef }: { overlaysActorRef: Overlays
115
116
  case 'relationshipDetails':
116
117
  return (
117
118
  <Overlay
119
+ overlayLevel={Math.min(index, 3) as 0 | 1 | 2 | 3}
118
120
  key={overlay.actorRef.sessionId}
119
121
  onClose={() => close(overlay.actorRef)}>
120
122
  <RelationshipDetails actorRef={overlay.actorRef} />