@likec4/diagram 1.9.0 → 1.12.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 (113) hide show
  1. package/package.json +20 -43
  2. package/src/LikeC4CustomColors.tsx +5 -2
  3. package/src/LikeC4Diagram.css.ts +1 -1
  4. package/src/LikeC4Diagram.props.ts +40 -94
  5. package/src/LikeC4Diagram.tsx +14 -4
  6. package/src/LikeC4ModelProvider.tsx +21 -0
  7. package/src/StaticLikeC4Diagram.tsx +1 -0
  8. package/src/globals.css.ts +2 -3
  9. package/src/hooks/index.ts +1 -0
  10. package/src/hooks/useMantinePortalProps.ts +8 -0
  11. package/src/hooks/useXYFlow.ts +1 -2
  12. package/src/index.ts +9 -0
  13. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  14. package/src/likec4model/index.ts +1 -0
  15. package/src/likec4model/useLikeC4Model.ts +58 -0
  16. package/src/state/diagram-to-xyflow.ts +1 -2
  17. package/src/state/diagramStore.ts +185 -66
  18. package/src/{theme.css.ts → theme-vars.ts} +11 -4
  19. package/src/ui/DiagramTitlePanel.css.ts +2 -3
  20. package/src/ui/DiagramTitlePanel.tsx +25 -3
  21. package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
  22. package/src/ui/DynamicViewWalkthrough.tsx +97 -52
  23. package/src/ui/OptionsPanel.css.ts +1 -2
  24. package/src/ui/OptionsPanel.tsx +5 -2
  25. package/src/ui/notation/NotationPanel.css.ts +2 -3
  26. package/src/ui/notation/NotationPanel.tsx +10 -11
  27. package/src/ui/options/NodeOptions.tsx +4 -1
  28. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
  29. package/src/ui/top-left/TopLeftPanel.tsx +128 -117
  30. package/src/ui/top-left/_shared.ts +7 -6
  31. package/src/ui/top-left/styles.css.ts +48 -8
  32. package/src/utils/vector.ts +57 -0
  33. package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
  34. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
  35. package/src/xyflow/XYFlow.tsx +13 -17
  36. package/src/xyflow/XYFlowEvents.tsx +58 -58
  37. package/src/xyflow/XYFlowInner.tsx +4 -8
  38. package/src/xyflow/edges/EdgeLabel.tsx +185 -0
  39. package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
  40. package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
  41. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
  42. package/src/xyflow/edges/edges.css.ts +75 -7
  43. package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
  44. package/src/xyflow/nodes/compound/CompoundNode.tsx +25 -8
  45. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  46. package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
  47. package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
  48. package/src/xyflow/nodes/element/element.css.ts +1 -2
  49. package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
  50. package/src/xyflow/types.ts +1 -24
  51. package/src/xyflow/useLayoutConstraints.ts +10 -6
  52. package/dist/LikeC4CustomColors.d.ts +0 -3
  53. package/dist/LikeC4CustomColors.props.d.ts +0 -5
  54. package/dist/LikeC4Diagram.css.d.ts +0 -4
  55. package/dist/LikeC4Diagram.d.ts +0 -7
  56. package/dist/LikeC4Diagram.props.d.ts +0 -154
  57. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  58. package/dist/globals.css.d.ts +0 -1
  59. package/dist/hooks/index.d.ts +0 -4
  60. package/dist/hooks/useDiagramState.d.ts +0 -6
  61. package/dist/hooks/useSetState.d.ts +0 -3
  62. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  63. package/dist/hooks/useXYFlow.d.ts +0 -267
  64. package/dist/index.d.ts +0 -9
  65. package/dist/index.mjs +0 -3684
  66. package/dist/mantine/EnsureMantine.d.ts +0 -8
  67. package/dist/mantine.css.d.ts +0 -3
  68. package/dist/state/DiagramContext.d.ts +0 -1192
  69. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  70. package/dist/state/diagramStore.d.ts +0 -1268
  71. package/dist/style.css +0 -2139
  72. package/dist/theme.css.d.ts +0 -63
  73. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  74. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  75. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  76. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  77. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  78. package/dist/ui/OptionsPanel.d.ts +0 -1
  79. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  80. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  81. package/dist/ui/notation/index.d.ts +0 -1
  82. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  83. package/dist/ui/options/NodeOptions.d.ts +0 -19
  84. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  85. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  86. package/dist/ui/top-left/_shared.d.ts +0 -5
  87. package/dist/ui/top-left/styles.css.d.ts +0 -3
  88. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  89. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  90. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  91. package/dist/xyflow/XYFlow.d.ts +0 -12
  92. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  93. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  94. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  95. package/dist/xyflow/const.d.ts +0 -7
  96. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  97. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  98. package/dist/xyflow/edges/utils.d.ts +0 -17
  99. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  100. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  101. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  102. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  103. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  104. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  105. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  106. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  107. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  108. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  109. package/dist/xyflow/types.d.ts +0 -50
  110. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  111. package/dist/xyflow/utils.d.ts +0 -18
  112. package/src/mantine.css.ts +0 -7
  113. package/src/reset.d.ts +0 -2
@@ -0,0 +1,426 @@
1
+ import {
2
+ type BorderStyle,
3
+ defaultTheme,
4
+ type Element,
5
+ ElementShapes,
6
+ type Fqn,
7
+ type NonEmptyArray,
8
+ type ThemeColor,
9
+ type ViewChange
10
+ } from '@likec4/core'
11
+ import {
12
+ ActionIcon,
13
+ Box,
14
+ Button,
15
+ CheckIcon,
16
+ ColorSwatch,
17
+ Divider,
18
+ Flex,
19
+ Group,
20
+ keys,
21
+ Menu,
22
+ MenuDropdown,
23
+ MenuItem,
24
+ MenuTarget,
25
+ Paper,
26
+ Popover,
27
+ PopoverDropdown,
28
+ type PopoverProps,
29
+ PopoverTarget,
30
+ rem,
31
+ SegmentedControl,
32
+ Slider,
33
+ Space,
34
+ Stack,
35
+ Text,
36
+ Tooltip,
37
+ TooltipGroup
38
+ } from '@mantine/core'
39
+ import { IconCheck, IconFileSymlink, IconSelector } from '@tabler/icons-react'
40
+ import { NodeToolbar, type NodeToolbarProps } from '@xyflow/react'
41
+ import clsx from 'clsx'
42
+ import { useEffect, useState } from 'react'
43
+ import { useDiagramState, useDiagramStoreApi, useMantinePortalProps, useUpdateEffect } from '../../../hooks'
44
+
45
+ const {
46
+ primary,
47
+ secondary,
48
+ muted,
49
+ ...otherColors
50
+ } = defaultTheme.elements
51
+
52
+ const themedColors = [
53
+ { key: 'primary', value: primary.fill },
54
+ { key: 'secondary', value: secondary.fill },
55
+ { key: 'muted', value: muted.fill }
56
+ ] satisfies Array<{ key: ThemeColor; value: string }>
57
+
58
+ const colors = keys(otherColors).map(key => ({
59
+ key,
60
+ value: defaultTheme.elements[key].fill
61
+ }))
62
+
63
+ type ThemeColorKey = typeof themedColors[0]['key']
64
+ type ColorKey = typeof colors[0]['key']
65
+
66
+ const stopEventPropagation = (e: React.MouseEvent) => e.stopPropagation()
67
+
68
+ type ToolbarProps = NodeToolbarProps & {
69
+ element: Element
70
+ onColorPreview: (color: ThemeColor | null) => void
71
+ }
72
+
73
+ type OnStyleChange = (style: ViewChange.ChangeElementStyle['style']) => void
74
+
75
+ export function CompoundToolbar({
76
+ element,
77
+ onColorPreview,
78
+ ...props
79
+ }: ToolbarProps) {
80
+ const targets = [element.id] as NonEmptyArray<Fqn>
81
+ const diagramApi = useDiagramStoreApi()
82
+ const { hasGoToSource } = useDiagramState(s => ({
83
+ hasGoToSource: !!s.onOpenSourceElement
84
+ }))
85
+
86
+ const onChange: OnStyleChange = (style) => {
87
+ diagramApi.getState().triggerChangeElementStyle({
88
+ op: 'change-element-style',
89
+ style,
90
+ targets
91
+ })
92
+ }
93
+
94
+ return (
95
+ <Toolbar element={element} {...props}>
96
+ <ColorButton
97
+ element={element}
98
+ isOpacityEditable
99
+ onColorPreview={onColorPreview}
100
+ onChange={onChange}
101
+ position="left-start"
102
+ />
103
+ <BorderStyleOption
104
+ elementBorderStyle={element.style?.border}
105
+ onChange={onChange}
106
+ />
107
+ {hasGoToSource && <GoToSourceButton elementId={element.id} />}
108
+ </Toolbar>
109
+ )
110
+ }
111
+
112
+ function Toolbar({ element, children, ...props }: Omit<ToolbarProps, 'onColorPreview'>) {
113
+ return (
114
+ <NodeToolbar {...props}>
115
+ <Paper
116
+ className={clsx('nodrag', 'nopan')}
117
+ px={5}
118
+ pb={8}
119
+ pt={4}
120
+ radius={'sm'}
121
+ shadow="xl"
122
+ onDoubleClickCapture={stopEventPropagation}
123
+ onPointerDown={stopEventPropagation}
124
+ onClick={stopEventPropagation}
125
+ onDoubleClick={stopEventPropagation}
126
+ withBorder>
127
+ <Stack gap={4}>
128
+ <Box pl={2}>
129
+ <Text c="dimmed" fz={10} fw={500}>{element.id}</Text>
130
+ </Box>
131
+ <Group gap={3}>
132
+ {children}
133
+ </Group>
134
+ </Stack>
135
+ </Paper>
136
+ </NodeToolbar>
137
+ )
138
+ }
139
+
140
+ export function ElementToolbar({
141
+ element,
142
+ onColorPreview,
143
+ ...props
144
+ }: ToolbarProps) {
145
+ const targets = [element.id] as NonEmptyArray<Fqn>
146
+ const diagramApi = useDiagramStoreApi()
147
+ const { hasGoToSource } = useDiagramState(s => ({
148
+ hasGoToSource: !!s.onOpenSourceElement
149
+ }))
150
+ const portalProps = useMantinePortalProps()
151
+
152
+ const onChange: OnStyleChange = (style) => {
153
+ diagramApi.getState().triggerChangeElementStyle({
154
+ op: 'change-element-style',
155
+ style,
156
+ targets
157
+ })
158
+ }
159
+
160
+ return (
161
+ <Toolbar element={element} {...props}>
162
+ <Menu
163
+ openDelay={300}
164
+ closeDelay={450}
165
+ floatingStrategy={'fixed'}
166
+ closeOnClickOutside
167
+ clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
168
+ closeOnEscape
169
+ closeOnItemClick={false}
170
+ position="bottom-start"
171
+ offset={2}
172
+ styles={{
173
+ item: {
174
+ padding: 'calc(var(--mantine-spacing-xs) / 1.5) var(--mantine-spacing-xs)'
175
+ }
176
+ }}
177
+ {...portalProps}
178
+ >
179
+ <MenuTarget>
180
+ <Button
181
+ variant="light"
182
+ color="gray"
183
+ size="compact-xs"
184
+ rightSection={<IconSelector size={12} />}
185
+ >
186
+ {element.shape}
187
+ </Button>
188
+ </MenuTarget>
189
+ <MenuDropdown
190
+ // className={css.menuDropdown}
191
+ // onPointerDownCapture={stopEventPropagation}
192
+ // onPointerDown={stopEventPropagation}
193
+ onDoubleClick={stopEventPropagation}
194
+ onClick={stopEventPropagation}
195
+ >
196
+ {ElementShapes.map(shape => (
197
+ <MenuItem
198
+ fz={12}
199
+ fw={500}
200
+ key={shape}
201
+ value={shape}
202
+ rightSection={element.shape === shape ? <IconCheck size={12} /> : undefined}
203
+ onClick={e => {
204
+ e.stopPropagation()
205
+ onChange({ shape })
206
+ }}
207
+ >
208
+ {shape}
209
+ </MenuItem>
210
+ ))}
211
+ </MenuDropdown>
212
+ </Menu>
213
+ <ColorButton
214
+ element={element}
215
+ onColorPreview={onColorPreview}
216
+ onChange={onChange}
217
+ position="right-end"
218
+ />
219
+ {hasGoToSource && <GoToSourceButton elementId={element.id} />}
220
+ </Toolbar>
221
+ )
222
+ }
223
+
224
+ type ColorButtonProps = Omit<PopoverProps, 'onChange'> & {
225
+ element: Element
226
+ isOpacityEditable?: boolean
227
+ onColorPreview: (color: ThemeColor | null) => void
228
+ onChange: OnStyleChange
229
+ }
230
+
231
+ function GoToSourceButton({ elementId }: { elementId: Fqn }) {
232
+ const diagramApi = useDiagramStoreApi()
233
+ return (
234
+ <ActionIcon
235
+ size={'sm'}
236
+ variant="subtle"
237
+ color="gray"
238
+ onClick={e => {
239
+ e.stopPropagation()
240
+ diagramApi.getState().onOpenSourceElement?.(elementId)
241
+ }}>
242
+ <IconFileSymlink size={'70%'} stroke={1.8} />
243
+ </ActionIcon>
244
+ )
245
+ }
246
+
247
+ function ColorButton({
248
+ element,
249
+ onColorPreview,
250
+ isOpacityEditable = false,
251
+ onChange,
252
+ ...props
253
+ }: ColorButtonProps) {
254
+ const elementColor: ThemeColor = (element.color as ThemeColor) ?? 'primary'
255
+ return (
256
+ <Popover
257
+ clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
258
+ position="right-end"
259
+ offset={2}
260
+ {...props}
261
+ >
262
+ <PopoverTarget>
263
+ <Button variant="subtle" color="gray" size="compact-xs" px={3}>
264
+ <ColorSwatch
265
+ color={defaultTheme.elements[elementColor]?.fill}
266
+ size={14}
267
+ withShadow
268
+ style={{ color: '#fff', cursor: 'pointer' }}
269
+ />
270
+ </Button>
271
+ </PopoverTarget>
272
+ <PopoverDropdown p={'xs'}>
273
+ <ColorSwatches
274
+ elementColor={elementColor}
275
+ onColorPreview={onColorPreview}
276
+ onChange={(color) => onChange({ color })}
277
+ />
278
+ {isOpacityEditable && (
279
+ <>
280
+ <Space h={'xs'} />
281
+ <Divider label="opacity" labelPosition="left" />
282
+ <Space h={'xs'} />
283
+ <OpacityOption
284
+ elementOpacity={element.style?.opacity}
285
+ onOpacityChange={(opacity) => {
286
+ onChange({ opacity })
287
+ }} />
288
+ </>
289
+ )}
290
+ </PopoverDropdown>
291
+ </Popover>
292
+ )
293
+ }
294
+
295
+ function ColorSwatches({
296
+ elementColor,
297
+ onColorPreview,
298
+ onChange
299
+ }: {
300
+ elementColor: ThemeColor
301
+ onColorPreview: (color: ThemeColor | null) => void
302
+ onChange: (color: ThemeColor) => void
303
+ }) {
304
+ const changeColor = (color: ColorKey | ThemeColorKey) => (e: React.MouseEvent) => {
305
+ e.stopPropagation()
306
+ onColorPreview(null)
307
+ if (elementColor === color) {
308
+ return
309
+ }
310
+ onChange(color)
311
+ }
312
+ return (
313
+ <Stack gap={2} onMouseLeave={() => onColorPreview(null)}>
314
+ <TooltipGroup openDelay={1000} closeDelay={300}>
315
+ <Flex maw={120} gap="6" justify="flex-start" align="flex-start" direction="row" wrap="wrap">
316
+ {themedColors.map(({ key, value }) => (
317
+ <Tooltip
318
+ key={key}
319
+ label={key}
320
+ fz={'xs'}
321
+ color="dark"
322
+ offset={2}
323
+ transitionProps={{ duration: 140, transition: 'slide-up' }}>
324
+ <ColorSwatch
325
+ color={value}
326
+ size={18}
327
+ withShadow
328
+ onMouseEnter={() => onColorPreview(key)}
329
+ onClick={changeColor(key)}
330
+ style={{ color: '#fff', cursor: 'pointer' }}
331
+ >
332
+ {elementColor === key && <CheckIcon style={{ width: rem(10), height: rem(10) }} />}
333
+ </ColorSwatch>
334
+ </Tooltip>
335
+ ))}
336
+ </Flex>
337
+
338
+ <Flex
339
+ mt="sm"
340
+ maw={110}
341
+ gap="6"
342
+ justify="flex-start"
343
+ align="flex-start"
344
+ direction="row"
345
+ wrap="wrap">
346
+ {colors.map(({ key, value }) => (
347
+ <Tooltip
348
+ key={key}
349
+ label={key}
350
+ fz={'xs'}
351
+ color="dark"
352
+ offset={2}
353
+ transitionProps={{ duration: 140, transition: 'slide-up' }}>
354
+ <ColorSwatch
355
+ color={value}
356
+ size={18}
357
+ onMouseEnter={() => onColorPreview(key)}
358
+ onClick={changeColor(key)}
359
+ style={{ color: '#fff', cursor: 'pointer' }}
360
+ >
361
+ {elementColor === key && <CheckIcon style={{ width: rem(10), height: rem(10) }} />}
362
+ </ColorSwatch>
363
+ </Tooltip>
364
+ ))}
365
+ </Flex>
366
+ </TooltipGroup>
367
+ </Stack>
368
+ )
369
+ }
370
+
371
+ function BorderStyleOption({
372
+ elementBorderStyle = 'dashed',
373
+ onChange
374
+ }: {
375
+ elementBorderStyle: BorderStyle | undefined
376
+ onChange: OnStyleChange
377
+ }) {
378
+ const [value, setValue] = useState(elementBorderStyle)
379
+ useEffect(() => {
380
+ setValue(elementBorderStyle)
381
+ }, [elementBorderStyle])
382
+
383
+ return (
384
+ <Box>
385
+ <SegmentedControl
386
+ size="xs"
387
+ fullWidth
388
+ withItemsBorders={false}
389
+ value={value}
390
+ onChange={v => {
391
+ const border = v as BorderStyle
392
+ setValue(border)
393
+ onChange({ border })
394
+ }}
395
+ data={[
396
+ { label: 'Solid', value: 'solid' },
397
+ { label: 'Dashed', value: 'dashed' },
398
+ { label: 'Dotted', value: 'dotted' },
399
+ { label: 'None', value: 'none' }
400
+ ]}
401
+ />
402
+ </Box>
403
+ )
404
+ }
405
+
406
+ function OpacityOption({
407
+ elementOpacity = 100,
408
+ onOpacityChange
409
+ }: {
410
+ elementOpacity: number | undefined
411
+ onOpacityChange: (opacity: number) => void
412
+ }) {
413
+ const [value, setValue] = useState(elementOpacity)
414
+ useUpdateEffect(() => {
415
+ setValue(elementOpacity)
416
+ }, [elementOpacity])
417
+
418
+ return (
419
+ <Slider
420
+ size={'sm'}
421
+ color={'dark'}
422
+ value={value}
423
+ onChange={setValue}
424
+ onChangeEnd={onOpacityChange} />
425
+ )
426
+ }
@@ -1,17 +1,6 @@
1
1
  import type { BBox, DiagramEdge, DiagramNode, Fqn, XYPoint } from '@likec4/core'
2
- import type {
3
- Edge,
4
- EdgeMouseHandler,
5
- InternalNode,
6
- Node,
7
- NodeMouseHandler,
8
- OnMoveEnd,
9
- OnMoveStart,
10
- ReactFlowInstance,
11
- ReactFlowState
12
- } from '@xyflow/react'
2
+ import type { Edge, InternalNode, Node, ReactFlowInstance, ReactFlowState } from '@xyflow/react'
13
3
  import { isNode } from '@xyflow/react'
14
- import type { MouseEvent as ReactMouseEvent } from 'react'
15
4
  import { isTruthy } from 'remeda'
16
5
  import type { Simplify } from 'type-fest'
17
6
 
@@ -44,7 +33,6 @@ export type RelationshipData = {
44
33
  edge: DiagramEdge
45
34
  // if set - edge was changed by user
46
35
  controlPoints: XYPoint[] | null
47
- stepNum: number | null
48
36
  label: null | {
49
37
  bbox: BBox
50
38
  text: string
@@ -69,14 +57,3 @@ export namespace XYFlowEdge {
69
57
 
70
58
  export type XYFlowInstance = ReactFlowInstance<XYFlowNode, XYFlowEdge>
71
59
  export type XYFlowState = ReactFlowState<XYFlowNode, XYFlowEdge>
72
-
73
- export type XYFlowEventHandlers = {
74
- onPanelClick: (event: ReactMouseEvent) => void
75
- onNodeContextMenu: NodeMouseHandler<XYFlowNode>
76
- onEdgeContextMenu: EdgeMouseHandler<XYFlowEdge>
77
- onPaneContextMenu: (event: ReactMouseEvent | MouseEvent) => void
78
- onNodeClick: NodeMouseHandler<XYFlowNode>
79
- onEdgeClick: EdgeMouseHandler<XYFlowEdge>
80
- onMoveStart: OnMoveStart
81
- onMoveEnd: OnMoveEnd
82
- }
@@ -181,12 +181,16 @@ function createLayoutConstraints(
181
181
  }
182
182
  }
183
183
 
184
+ const draggingNodes = new Set(nodeLookup.values()
185
+ .filter(x => x.dragging === true || x.id === draggingNodeId || x.selected)
186
+ .map(x => x.id))
187
+
184
188
  while (traverse.length > 0) {
185
189
  const { xynode, parent } = traverse.shift()!
186
- const isDragging = xynode.dragging === true || xynode.id === draggingNodeId
190
+ const isDragging = draggingNodes.has(xynode.id)
187
191
 
188
192
  // Traverse children if the node is a compound, not dragging, and is an ancestor of the dragging node
189
- const shouldTraverse = !isDragging && xynode.type === 'compound' && isAncestor(xynode.id, draggingNodeId)
193
+ const shouldTraverse = !isDragging && xynode.type === 'compound' && draggingNodes.values().some(x => isAncestor(xynode.id, x))
190
194
 
191
195
  const rect = shouldTraverse ? new Compound(solver, xynode, parent) : new Leaf(solver, xynode, parent, isDragging)
192
196
  rects.set(xynode.id, rect)
@@ -203,8 +207,7 @@ function createLayoutConstraints(
203
207
 
204
208
  solver.updateVariables()
205
209
  solver.maxIterations = 1000
206
-
207
- const rectsToUpdate = [...rects.values()].filter(r => r.id !== draggingNodeId)
210
+ const rectsToUpdate = [...rects.values()].filter(r => !draggingNodes.has(r.id))
208
211
 
209
212
  function updateXYFlowNodes() {
210
213
  solver.updateVariables()
@@ -243,8 +246,8 @@ function createLayoutConstraints(
243
246
  animationFrameId = null
244
247
  const pos = nodeLookup.get(xynode.id)!.internals.positionAbsolute
245
248
  const rect = nonNullable(rects.get(xynode.id))
246
- solver.suggestValue(rect.minX, Math.ceil(pos.x))
247
- solver.suggestValue(rect.minY, Math.ceil(pos.y))
249
+ solver.suggestValue(rect.minX, Math.floor(pos.x))
250
+ solver.suggestValue(rect.minY, Math.floor(pos.y))
248
251
  updateXYFlowNodes()
249
252
  })
250
253
  }
@@ -273,6 +276,7 @@ export function useLayoutConstraints(): LayoutConstraints {
273
276
  solverRef.current?.onNodeDrag(xynode)
274
277
  },
275
278
  onNodeDragStop: (_event, _xynode) => {
279
+ solverRef.current?.updateXYFlowNodes()
276
280
  diagramApi.getState().scheduleSaveManualLayout()
277
281
  solverRef.current = undefined
278
282
  }
@@ -1,3 +0,0 @@
1
- import { LikeC4CustomColorsProperties } from './LikeC4CustomColors.props';
2
-
3
- export declare function LikeC4CustomColors({ customColors }: LikeC4CustomColorsProperties): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +0,0 @@
1
- import { CustomColorDefinitions } from '@likec4/core';
2
-
3
- export interface LikeC4CustomColorsProperties {
4
- customColors: CustomColorDefinitions;
5
- }
@@ -1,4 +0,0 @@
1
- export declare const cssReactFlow: string;
2
- export declare const cssDisablePan: string;
3
- export declare const cssTransparentBg: string;
4
- export declare const cssNoControls: string;
@@ -1,7 +0,0 @@
1
- import { LikeC4DiagramEventHandlers, LikeC4DiagramProperties } from './LikeC4Diagram.props';
2
-
3
- export type LikeC4DiagramProps = LikeC4DiagramProperties & LikeC4DiagramEventHandlers;
4
- export declare function LikeC4Diagram({ view, className, fitView, fitViewPadding, readonly, pannable, zoomable, nodesSelectable, nodesDraggable, background, controls, showElementLinks, showDiagramTitle, showNotations, enableDynamicViewWalkthrough, enableFocusMode, initialWidth, initialHeight, keepAspectRatio, experimentalEdgeEditing, onCanvasClick, onCanvasContextMenu, onCanvasDblClick, onEdgeClick, onChange, onEdgeContextMenu, onNavigateTo, onNodeClick, onNodeContextMenu, onOpenSourceElement, onOpenSourceView, onOpenSourceRelation, onBurgerMenuClick, renderIcon, where, showNavigationButtons }: LikeC4DiagramProps): import("react/jsx-runtime").JSX.Element;
5
- export declare namespace LikeC4Diagram {
6
- var displayName: string;
7
- }
@@ -1,154 +0,0 @@
1
- import { DiagramEdge, DiagramNode, DiagramView, Fqn, RelationID, ViewChange, ViewID, WhereOperator } from '@likec4/core';
2
- import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
3
- import { XYFlowEdge, XYFlowNode } from './xyflow/types';
4
- import { XYBackground } from './xyflow/XYFlowBackground';
5
-
6
- export type { WhereOperator };
7
- export type DiagramNodeWithNavigate = Omit<DiagramNode, 'navigateTo'> & {
8
- navigateTo: ViewID;
9
- };
10
- type ElementIconNodeProps = {
11
- id: string;
12
- title: string;
13
- icon?: string | undefined;
14
- };
15
- export type ElementIconRenderer = (props: {
16
- node: ElementIconNodeProps;
17
- }) => ReactNode;
18
- export type OnNavigateTo = {
19
- (to: ViewID, event?: ReactMouseEvent, element?: DiagramNodeWithNavigate, xynode?: XYFlowNode): void;
20
- };
21
- export type OnNodeClick = (event: {
22
- element: DiagramNode;
23
- xynode: XYFlowNode;
24
- event: ReactMouseEvent;
25
- }) => void;
26
- export type OnEdgeClick = (event: {
27
- edge: DiagramEdge;
28
- xyedge: XYFlowEdge;
29
- event: ReactMouseEvent;
30
- }) => void;
31
- /**
32
- * On pane/canvas click (not on any node or edge)
33
- */
34
- export type OnCanvasClick = (event: ReactMouseEvent) => void;
35
- export type ChangeEvent = {
36
- change: ViewChange;
37
- };
38
- export type OnChange = {
39
- (event: ChangeEvent): void;
40
- };
41
- export type LikeC4ColorScheme = 'light' | 'dark';
42
- export interface LikeC4DiagramProperties {
43
- view: DiagramView;
44
- className?: string | undefined;
45
- /**
46
- * Controls color scheme used for styling the flow
47
- * By default inherits from system or surrounding MantineProvider
48
- *
49
- * @example 'light' | 'dark'
50
- */
51
- /**
52
- * Show/hide controls menu
53
- * @default false
54
- */
55
- controls?: boolean | undefined;
56
- /**
57
- * Enable/disable panning
58
- * @default true
59
- */
60
- pannable?: boolean | undefined;
61
- /**
62
- * Enable/disable zooming
63
- * @default true
64
- */
65
- zoomable?: boolean | undefined;
66
- /**
67
- * Disable any editing (dragging still can be enabled with `nodesDraggable`)
68
- * @default true
69
- */
70
- readonly?: boolean | undefined;
71
- /**
72
- * If set, initial viewport will show all nodes & edges
73
- * @default true
74
- */
75
- fitView?: boolean | undefined;
76
- /**
77
- * Seems like this is percentage of the view size
78
- * @default 0
79
- */
80
- fitViewPadding?: number | undefined;
81
- nodesSelectable?: boolean | undefined;
82
- /**
83
- * @default false
84
- */
85
- nodesDraggable?: boolean | undefined;
86
- initialWidth?: number | undefined;
87
- initialHeight?: number | undefined;
88
- /**
89
- * Keep aspect ratio of the diagram
90
- * @default false
91
- */
92
- keepAspectRatio?: boolean | undefined;
93
- /**
94
- * Background pattern
95
- * @default 'dots'
96
- */
97
- background?: 'transparent' | 'solid' | XYBackground | undefined;
98
- /**
99
- * Display hovercards with element links
100
- * @default true
101
- */
102
- showElementLinks?: boolean | undefined;
103
- /**
104
- * Display panel with diagram title / description
105
- * @default true
106
- */
107
- showDiagramTitle?: boolean | undefined;
108
- /**
109
- * Show back/forward navigation buttons
110
- * @default false
111
- */
112
- showNavigationButtons?: undefined | boolean;
113
- /**
114
- * Display notations of the view
115
- * @default true
116
- */
117
- showNotations?: boolean | undefined;
118
- /**
119
- * If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
120
- * @default true
121
- */
122
- enableFocusMode?: boolean | undefined;
123
- /**
124
- * If Walkthrough for dynamic views should be enabled
125
- * @default false
126
- */
127
- enableDynamicViewWalkthrough?: boolean | undefined;
128
- /**
129
- * Experimental feature to enable edge editing
130
- * @default false
131
- */
132
- experimentalEdgeEditing?: boolean | undefined;
133
- /**
134
- * Render custom icon for a node
135
- * By default, if icon is http:// or https://, it will be rendered as an image
136
- */
137
- renderIcon?: ElementIconRenderer | undefined;
138
- where?: WhereOperator<string, string> | undefined;
139
- }
140
- export interface LikeC4DiagramEventHandlers {
141
- onChange?: OnChange | null | undefined;
142
- onNavigateTo?: OnNavigateTo | null | undefined;
143
- onNodeClick?: OnNodeClick | null | undefined;
144
- onNodeContextMenu?: OnNodeClick | null | undefined;
145
- onCanvasContextMenu?: OnCanvasClick | null | undefined;
146
- onEdgeClick?: OnEdgeClick | null | undefined;
147
- onEdgeContextMenu?: OnEdgeClick | null | undefined;
148
- onCanvasClick?: OnCanvasClick | null | undefined;
149
- onCanvasDblClick?: OnCanvasClick | null | undefined;
150
- onBurgerMenuClick?: null | undefined | (() => void);
151
- onOpenSourceView?: null | undefined | (() => void);
152
- onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void);
153
- onOpenSourceRelation?: null | undefined | ((id: RelationID) => void);
154
- }
@@ -1,4 +0,0 @@
1
- import { LikeC4DiagramProperties } from './LikeC4Diagram.props';
2
-
3
- export type StaticLikeC4DiagramProps = Pick<LikeC4DiagramProperties, 'view' | 'keepAspectRatio' | 'className' | 'fitView' | 'fitViewPadding' | 'background' | 'initialWidth' | 'initialHeight' | 'renderIcon' | 'where'>;
4
- export declare function StaticLikeC4Diagram({ view, fitView, fitViewPadding, background, ...rest }: StaticLikeC4DiagramProps): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare const rootClassName = "likec4-diagram-root";