@likec4/diagram 1.1.0 → 1.2.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 (76) hide show
  1. package/dist/KeepAspectRatioContainer.d.ts +1 -1
  2. package/dist/LikeC4Diagram.d.ts +1 -1
  3. package/dist/LikeC4Diagram.props.d.ts +22 -12
  4. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.mjs +2209 -706
  7. package/dist/mantine/EnsureMantine.d.ts +1 -1
  8. package/dist/state/DiagramContext.d.ts +2143 -19
  9. package/dist/state/WhenInitialized.d.ts +1 -1
  10. package/dist/state/diagramStore.d.ts +2152 -19
  11. package/dist/state/useDiagramStore.d.ts +2142 -18
  12. package/dist/style.css +256 -118
  13. package/dist/theme.css.d.ts +1 -0
  14. package/dist/ui/DiagramTitlePanel.css.d.ts +4 -0
  15. package/dist/ui/DiagramTitlePanel.d.ts +1 -0
  16. package/dist/ui/DynamicViewWalkthrough.css.d.ts +3 -0
  17. package/dist/ui/DynamicViewWalkthrough.d.ts +1 -0
  18. package/dist/ui/OptionsPanel.d.ts +1 -3
  19. package/dist/ui/index.d.ts +1 -0
  20. package/dist/ui/options/NodeOptions.d.ts +1 -2
  21. package/dist/xyflow/FitviewOnDiagramChange.d.ts +4 -2
  22. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +1 -0
  23. package/dist/xyflow/SyncWithDiagram.d.ts +1 -2
  24. package/dist/xyflow/XYFlow.d.ts +4 -7
  25. package/dist/xyflow/XYFlowBackground.d.ts +5 -4
  26. package/dist/xyflow/XYFlowEvents.d.ts +84 -84
  27. package/dist/xyflow/XYFlowInner.d.ts +11 -0
  28. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  29. package/dist/xyflow/edges/RelationshipEdge.d.ts +14 -14
  30. package/dist/xyflow/edges/edges.css.d.ts +5 -2
  31. package/dist/xyflow/edges/utils.d.ts +2 -0
  32. package/dist/xyflow/hooks/useXYFlow.d.ts +126 -126
  33. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
  34. package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -1
  35. package/dist/xyflow/nodes/element/ElementLink.d.ts +1 -1
  36. package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
  37. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +2 -2
  38. package/dist/xyflow/nodes/element/element.css.d.ts +2 -1
  39. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +1 -2
  40. package/dist/xyflow/types.d.ts +18 -18
  41. package/package.json +21 -16
  42. package/src/KeepAspectRatioContainer.tsx +1 -1
  43. package/src/LikeC4Diagram.css.ts +4 -1
  44. package/src/LikeC4Diagram.props.ts +24 -14
  45. package/src/LikeC4Diagram.tsx +18 -9
  46. package/src/StaticLikeC4Diagram.tsx +2 -0
  47. package/src/globals.css.ts +7 -1
  48. package/src/index.ts +0 -3
  49. package/src/state/DiagramContext.tsx +21 -12
  50. package/src/state/diagramStore.ts +275 -98
  51. package/src/theme.css.ts +1 -0
  52. package/src/ui/DiagramTitlePanel.css.ts +33 -0
  53. package/src/ui/DiagramTitlePanel.tsx +138 -0
  54. package/src/ui/DynamicViewWalkthrough.css.ts +46 -0
  55. package/src/ui/DynamicViewWalkthrough.tsx +86 -0
  56. package/src/ui/OptionsPanel.tsx +7 -24
  57. package/src/ui/index.ts +1 -0
  58. package/src/ui/options/NodeOptions.tsx +103 -27
  59. package/src/xyflow/FitviewOnDiagramChange.tsx +89 -69
  60. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +75 -0
  61. package/src/xyflow/SyncWithDiagram.tsx +55 -101
  62. package/src/xyflow/XYFlow.tsx +52 -32
  63. package/src/xyflow/XYFlowBackground.tsx +7 -5
  64. package/src/xyflow/XYFlowEvents.tsx +150 -104
  65. package/src/xyflow/XYFlowInner.tsx +50 -0
  66. package/src/xyflow/diagram-to-xyflow.ts +16 -21
  67. package/src/xyflow/edges/RelationshipEdge.tsx +102 -70
  68. package/src/xyflow/edges/edges.css.ts +128 -53
  69. package/src/xyflow/edges/utils.ts +31 -0
  70. package/src/xyflow/nodes/compound/CompoundNode.css.ts +20 -12
  71. package/src/xyflow/nodes/compound/CompoundNode.tsx +6 -3
  72. package/src/xyflow/nodes/element/ElementLink.tsx +13 -3
  73. package/src/xyflow/nodes/element/ElementNode.tsx +24 -19
  74. package/src/xyflow/nodes/element/element.css.ts +10 -2
  75. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -0
  76. package/src/xyflow/types.ts +7 -8
@@ -3,26 +3,18 @@ import { Box } from '@mantine/core'
3
3
  import { assignInlineVars } from '@vanilla-extract/dynamic'
4
4
  import type { EdgeProps, XYPosition } from '@xyflow/react'
5
5
  import { EdgeLabelRenderer, getBezierPath } from '@xyflow/react'
6
+ import { getNodePositionWithOrigin } from '@xyflow/system'
6
7
  import clsx from 'clsx'
8
+ import { curveCatmullRomOpen, line as d3line } from 'd3-shape'
7
9
  import { deepEqual as eq } from 'fast-equals'
8
10
  import { type CSSProperties, memo } from 'react'
9
- import { hasAtLeast } from 'remeda'
11
+ import { first, hasAtLeast, last, zip } from 'remeda'
10
12
  import { useDiagramState } from '../../state'
11
13
  import { ZIndexes } from '../const'
12
14
  import { useXYStoreApi } from '../hooks'
13
15
  import { type XYFlowEdge } from '../types'
14
- import { toDomPrecision } from '../utils'
15
- import {
16
- container,
17
- cssEdgePath,
18
- edgeLabel,
19
- edgeLabelBody,
20
- edgePathBg,
21
- fillStrokeCtx,
22
- varLabelX,
23
- varLabelY
24
- } from './edges.css'
25
- import { getEdgeParams } from './utils'
16
+ import * as edgesCss from './edges.css'
17
+ import { getEdgeParams, getNodeIntersectionFromCenterToPoint } from './utils'
26
18
  // import { getEdgeParams } from './utils'
27
19
 
28
20
  // function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size = 8): string {
@@ -70,9 +62,9 @@ function bezierPath(bezierSpline: NonEmptyArray<Point>) {
70
62
  return path
71
63
  }
72
64
 
73
- // If points are within 2px, consider them the same
65
+ // If points are within 3px, consider them the same
74
66
  const isSamePoint = (a: number, b: number) => {
75
- return Math.abs(a - b) < 2.1
67
+ return Math.abs(a - b) < 3.1
76
68
  }
77
69
 
78
70
  const isSamePosition = (a: XYPosition, [bx, by]: Point) => {
@@ -83,18 +75,25 @@ const isEqualProps = (prev: EdgeProps<XYFlowEdge>, next: EdgeProps<XYFlowEdge>)
83
75
  prev.id === next.id
84
76
  && prev.source === next.source
85
77
  && prev.target === next.target
78
+ && eq(prev.selected ?? false, next.selected ?? false)
86
79
  && isSamePoint(prev.sourceX, next.sourceX)
87
80
  && isSamePoint(prev.sourceY, next.sourceY)
88
81
  && isSamePoint(prev.targetX, next.targetX)
89
82
  && isSamePoint(prev.targetY, next.targetY)
90
- && eq(prev.data, next.data)
83
+ && eq(prev.data.stepNum, next.data.stepNum)
84
+ && eq(prev.data.edge, next.data.edge)
85
+ && zip(prev.data.controlPoints, next.data.controlPoints).every(([a, b]) =>
86
+ isSamePoint(a[0], b[0]) && isSamePoint(a[1], b[1])
87
+ )
91
88
  )
92
89
 
90
+ const curve = d3line<Point>()
91
+ .curve(curveCatmullRomOpen)
92
+
93
93
  export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(function RelationshipEdgeR({
94
94
  id,
95
95
  data,
96
96
  selected: _selected,
97
- markerEnd: _markerEnd,
98
97
  style,
99
98
  source,
100
99
  target,
@@ -104,19 +103,22 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
104
103
  const sourceNode = nonNullable(nodeLookup.get(source)!, `source node ${source} not found`)
105
104
  const targetNode = nonNullable(nodeLookup.get(target)!, `target node ${target} not found`)
106
105
 
107
- const isNotModified = isSamePosition(sourceNode.internals.positionAbsolute, sourceNode.data.element.position)
108
- && isSamePosition(targetNode.internals.positionAbsolute, targetNode.data.element.position)
106
+ const isModified = !isSamePosition(sourceNode.internals.positionAbsolute, sourceNode.data.element.position)
107
+ || !isSamePosition(targetNode.internals.positionAbsolute, targetNode.data.element.position)
108
+
109
+ const { edge: diagramEdge, controlPoints } = data
109
110
 
110
- invariant(data, 'data is required')
111
- const {
112
- edge
113
- } = data
114
111
  // const edgePath = bezierPath(edge.points)
115
112
 
116
- const color = edge.color ?? 'gray'
117
- const isHovered = useDiagramState(s => s.hoveredEdgeId === id)
113
+ const color = diagramEdge.color ?? 'gray'
114
+ const isStepEdge = data.stepNum !== null
115
+ const { isActive, isHovered, isDimmed } = useDiagramState(s => ({
116
+ isActive: s.activeDynamicViewStep !== null && s.activeDynamicViewStep === data.stepNum,
117
+ isHovered: s.hoveredEdgeId === id,
118
+ isDimmed: s.dimmed.has(id)
119
+ }))
118
120
 
119
- const line = edge.line ?? 'dashed'
121
+ const line = diagramEdge.line ?? 'dashed'
120
122
  const isDotted = line === 'dotted'
121
123
  const isDashed = isDotted || line === 'dashed'
122
124
 
@@ -131,8 +133,14 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
131
133
 
132
134
  let edgePath: string, labelX: number, labelY: number
133
135
 
134
- if (isNotModified) {
135
- edgePath = bezierPath(edge.points)
136
+ if (!isModified) {
137
+ edgePath = bezierPath(diagramEdge.points)
138
+ // if (diagramEdge.tailArrowPoint) {
139
+ // edgePath = `M ${diagramEdge.tailArrowPoint[0]},${diagramEdge.tailArrowPoint[1]} L ${edgePath.substring(1)}`
140
+ // }
141
+ // if (diagramEdge.headArrowPoint) {
142
+ // edgePath += ` L ${diagramEdge.headArrowPoint[0]},${diagramEdge.headArrowPoint[1]}`
143
+ // }
136
144
  labelX = data.label?.bbox.x ?? 0
137
145
  labelY = data.label?.bbox.y ?? 0
138
146
  } else {
@@ -149,29 +157,42 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
149
157
  edgePath = _edgePath
150
158
  labelX = _labelX
151
159
  labelY = _labelY
160
+
161
+ const sourceCenterPos = getNodePositionWithOrigin(sourceNode, [-0.5, -0.5])
162
+ const targetCenterPos = getNodePositionWithOrigin(targetNode, [-0.5, -0.5])
163
+
164
+ edgePath = curve([
165
+ [sourceCenterPos.positionAbsolute.x, sourceCenterPos.positionAbsolute.y],
166
+ getNodeIntersectionFromCenterToPoint(sourceNode, first(controlPoints)!),
167
+ ...controlPoints,
168
+ getNodeIntersectionFromCenterToPoint(targetNode, last(controlPoints)!),
169
+ [targetCenterPos.positionAbsolute.x, targetCenterPos.positionAbsolute.y]
170
+ ])!
152
171
  }
153
- // const deferredEdgePath = edgePath
154
- // useTilg()`
155
- // ${id}
156
- // path=${edgePath}
157
- // isModified=${isModified}
158
- // `
159
172
 
160
173
  return (
161
- <g className={clsx(container)} data-likec4-color={color} data-edge-hovered={isHovered}>
162
- <g className={clsx(fillStrokeCtx)}>
174
+ <g
175
+ className={clsx([
176
+ edgesCss.container,
177
+ isDimmed && edgesCss.dimmed
178
+ ])}
179
+ data-likec4-color={color}
180
+ data-edge-dir={diagramEdge.dir}
181
+ data-edge-active={isActive}
182
+ data-edge-hovered={isHovered}>
183
+ <g className={edgesCss.markerContext}>
163
184
  <defs>
164
185
  <marker
165
186
  id={`arrow-${id}`}
166
- viewBox="0 0 10 8"
167
- refX="0"
168
- refY="4"
187
+ viewBox="0 0 16 10"
188
+ refX={isModified ? '12' : '10'}
189
+ refY="5"
169
190
  markerWidth="5"
170
- markerHeight="5"
191
+ markerHeight="4"
171
192
  markerUnits="strokeWidth"
172
193
  preserveAspectRatio="xMaxYMid meet"
173
194
  orient="auto-start-reverse">
174
- <path d="M 0 0 L 10 4 L 0 8 z" stroke="context-stroke" fill="context-stroke" />
195
+ <path d="M 0 0 L 16 5 L 0 10 z" stroke="context-stroke" fill="context-stroke" />
175
196
  </marker>
176
197
  </defs>
177
198
  </g>
@@ -179,41 +200,52 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
179
200
  edgePath={edgePath}
180
201
  interactionWidth={interactionWidth ?? 10}
181
202
  strokeDasharray={strokeDasharray}
182
- markerStart={edge.tailArrow ? marker : undefined}
183
- markerEnd={edge.headArrow ? marker : undefined}
203
+ markerStart={diagramEdge.tailArrow ? marker : undefined}
204
+ markerEnd={diagramEdge.headArrow ? marker : undefined}
184
205
  style={style} />
185
- {
186
- /*
187
- {controlPoints.map((p, i) => (
206
+ {isModified && controlPoints.map((p, i) => (
188
207
  <circle
189
- className={styles.controlPoint}
208
+ className={edgesCss.controlPoint}
190
209
  key={i}
191
210
  cx={p[0]}
192
211
  cy={p[1]}
193
- r={5}
212
+ r={3}
194
213
  />
195
- ))} */
196
- }
197
- {data.label && (
214
+ ))}
215
+ {(data.label || isStepEdge) && (
198
216
  <EdgeLabelRenderer>
199
217
  <Box
200
- className={clsx(container, edgeLabel)}
218
+ className={clsx([
219
+ edgesCss.container,
220
+ edgesCss.edgeLabel,
221
+ isDimmed && edgesCss.dimmed
222
+ ])}
201
223
  data-likec4-color={color}
202
224
  style={{
203
225
  ...assignInlineVars({
204
- [varLabelX]: toDomPrecision(labelX) + 'px',
205
- [varLabelY]: toDomPrecision(labelY) + 'px'
226
+ [edgesCss.varLabelX]: Math.floor(labelX) + 'px',
227
+ [edgesCss.varLabelY]: Math.floor(labelY) + 'px'
228
+ }),
229
+ ...(data.label && {
230
+ maxWidth: data.label.bbox.width + 12
206
231
  }),
207
- maxWidth: data.label.bbox.width + 10,
208
232
  zIndex: edgeLookup.get(id)?.zIndex ?? ZIndexes.Edge
209
233
  }}
210
234
  mod={{
211
- 'data-edge-hovered': isHovered
235
+ 'data-edge-hovered': isHovered,
236
+ 'data-edge-active': isActive
212
237
  }}
213
238
  >
214
- <Box className={edgeLabelBody}>
215
- {data.label.text}
216
- </Box>
239
+ {isStepEdge && (
240
+ <Box className={edgesCss.stepEdgeNumber}>
241
+ {data.stepNum}
242
+ </Box>
243
+ )}
244
+ {data.label?.text && (
245
+ <Box className={edgesCss.edgeLabelText}>
246
+ {data.label.text}
247
+ </Box>
248
+ )}
217
249
  {
218
250
  /* <Popover
219
251
  position="bottom"
@@ -246,30 +278,30 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
246
278
  )
247
279
  }, isEqualProps)
248
280
 
249
- const RelationshipPath = memo<{
250
- edgePath: string
251
- interactionWidth: number
252
- strokeDasharray: string | undefined
253
- markerStart: string | undefined
254
- markerEnd: string | undefined
255
- style: CSSProperties | undefined
256
- }>(({
281
+ const RelationshipPath = ({
257
282
  edgePath,
258
283
  interactionWidth,
259
284
  strokeDasharray,
260
285
  markerStart,
261
286
  markerEnd,
262
287
  style
288
+ }: {
289
+ edgePath: string
290
+ interactionWidth: number
291
+ strokeDasharray: string | undefined
292
+ markerStart: string | undefined
293
+ markerEnd: string | undefined
294
+ style: CSSProperties | undefined
263
295
  }) => (
264
296
  <>
265
297
  <path
266
- className={clsx('react-flow__edge-path', edgePathBg)}
298
+ className={clsx('react-flow__edge-path', edgesCss.edgePathBg)}
267
299
  d={edgePath}
268
300
  style={style}
269
301
  strokeLinecap={'round'}
270
302
  />
271
303
  <path
272
- className={clsx('react-flow__edge-path', cssEdgePath)}
304
+ className={clsx('react-flow__edge-path', edgesCss.cssEdgePath)}
273
305
  d={edgePath}
274
306
  style={style}
275
307
  strokeLinecap={'round'}
@@ -285,4 +317,4 @@ const RelationshipPath = memo<{
285
317
  strokeWidth={interactionWidth}
286
318
  />
287
319
  </>
288
- ))
320
+ )
@@ -1,78 +1,136 @@
1
1
  import { rem } from '@mantine/core'
2
- import { createVar, globalStyle, style } from '@vanilla-extract/css'
3
- import { mantine } from '../../mantine.css'
2
+ import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
4
3
  import { vars, xyvars } from '../../theme.css'
5
4
 
5
+ const mixColor = createVar('mix-color')
6
+
6
7
  export const container = style({
7
8
  vars: {
9
+ [mixColor]: `black`,
8
10
  [xyvars.edge.stroke]: vars.relation.lineColor,
9
- [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, white 30%)`,
11
+ [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
10
12
  [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 20%)`,
11
13
  [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 35%)`,
12
- [xyvars.edge.strokeWidth]: '2.5px'
13
- },
14
- selectors: {
15
- [mantine.darkSelector]: {
16
- vars: {
17
- [xyvars.edge.labelColor]: vars.relation.labelColor,
18
- [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 55%)`
19
- }
20
- }
21
- // [mantine.lightSelector]: {
22
- // // mixBlendMode: 'luminosity',
23
- // vars: {
24
- // // [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, #000 85%)`
25
- // }
26
- // }
14
+ [xyvars.edge.strokeWidth]: '2.2px'
15
+ }
16
+ })
17
+
18
+ export const dimmed = style({})
19
+
20
+ globalStyle(`svg:has(${dimmed})`, {
21
+ filter: vars.filterDimmed,
22
+ transition: 'filter 800ms ease-out'
23
+ })
24
+
25
+ globalStyle(`:where([data-mantine-color-scheme="dark"]) ${container}`, {
26
+ vars: {
27
+ [mixColor]: `white`,
28
+ [xyvars.edge.labelColor]: vars.relation.labelColor,
29
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 55%)`
30
+ }
31
+ })
32
+
33
+ const isSelected = '.react-flow__edge.selected'
34
+
35
+ globalStyle(`:where(${isSelected}) ${container}`, {
36
+ vars: {
37
+ [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
38
+ [xyvars.edge.strokeWidth]: '3px'
27
39
  }
28
40
  })
29
41
 
30
- globalStyle(`${container}[data-edge-hovered='true']`, {
42
+ globalStyle(`${container}:is([data-edge-hovered='true'],[data-edge-active='true'])`, {
31
43
  vars: {
32
- [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, white 30%)`,
44
+ // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
45
+ [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
33
46
  [xyvars.edge.strokeWidth]: '3px'
34
47
  }
35
48
  })
49
+
50
+ globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
51
+ vars: {
52
+ [xyvars.edge.strokeWidth]: '3.6px'
53
+ }
54
+ })
36
55
  // globalStyle(`${container}[data-edge-hovered='true']`, {
37
56
  // vars: {
38
57
  // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, white 35%)`
39
58
  // },
40
59
  // })
41
60
 
42
- globalStyle(`.react-flow__edge.selected ${container}`, {
43
- vars: {
44
- [xyvars.edge.strokeWidth]: '2.5'
45
- }
46
- })
47
-
48
61
  globalStyle(`.react-flow__edges > svg`, {
49
62
  mixBlendMode: 'screen'
50
63
  })
51
- globalStyle(`:where([data-mantine-color-scheme="dark"]) .react-flow__edges > svg`, {
52
- mixBlendMode: 'lighten'
53
- })
64
+ // globalStyle(`:where([data-mantine-color-scheme="dark"]) .react-flow__edges > svg`, {
65
+ // mixBlendMode: 'lighten'
66
+ // })
54
67
 
55
68
  export const edgePathBg = style({
56
- strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8)`,
57
- strokeOpacity: 0.1
69
+ strokeWidth: xyvars.edge.strokeWidth,
70
+ strokeOpacity: 0.08,
71
+ // transition: 'stroke-width 175ms ease-in-out',
72
+ // transition: 'stroke-width 175ms ease-in-out, stroke-opacity 150ms ease-out',
73
+ transitionProperty: 'stroke-width, stroke-opacity',
74
+ transitionDuration: '155ms',
75
+ transitionTimingFunction: 'ease-out',
76
+ selectors: {
77
+ [`:where(${isSelected}, [data-edge-active='true'], [data-edge-hovered='true']) &`]: {
78
+ strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8px)`,
79
+ strokeOpacity: 0.15
80
+ }
81
+ }
58
82
  })
59
83
 
60
- export const fillStrokeCtx = style({
84
+ // To fix issue with marker not inheriting color from path - we need to create container
85
+ export const markerContext = style({
61
86
  fill: xyvars.edge.stroke,
62
87
  stroke: xyvars.edge.stroke,
88
+ pointerEvents: 'none'
89
+ })
90
+
91
+ export const controlPoint = style({
92
+ fill: xyvars.edge.stroke,
93
+ stroke: xyvars.edge.stroke,
94
+ fillOpacity: 0.75,
95
+ strokeWidth: 1,
96
+ cursor: 'grab',
97
+ pointerEvents: 'visible',
63
98
  selectors: {
64
- '.react-flow__edge.selected &': {
65
- vars: {
66
- [xyvars.edge.stroke]: xyvars.edge.strokeSelected
67
- }
99
+ [`:where(${isSelected}, [data-edge-active='true'], [data-edge-hovered='true']) &`]: {
100
+ transition: 'all 150ms ease-out',
101
+ fillOpacity: 1,
102
+ strokeWidth: 3
68
103
  }
69
104
  }
70
105
  })
71
106
 
107
+ const strokeKeyframes = generateIdentifier('strokeKeyframes')
108
+ globalKeyframes(strokeKeyframes, {
109
+ 'from': {
110
+ strokeDashoffset: 18 * 2
111
+ },
112
+ 'to': {
113
+ strokeDashoffset: 1
114
+ }
115
+ })
116
+
72
117
  export const cssEdgePath = style({
118
+ animationDuration: '800ms',
119
+ animationIterationCount: 'infinite',
120
+ animationTimingFunction: 'linear',
73
121
  selectors: {
74
- [`${container}[data-edge-hovered='true'] &`]: {
122
+ [`:where([data-edge-hovered='true']) &`]: {
123
+ animationName: strokeKeyframes,
124
+ animationDelay: '350ms',
125
+ transition: 'all 130ms ease-out'
126
+ },
127
+ [`:where(${isSelected}, [data-edge-active='true']) &`]: {
128
+ animationName: strokeKeyframes,
129
+ animationDelay: '0ms',
75
130
  transition: 'all 130ms ease-out'
131
+ },
132
+ [`:where([data-edge-dir='back']) &`]: {
133
+ animationDirection: 'reverse'
76
134
  }
77
135
  }
78
136
  })
@@ -90,35 +148,52 @@ export const edgeLabel = style({
90
148
  pointerEvents: 'all',
91
149
  cursor: 'pointer',
92
150
  transformOrigin: '50% 50%',
93
- textAlign: 'left',
94
151
  mixBlendMode: 'screen',
95
- padding: '2px 6px 4px 6px',
96
- width: 'max-content',
152
+ color: xyvars.edge.labelColor,
97
153
  backgroundColor: xyvars.edge.labelBgColor,
98
154
  borderRadius: '3px',
99
155
  transform: varTranslate,
100
- willChange: 'transform',
101
156
  vars: {
102
- // [varTranslate]: `translate(calc(${varLabelX} - 50%), calc(${varLabelY} - 50%))`,
103
- [varTranslate]: `translate(${varLabelX}, ${varLabelY})`,
104
- [varLabelX]: '50%',
105
- [varLabelY]: '50%'
157
+ [varTranslate]: `translate(${fallbackVar(varLabelX, '50%')}, ${fallbackVar(varLabelY, '50%')})`
106
158
  },
107
159
  selectors: {
108
- // [mantine.darkSelector]: {
109
- // mixBlendMode: 'luminosity'
110
- // },
111
160
  '&[data-edge-hovered="true"]': {
112
161
  transition: 'all 140ms ease-out',
113
162
  transform: `${varTranslate} scale(1.1)`
163
+ },
164
+ [`&:is(${dimmed})`]: {
165
+ transition: 'filter 800ms ease-out',
166
+ filter: vars.filterDimmed
114
167
  }
115
168
  }
116
169
  })
117
170
 
118
- export const edgeLabelBody = style({
119
- // display: 'inline',
120
- color: xyvars.edge.labelColor,
171
+ export const stepEdgeNumber = style({
172
+ position: 'absolute',
173
+ top: -2,
174
+ left: -2,
175
+ fontWeight: 500,
176
+ fontSize: rem(16),
177
+ lineHeight: 0.9,
178
+ padding: '4px 6px',
179
+ borderRadius: 99999,
180
+ textAlign: 'center',
181
+ minWidth: 22,
182
+ backgroundColor: xyvars.edge.labelBgColor,
183
+ transition: 'all 120ms ease-out',
184
+ transform: 'translateX(-100%)',
185
+ fontVariantNumeric: 'tabular-nums',
186
+ selectors: {
187
+ ':where([data-edge-hovered="true"],[data-edge-active="true"]) &': {
188
+ fontWeight: 600,
189
+ backgroundColor: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 5%)`
190
+ }
191
+ }
192
+ })
193
+
194
+ export const edgeLabelText = style({
195
+ padding: '2px 6px 4px 6px',
196
+ textAlign: 'left',
121
197
  fontSize: rem(12.5),
122
- lineHeight: 1.12,
123
- transition: 'color 120ms ease-out'
198
+ lineHeight: 1.2
124
199
  })
@@ -1,7 +1,38 @@
1
+ import type { Point } from '@likec4/core'
1
2
  import { Position, type XYPosition } from '@xyflow/react'
2
3
  import { getNodeDimensions } from '@xyflow/system'
3
4
  import type { InternalXYFlowNode } from '../types'
4
5
 
6
+ // this helper function returns the intersection point
7
+ // of the line between the center of the intersectionNode and the target node
8
+ export function getNodeIntersectionFromCenterToPoint(intersectionNode: InternalXYFlowNode, [x1, y1]: Point) {
9
+ // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
10
+ const {
11
+ width: intersectionNodeWidth,
12
+ height: intersectionNodeHeight
13
+ } = getNodeDimensions(intersectionNode)
14
+
15
+ const intersectionNodePosition = intersectionNode.internals.positionAbsolute
16
+
17
+ const w = intersectionNodeWidth / 2
18
+ const h = intersectionNodeHeight / 2
19
+
20
+ const x2 = intersectionNodePosition.x + w
21
+ const y2 = intersectionNodePosition.y + h
22
+ // const x1 = targetPoint.x
23
+ // const y1 = targetPoint.y
24
+
25
+ const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h)
26
+ const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h)
27
+ const a = 1 / (Math.abs(xx1) + Math.abs(yy1))
28
+ const xx3 = a * xx1
29
+ const yy3 = a * yy1
30
+ const x = w * (xx3 + yy3) + x2
31
+ const y = h * (-xx3 + yy3) + y2
32
+
33
+ return [x, y] as const
34
+ }
35
+
5
36
  // this helper function returns the intersection point
6
37
  // of the line between the center of the intersectionNode and the target node
7
38
  function getNodeIntersection(intersectionNode: InternalXYFlowNode, targetNode: InternalXYFlowNode): XYPosition {
@@ -11,6 +11,11 @@ export const container = style({
11
11
  margin: 0
12
12
  })
13
13
 
14
+ export const dimmed = style({
15
+ filter: vars.filterDimmed,
16
+ transition: 'filter 800ms ease-out'
17
+ })
18
+
14
19
  export const varTransparency = createVar('transparency')
15
20
 
16
21
  const outlineColor = fallbackVar(
@@ -23,7 +28,7 @@ export const compoundBody = style({
23
28
  width: '100%',
24
29
  height: '100%',
25
30
  position: 'relative',
26
- borderRadius: rem(6),
31
+ borderRadius: 6,
27
32
  boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
28
33
  padding: 8,
29
34
  background: vars.element.fill,
@@ -43,7 +48,6 @@ export const compoundBody = style({
43
48
  const bgTransparency = createVar('bgTransparency')
44
49
  const bgTransparencyDelta = createVar('bgTransparencyDelta')
45
50
  export const transparent = style({
46
- borderRadius: 8,
47
51
  borderStyle: 'dashed',
48
52
  borderWidth: 3,
49
53
  padding: 6,
@@ -58,19 +62,19 @@ export const transparent = style({
58
62
  '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`
59
63
  },
60
64
  selectors: {
61
- [`:where(${container}[data-hovered]) &`]: {
62
- transitionDelay: '175ms',
65
+ [`:where(.react-flow__node.selected) &`]: {
66
+ transitionDelay: '50ms',
67
+ // borderColor: 'transparent',
63
68
  // transitionTimingFunction: 'ease-out',
64
69
  vars: {
65
- [bgTransparencyDelta]: '15%'
70
+ [bgTransparencyDelta]: '5%'
66
71
  }
67
72
  },
68
- [`:where(.react-flow__node.selected) &`]: {
69
- transitionDelay: '50ms',
70
- borderColor: 'transparent',
73
+ [`:where([data-hovered]) &`]: {
74
+ transitionDelay: '175ms',
71
75
  // transitionTimingFunction: 'ease-out',
72
76
  vars: {
73
- [bgTransparencyDelta]: '25%'
77
+ [bgTransparencyDelta]: '15%'
74
78
  }
75
79
  }
76
80
  }
@@ -93,10 +97,10 @@ export const title = style({
93
97
  selectors: {
94
98
  [`:where([data-likec4-navigable='true']) &`]: {
95
99
  paddingLeft: 26
96
- },
97
- [`:where([data-mantine-color-scheme='light']) &`]: {
98
- opacity: 1
99
100
  }
101
+ // [`:where([data-mantine-color-scheme='light']) &`]: {
102
+ // opacity: 1
103
+ // }
100
104
  }
101
105
  })
102
106
 
@@ -106,6 +110,10 @@ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transpa
106
110
  }
107
111
  })
108
112
 
113
+ globalStyle(`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) ${title}`, {
114
+ opacity: 1
115
+ })
116
+
109
117
  const indicatorKeyframes = generateIdentifier('indicator')
110
118
  globalKeyframes(indicatorKeyframes, {
111
119
  'from': {
@@ -37,15 +37,17 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
37
37
  // const h = toDomPrecision(height ?? compound.height)
38
38
  const opacity = style.opacity ?? 100
39
39
 
40
- const { isHovered, hasOnNavigateTo } = useDiagramState(s => ({
40
+ const { isHovered, isDimmed, hasOnNavigateTo } = useDiagramState(s => ({
41
41
  isHovered: s.hoveredNodeId === id,
42
+ isDimmed: s.dimmed.has(id),
42
43
  hasOnNavigateTo: !!s.onNavigateTo
43
44
  }))
44
45
  const isnavigable = !!compound.navigateTo && hasOnNavigateTo
45
46
 
47
+ // invert opacity to transparency
46
48
  const transparency = clamp(100 - opacity, {
47
49
  min: 0,
48
- max: 98
50
+ max: 99
49
51
  })
50
52
 
51
53
  return (
@@ -53,7 +55,8 @@ export const CompoundNodeMemo = /* @__PURE__ */ memo<CompoundNodeProps>(function
53
55
  className={clsx(
54
56
  css.container,
55
57
  'likec4-compound-node',
56
- opacity < 100 && 'likec4-compound-transparent'
58
+ opacity < 100 && 'likec4-compound-transparent',
59
+ isDimmed && css.dimmed
57
60
  )}
58
61
  mod={{
59
62
  'compound-depth': depth,