@likec4/diagram 1.8.1 → 1.12.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 (114) hide show
  1. package/package.json +21 -45
  2. package/src/LikeC4CustomColors.props.ts +5 -0
  3. package/src/LikeC4CustomColors.tsx +37 -0
  4. package/src/LikeC4Diagram.css.ts +1 -1
  5. package/src/LikeC4Diagram.props.ts +43 -95
  6. package/src/LikeC4Diagram.tsx +21 -2
  7. package/src/LikeC4ModelProvider.tsx +49 -0
  8. package/src/StaticLikeC4Diagram.tsx +1 -0
  9. package/src/globals.css.ts +2 -3
  10. package/src/hooks/index.ts +1 -0
  11. package/src/hooks/useMantinePortalProps.ts +8 -0
  12. package/src/hooks/useXYFlow.ts +1 -2
  13. package/src/index.ts +3 -0
  14. package/src/likec4model/LikeC4ModelContext.ts +4 -0
  15. package/src/likec4model/index.ts +1 -0
  16. package/src/likec4model/useLikeC4Model.ts +55 -0
  17. package/src/mantine/EnsureMantine.tsx +1 -0
  18. package/src/state/DiagramContext.tsx +1 -0
  19. package/src/state/diagram-to-xyflow.ts +2 -3
  20. package/src/state/diagramStore.ts +185 -66
  21. package/src/{theme.css.ts → theme-vars.ts} +11 -4
  22. package/src/ui/DiagramTitlePanel.css.ts +2 -3
  23. package/src/ui/DiagramTitlePanel.tsx +17 -1
  24. package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
  25. package/src/ui/DynamicViewWalkthrough.tsx +97 -52
  26. package/src/ui/OptionsPanel.css.ts +1 -2
  27. package/src/ui/OptionsPanel.tsx +1 -1
  28. package/src/ui/notation/NotationPanel.css.ts +2 -3
  29. package/src/ui/notation/NotationPanel.tsx +11 -12
  30. package/src/ui/options/NodeOptions.tsx +1 -1
  31. package/src/ui/top-left/ChangeAutoLayoutButton.tsx +20 -32
  32. package/src/ui/top-left/TopLeftPanel.tsx +128 -117
  33. package/src/ui/top-left/_shared.ts +7 -6
  34. package/src/ui/top-left/styles.css.ts +17 -8
  35. package/src/utils/vector.ts +57 -0
  36. package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
  37. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
  38. package/src/xyflow/XYFlow.tsx +10 -11
  39. package/src/xyflow/XYFlowEvents.tsx +59 -60
  40. package/src/xyflow/XYFlowInner.tsx +5 -5
  41. package/src/xyflow/edges/EdgeLabel.tsx +185 -0
  42. package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
  43. package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
  44. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
  45. package/src/xyflow/edges/edges.css.ts +75 -7
  46. package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
  47. package/src/xyflow/nodes/compound/CompoundNode.tsx +4 -4
  48. package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
  49. package/src/xyflow/nodes/element/ElementLink.tsx +3 -8
  50. package/src/xyflow/nodes/element/ElementNode.tsx +47 -4
  51. package/src/xyflow/nodes/element/element.css.ts +1 -2
  52. package/src/xyflow/types.ts +1 -24
  53. package/src/xyflow/useLayoutConstraints.ts +10 -6
  54. package/src/xyflow/utils.ts +1 -1
  55. package/dist/LikeC4Diagram.css.d.ts +0 -4
  56. package/dist/LikeC4Diagram.d.ts +0 -4
  57. package/dist/LikeC4Diagram.props.d.ts +0 -185
  58. package/dist/StaticLikeC4Diagram.d.ts +0 -4
  59. package/dist/globals.css.d.ts +0 -1
  60. package/dist/hooks/index.d.ts +0 -4
  61. package/dist/hooks/useDiagramState.d.ts +0 -6
  62. package/dist/hooks/useSetState.d.ts +0 -3
  63. package/dist/hooks/useUpdateEffect.d.ts +0 -5
  64. package/dist/hooks/useXYFlow.d.ts +0 -267
  65. package/dist/index.d.ts +0 -9
  66. package/dist/index.mjs +0 -3651
  67. package/dist/mantine/EnsureMantine.d.ts +0 -5
  68. package/dist/mantine.css.d.ts +0 -3
  69. package/dist/state/DiagramContext.d.ts +0 -1189
  70. package/dist/state/diagram-to-xyflow.d.ts +0 -12
  71. package/dist/state/diagramStore.d.ts +0 -1268
  72. package/dist/style.css +0 -2139
  73. package/dist/theme.css.d.ts +0 -63
  74. package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
  75. package/dist/ui/DiagramTitlePanel.d.ts +0 -1
  76. package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
  77. package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
  78. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  79. package/dist/ui/OptionsPanel.d.ts +0 -1
  80. package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
  81. package/dist/ui/notation/NotationPanel.d.ts +0 -1
  82. package/dist/ui/notation/index.d.ts +0 -1
  83. package/dist/ui/options/EdgeOptions.d.ts +0 -19
  84. package/dist/ui/options/NodeOptions.d.ts +0 -19
  85. package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
  86. package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
  87. package/dist/ui/top-left/_shared.d.ts +0 -5
  88. package/dist/ui/top-left/styles.css.d.ts +0 -3
  89. package/dist/xyflow/EdgeMarkers.d.ts +0 -12
  90. package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
  91. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
  92. package/dist/xyflow/XYFlow.d.ts +0 -9
  93. package/dist/xyflow/XYFlowBackground.d.ts +0 -11
  94. package/dist/xyflow/XYFlowEvents.d.ts +0 -161
  95. package/dist/xyflow/XYFlowInner.d.ts +0 -10
  96. package/dist/xyflow/const.d.ts +0 -7
  97. package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
  98. package/dist/xyflow/edges/edges.css.d.ts +0 -12
  99. package/dist/xyflow/edges/utils.d.ts +0 -17
  100. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
  101. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
  102. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  103. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
  104. package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
  105. package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
  106. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
  107. package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
  108. package/dist/xyflow/nodes/element/index.d.ts +0 -1
  109. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
  110. package/dist/xyflow/types.d.ts +0 -50
  111. package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
  112. package/dist/xyflow/utils.d.ts +0 -18
  113. package/src/mantine.css.ts +0 -7
  114. package/src/reset.d.ts +0 -2
@@ -0,0 +1,214 @@
1
+ import { type DiagramNode, LikeC4Model, nameFromFqn } from '@likec4/core'
2
+ import {
3
+ ActionIcon,
4
+ Box,
5
+ Group,
6
+ Menu,
7
+ MenuDivider,
8
+ MenuDropdown,
9
+ MenuItem,
10
+ MenuLabel,
11
+ MenuTarget,
12
+ Space,
13
+ Stack,
14
+ type StackProps,
15
+ Text,
16
+ Tooltip as MantineTooltip,
17
+ TooltipGroup
18
+ } from '@mantine/core'
19
+ import { IconArrowRight, IconFileSymlink, IconZoomScan } from '@tabler/icons-react'
20
+ import clsx from 'clsx'
21
+ import { forwardRef, Fragment, type MouseEventHandler, type PropsWithChildren } from 'react'
22
+ import { filter, isTruthy, map, partition, pipe } from 'remeda'
23
+ import { useDiagramState, useDiagramStoreApi, useMantinePortalProps, useXYNodesData } from '../../hooks'
24
+ import type { RelationshipData } from '../types'
25
+ import * as css from './RelationshipsDropdownMenu.css'
26
+
27
+ const stopPropagation: MouseEventHandler = (e) => e.stopPropagation()
28
+
29
+ export const Tooltip = MantineTooltip.withProps({
30
+ color: 'gray',
31
+ fz: 'xs',
32
+ openDelay: 300,
33
+ closeDelay: 150,
34
+ label: '',
35
+ children: null,
36
+ offset: 8,
37
+ withinPortal: false
38
+ })
39
+
40
+ export function RelationshipsDropdownMenu({
41
+ edge,
42
+ disabled = false,
43
+ likec4model,
44
+ children
45
+ }: PropsWithChildren<{
46
+ edge: RelationshipData['edge']
47
+ disabled?: boolean | undefined
48
+ likec4model: LikeC4Model
49
+ }>) {
50
+ const portalProps = useMantinePortalProps()
51
+ const [sourceXYNode, targetXYNode] = useXYNodesData([edge.source, edge.target])
52
+
53
+ if (!sourceXYNode || !targetXYNode) return children
54
+
55
+ const [direct, nested] = pipe(
56
+ edge.relations,
57
+ map(id => {
58
+ try {
59
+ return likec4model.relationship(id)
60
+ } catch (e) {
61
+ // View was cached, but likec4model based on new data
62
+ console.error(
63
+ `View is cached and likec4model missing relationship ${id} from ${edge.source} -> ${edge.target}`,
64
+ e
65
+ )
66
+ return null
67
+ }
68
+ }),
69
+ filter(isTruthy),
70
+ partition(r => r.relationship.source === edge.source && r.relationship.target === edge.target)
71
+ )
72
+
73
+ if (direct.length + nested.length === 0) {
74
+ return <>{children}</>
75
+ }
76
+
77
+ const renderRelationship = (relationship: LikeC4Model.ViewModel.Relationship, index: number) => (
78
+ <Fragment key={relationship.id}>
79
+ {index > 0 && <MenuDivider opacity={0.65} />}
80
+ <MenuItem
81
+ component={Relationship}
82
+ relationship={relationship}
83
+ sourceNode={sourceXYNode.data.element}
84
+ targetNode={targetXYNode.data.element}
85
+ edge={edge} />
86
+ </Fragment>
87
+ )
88
+
89
+ return (
90
+ <Menu
91
+ trigger={'click-hover'}
92
+ openDelay={300}
93
+ closeDelay={450}
94
+ floatingStrategy={'fixed'}
95
+ closeOnClickOutside
96
+ clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
97
+ closeOnEscape
98
+ closeOnItemClick={false}
99
+ disabled={disabled}
100
+ {...portalProps}
101
+ >
102
+ <MenuTarget>
103
+ {children}
104
+ </MenuTarget>
105
+ <MenuDropdown
106
+ className={css.menuDropdown}
107
+ onPointerDownCapture={stopPropagation}
108
+ onPointerDown={stopPropagation}
109
+ onClick={stopPropagation}
110
+ onDoubleClick={stopPropagation}
111
+ >
112
+ {direct.length > 0 && (
113
+ <>
114
+ <MenuLabel>direct relationships</MenuLabel>
115
+ {direct.map(renderRelationship)}
116
+ </>
117
+ )}
118
+ {nested.length > 0 && (
119
+ <>
120
+ {direct.length > 0 && <MenuDivider />}
121
+ <MenuLabel>resolved from nested</MenuLabel>
122
+ {nested.map(renderRelationship)}
123
+ </>
124
+ )}
125
+ </MenuDropdown>
126
+ </Menu>
127
+ )
128
+ }
129
+
130
+ const Relationship = forwardRef<
131
+ HTMLDivElement,
132
+ StackProps & {
133
+ relationship: LikeC4Model.ViewModel.Relationship
134
+ edge: RelationshipData['edge']
135
+ sourceNode: DiagramNode
136
+ targetNode: DiagramNode
137
+ }
138
+ >(({
139
+ className,
140
+ relationship: r,
141
+ edge,
142
+ sourceNode,
143
+ targetNode,
144
+ ...props
145
+ }, ref) => {
146
+ const diagramApi = useDiagramStoreApi()
147
+ const {
148
+ viewId,
149
+ hasOnOpenSourceRelation,
150
+ hasOnNavigateTo
151
+ } = useDiagramState(s => ({
152
+ viewId: s.view.id,
153
+ hasOnOpenSourceRelation: !!s.onOpenSourceRelation,
154
+ hasOnNavigateTo: !!s.onNavigateTo
155
+ }))
156
+ const sourceId = nameFromFqn(edge.source) + r.source.id.slice(edge.source.length)
157
+ const targetId = nameFromFqn(edge.target) + r.target.id.slice(edge.target.length)
158
+ const navigateTo = hasOnNavigateTo && r.relationship.navigateTo !== viewId ? r.relationship.navigateTo : undefined
159
+
160
+ return (
161
+ <Stack ref={ref} className={clsx(css.menuItemRelationship, className)} {...props}>
162
+ <Group gap={4}>
163
+ <Text component="div" className={css.endpoint} data-likec4-color={sourceNode.color}>
164
+ {sourceId}
165
+ </Text>
166
+ <IconArrowRight stroke={2.5} size={11} />
167
+ <Text component="div" className={css.endpoint} data-likec4-color={targetNode.color}>
168
+ {targetId}
169
+ </Text>
170
+ {(navigateTo || hasOnOpenSourceRelation) && (
171
+ <TooltipGroup openDelay={100}>
172
+ <Space w={'xs'} />
173
+ {navigateTo && (
174
+ <Tooltip label={'Open dynamic view'}>
175
+ <ActionIcon
176
+ className={clsx('nodrag nopan')}
177
+ size={'sm'}
178
+ radius="sm"
179
+ variant="default"
180
+ onPointerDownCapture={stopPropagation}
181
+ onClick={event => {
182
+ diagramApi.getState().onNavigateTo?.(navigateTo, event)
183
+ }}
184
+ role="button"
185
+ >
186
+ <IconZoomScan size="80%" stroke={2} />
187
+ </ActionIcon>
188
+ </Tooltip>
189
+ )}
190
+ {hasOnOpenSourceRelation && (
191
+ <Tooltip label={'Open source'}>
192
+ <ActionIcon
193
+ className={clsx('nodrag nopan')}
194
+ size={'sm'}
195
+ radius="sm"
196
+ variant="default"
197
+ onPointerDownCapture={stopPropagation}
198
+ onClick={event => {
199
+ event.stopPropagation()
200
+ diagramApi.getState().onOpenSourceRelation?.(r.id)
201
+ }}
202
+ role="button"
203
+ >
204
+ <IconFileSymlink size="80%" stroke={2} />
205
+ </ActionIcon>
206
+ </Tooltip>
207
+ )}
208
+ </TooltipGroup>
209
+ )}
210
+ </Group>
211
+ <Box className={css.title}>{r.title || 'untitled'}</Box>
212
+ </Stack>
213
+ )
214
+ })
@@ -1,7 +1,6 @@
1
1
  import { rem } from '@mantine/core'
2
2
  import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
- import { mantine } from '../../mantine.css'
4
- import { vars, xyvars } from '../../theme.css'
3
+ import { mantine, vars, xyvars } from '../../theme-vars'
5
4
 
6
5
  const mixColor = createVar('mix-color')
7
6
 
@@ -145,7 +144,7 @@ export const cssEdgePath = style({
145
144
  animationDelay: '450ms',
146
145
  transition: 'stroke 130ms ease-out,stroke-width 130ms ease-out'
147
146
  },
148
- [`:where(${isSelected}, [data-edge-active='true']) &`]: {
147
+ [`:where(${isSelected}, [data-edge-active='true'], [data-edge-animated='true']) &`]: {
149
148
  animationName: strokeKeyframes,
150
149
  animationDelay: '0ms',
151
150
  transition: 'stroke 130ms ease-out,stroke-width 130ms ease-out'
@@ -171,10 +170,15 @@ export const stepEdgeNumber = style({
171
170
  borderTopLeftRadius: labelBorderRadius,
172
171
  borderBottomLeftRadius: labelBorderRadius,
173
172
  backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, ${mixColor} 5%)`,
174
- fontVariantNumeric: 'tabular-nums'
175
- })
176
- globalStyle(`:where([data-mantine-color-scheme="dark"]) :where([data-likec4-color="gray"]) ${stepEdgeNumber}`, {
177
- backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, ${mixColor} 15%)`
173
+ fontVariantNumeric: 'tabular-nums',
174
+ selectors: {
175
+ [`:where([data-mantine-color-scheme="dark"]) :where([data-likec4-color="gray"]) &`]: {
176
+ backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, ${mixColor} 15%)`
177
+ },
178
+ [`:where([data-edge-active='true']) &`]: {
179
+ backgroundColor: 'transparent'
180
+ }
181
+ }
178
182
  })
179
183
 
180
184
  export const varLabelX = createVar('label-x')
@@ -230,3 +234,67 @@ export const edgeLabelText = style({
230
234
  globalStyle(`${edgeLabel}:has(${stepEdgeNumber}) ${edgeLabelText}`, {
231
235
  padding: '2px 5px 4px 2px'
232
236
  })
237
+
238
+ export const edgeNoteCloseButton = style({
239
+ position: 'absolute',
240
+ top: 4,
241
+ right: 4,
242
+ zIndex: 9
243
+ })
244
+
245
+ export const edgeNoteText = style({
246
+ userSelect: 'all',
247
+ textAlign: 'left',
248
+ whiteSpaceCollapse: 'preserve-breaks',
249
+ textWrap: 'pretty',
250
+ lineHeight: 1.25,
251
+ vars: {
252
+ '--text-fz': mantine.fontSizes.sm
253
+ },
254
+ '@media': {
255
+ [mantine.largerThan('md')]: {
256
+ vars: {
257
+ '--text-fz': mantine.fontSizes.md
258
+ }
259
+ }
260
+ }
261
+ })
262
+
263
+ export const cssNavigateBtn = style({
264
+ zIndex: 'calc(var(--layer-overlays, 1) + 1)',
265
+ position: 'absolute',
266
+ pointerEvents: 'all',
267
+ left: '50%',
268
+ top: 'calc(100% + 1px)',
269
+ color: xyvars.edge.labelColor,
270
+ cursor: 'pointer',
271
+ transformOrigin: 'top center',
272
+ opacity: 0.8,
273
+ transition: 'all 150ms ease-in-out',
274
+ transform: 'translate(-50%, 0)',
275
+ transitionDelay: '0ms',
276
+ backgroundColor: 'var(--ai-bg)',
277
+ vars: {
278
+ '--ai-bg': vars.relation.labelBgColor,
279
+ '--ai-hover': `color-mix(in srgb , ${vars.relation.labelBgColor}, ${mixColor} 10%)`
280
+ },
281
+ selectors: {
282
+ [`:is(${dimmed}) &`]: {
283
+ display: 'none'
284
+ }
285
+ },
286
+ ':hover': {
287
+ boxShadow: mantine.shadows.lg,
288
+ transform: 'translate(-50%, 0) scale(1.3)',
289
+ opacity: 1
290
+ },
291
+ ':active': {
292
+ transform: 'translate(-50%, 0) scale(1.02)'
293
+ }
294
+ })
295
+ globalStyle(`${cssNavigateBtn} .tabler-icon`, {
296
+ width: '75%',
297
+ height: '75%',
298
+ strokeWidth: 2
299
+ })
300
+ export const cssNavigateBtnIcon = style({})
@@ -1,8 +1,7 @@
1
1
  import { rem } from '@mantine/core'
2
2
  import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
3
  import { calc } from '@vanilla-extract/css-utils'
4
- import { mantine } from '../../../mantine.css'
5
- import { vars } from '../../../theme.css'
4
+ import { mantine, vars } from '../../../theme-vars'
6
5
 
7
6
  export const container = style({
8
7
  width: '100%',
@@ -83,15 +83,15 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
83
83
  opacity < 1 && css.transparent,
84
84
  'likec4-compound'
85
85
  )}
86
- style={opacity < 1
87
- ? {
86
+ style={{
87
+ ...(opacity < 1 && {
88
88
  ...assignInlineVars({
89
89
  [css.varBorderTransparency]: `${borderTransparency}%`,
90
90
  [css.varOpacity]: opacity.toFixed(2)
91
91
  }),
92
92
  borderStyle: style.border ?? 'dashed'
93
- }
94
- : undefined}
93
+ })
94
+ }}
95
95
  >
96
96
  <Text
97
97
  component="div"
@@ -1,5 +1,5 @@
1
1
  import { globalStyle, style } from '@vanilla-extract/css'
2
- import { vars } from '../../../theme.css'
2
+ import { vars } from '../../../theme-vars'
3
3
 
4
4
  export const elementLink = style({
5
5
  position: 'absolute',
@@ -15,7 +15,7 @@ import { IconLink } from '@tabler/icons-react'
15
15
  import clsx from 'clsx'
16
16
  import { useId } from 'react'
17
17
  import { clamp } from 'remeda'
18
- import { useDiagramState } from '../../../hooks/useDiagramState'
18
+ import { useMantinePortalProps } from '../../../hooks/useMantinePortalProps'
19
19
  import { type DiagramState } from '../../../state/diagramStore'
20
20
  import type { XYFlowNode } from '../../types'
21
21
  import { elementLink, trigger } from './ElementLink.css'
@@ -24,17 +24,12 @@ type ElementLinkProps = {
24
24
  element: XYFlowNode['data']['element']
25
25
  }
26
26
 
27
- const selector = (s: DiagramState) => {
28
- const target = s.getContainer()
29
- return target ? { target } : null
30
- }
31
-
32
27
  export function ElementLink({
33
28
  element
34
29
  }: ElementLinkProps) {
35
30
  invariant(element.links, 'ElementLink: links are required')
36
31
  const id = useId()
37
- const portalProps = useDiagramState(selector)
32
+ const portalProps = useMantinePortalProps()
38
33
  return (
39
34
  <div className={elementLink} data-likec4-linkid={id}>
40
35
  <HoverCard
@@ -47,7 +42,7 @@ export function ElementLink({
47
42
  transitionProps={{
48
43
  transition: 'pop'
49
44
  }}
50
- {...(portalProps ? { portalProps } : { withinPortal: false })}
45
+ {...portalProps}
51
46
  floatingStrategy={'fixed'}
52
47
  openDelay={350}
53
48
  closeDelay={800}
@@ -1,7 +1,6 @@
1
- import type { DiagramNode } from '@likec4/core/types'
2
- import { ActionIcon, Text as MantineText, Tooltip } from '@mantine/core'
3
- import { IconFileSymlink } from '@tabler/icons-react'
4
- import { Handle, type NodeProps, NodeToolbar, Position } from '@xyflow/react'
1
+ import type { DiagramNode } from '@likec4/core'
2
+ import { Text as MantineText } from '@mantine/core'
3
+ import { Handle, type NodeProps, Position } from '@xyflow/react'
5
4
  import clsx from 'clsx'
6
5
  import { deepEqual as eq } from 'fast-equals'
7
6
  import { m, type Variants } from 'framer-motion'
@@ -123,6 +122,50 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
123
122
 
124
123
  return (
125
124
  <>
125
+ {
126
+ /* <NodeToolbar isVisible={isTollbarVisible} offset={0}>
127
+ <ActionIconGroup>
128
+ <ActionIcon
129
+ size={'md'}
130
+ variant="default"
131
+ color="dark"
132
+ onClick={e => {
133
+ e.stopPropagation()
134
+ store.getState().onOpenSourceElement?.(element.id)
135
+ }}>
136
+ <IconFileSymlink
137
+ // stroke={1.2}
138
+ style={{
139
+ width: '70%',
140
+ height: '70%'
141
+ }} />
142
+ </ActionIcon>
143
+ <ActionIcon
144
+ size={'md'}
145
+ variant="default"
146
+ color="dark"
147
+ >
148
+ <ColorSwatch
149
+ color={defaultTheme.elements[element.color as ThemeColor]?.fill}
150
+ size={16}
151
+ withShadow
152
+ style={{ color: '#fff', cursor: 'pointer' }}
153
+ />
154
+ </ActionIcon>
155
+ <ActionIcon
156
+ size={'md'}
157
+ variant="default"
158
+ color="dark"
159
+ w={'fit-content'}
160
+ fz={'xs'}
161
+ >
162
+ {element.shape}
163
+ </ActionIcon>
164
+
165
+ </ActionIconGroup>
166
+ </NodeToolbar> */
167
+ }
168
+
126
169
  <Handle
127
170
  type="target"
128
171
  position={Position.Top}
@@ -1,7 +1,6 @@
1
1
  import { rem } from '@mantine/core'
2
2
  import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
3
- import { mantine } from '../../../mantine.css'
4
- import { vars } from '../../../theme.css'
3
+ import { mantine, vars } from '../../../theme-vars'
5
4
 
6
5
  export const stokeFillMix = createVar('stroke-fill-mix')
7
6
 
@@ -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,5 +1,5 @@
1
1
  import { hasAtLeast, invariant } from '@likec4/core'
2
- import type { DiagramEdge, Point } from '@likec4/core/types'
2
+ import type { DiagramEdge, Point } from '@likec4/core'
3
3
  import type { Rect, XYPosition } from '@xyflow/react'
4
4
  import { Bezier } from 'bezier-js'
5
5
  import { isArray } from 'remeda'
@@ -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,4 +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;