@likec4/diagram 1.1.1 → 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 (74) 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 +7 -2
  4. package/dist/StaticLikeC4Diagram.d.ts +1 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.mjs +2517 -1326
  7. package/dist/mantine/EnsureMantine.d.ts +1 -1
  8. package/dist/state/DiagramContext.d.ts +2125 -10
  9. package/dist/state/WhenInitialized.d.ts +1 -1
  10. package/dist/state/diagramStore.d.ts +2133 -10
  11. package/dist/state/useDiagramStore.d.ts +2124 -9
  12. package/dist/style.css +210 -136
  13. package/dist/theme.css.d.ts +1 -0
  14. package/dist/ui/DiagramTitlePanel.d.ts +1 -2
  15. package/dist/ui/DynamicViewWalkthrough.css.d.ts +3 -0
  16. package/dist/ui/DynamicViewWalkthrough.d.ts +1 -0
  17. package/dist/ui/OptionsPanel.d.ts +1 -2
  18. package/dist/ui/index.d.ts +1 -0
  19. package/dist/ui/options/NodeOptions.d.ts +1 -2
  20. package/dist/xyflow/FitviewOnDiagramChange.d.ts +4 -2
  21. package/dist/xyflow/SyncWithDiagram.d.ts +1 -2
  22. package/dist/xyflow/XYFlow.d.ts +4 -7
  23. package/dist/xyflow/XYFlowBackground.d.ts +5 -4
  24. package/dist/xyflow/XYFlowEvents.d.ts +57 -63
  25. package/dist/xyflow/XYFlowInner.d.ts +11 -0
  26. package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
  27. package/dist/xyflow/edges/RelationshipEdge.d.ts +14 -14
  28. package/dist/xyflow/edges/edges.css.d.ts +4 -2
  29. package/dist/xyflow/edges/utils.d.ts +2 -0
  30. package/dist/xyflow/hooks/useXYFlow.d.ts +126 -126
  31. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
  32. package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -1
  33. package/dist/xyflow/nodes/element/ElementLink.d.ts +1 -1
  34. package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
  35. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +2 -2
  36. package/dist/xyflow/nodes/element/element.css.d.ts +2 -1
  37. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +1 -2
  38. package/dist/xyflow/types.d.ts +18 -18
  39. package/package.json +19 -16
  40. package/src/KeepAspectRatioContainer.tsx +1 -1
  41. package/src/LikeC4Diagram.css.ts +2 -0
  42. package/src/LikeC4Diagram.props.ts +9 -4
  43. package/src/LikeC4Diagram.tsx +17 -11
  44. package/src/StaticLikeC4Diagram.tsx +1 -0
  45. package/src/globals.css.ts +1 -0
  46. package/src/index.ts +0 -3
  47. package/src/state/DiagramContext.tsx +21 -12
  48. package/src/state/diagramStore.ts +177 -38
  49. package/src/theme.css.ts +1 -0
  50. package/src/ui/DiagramTitlePanel.css.ts +2 -1
  51. package/src/ui/DiagramTitlePanel.tsx +3 -4
  52. package/src/ui/DynamicViewWalkthrough.css.ts +46 -0
  53. package/src/ui/DynamicViewWalkthrough.tsx +86 -0
  54. package/src/ui/OptionsPanel.tsx +5 -3
  55. package/src/ui/index.ts +1 -0
  56. package/src/ui/options/NodeOptions.tsx +23 -24
  57. package/src/xyflow/FitviewOnDiagramChange.tsx +89 -69
  58. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +57 -39
  59. package/src/xyflow/SyncWithDiagram.tsx +55 -107
  60. package/src/xyflow/XYFlow.tsx +51 -35
  61. package/src/xyflow/XYFlowBackground.tsx +7 -5
  62. package/src/xyflow/XYFlowEvents.tsx +97 -92
  63. package/src/xyflow/XYFlowInner.tsx +50 -0
  64. package/src/xyflow/diagram-to-xyflow.ts +16 -21
  65. package/src/xyflow/edges/RelationshipEdge.tsx +70 -34
  66. package/src/xyflow/edges/edges.css.ts +68 -34
  67. package/src/xyflow/edges/utils.ts +31 -0
  68. package/src/xyflow/nodes/compound/CompoundNode.css.ts +6 -2
  69. package/src/xyflow/nodes/compound/CompoundNode.tsx +6 -3
  70. package/src/xyflow/nodes/element/ElementLink.tsx +13 -3
  71. package/src/xyflow/nodes/element/ElementNode.tsx +24 -19
  72. package/src/xyflow/nodes/element/element.css.ts +9 -2
  73. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -0
  74. package/src/xyflow/types.ts +7 -8
@@ -8,14 +8,13 @@ import clsx from 'clsx'
8
8
  import { curveCatmullRomOpen, line as d3line } from 'd3-shape'
9
9
  import { deepEqual as eq } from 'fast-equals'
10
10
  import { type CSSProperties, memo } from 'react'
11
- import { hasAtLeast } from 'remeda'
11
+ import { first, hasAtLeast, last, zip } from 'remeda'
12
12
  import { useDiagramState } from '../../state'
13
13
  import { ZIndexes } from '../const'
14
14
  import { useXYStoreApi } from '../hooks'
15
15
  import { type XYFlowEdge } from '../types'
16
- import { toDomPrecision } from '../utils'
17
16
  import * as edgesCss from './edges.css'
18
- import { getEdgeParams } from './utils'
17
+ import { getEdgeParams, getNodeIntersectionFromCenterToPoint } from './utils'
19
18
  // import { getEdgeParams } from './utils'
20
19
 
21
20
  // function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size = 8): string {
@@ -63,9 +62,9 @@ function bezierPath(bezierSpline: NonEmptyArray<Point>) {
63
62
  return path
64
63
  }
65
64
 
66
- // If points are within 2px, consider them the same
65
+ // If points are within 3px, consider them the same
67
66
  const isSamePoint = (a: number, b: number) => {
68
- return Math.abs(a - b) < 2.1
67
+ return Math.abs(a - b) < 3.1
69
68
  }
70
69
 
71
70
  const isSamePosition = (a: XYPosition, [bx, by]: Point) => {
@@ -81,10 +80,15 @@ const isEqualProps = (prev: EdgeProps<XYFlowEdge>, next: EdgeProps<XYFlowEdge>)
81
80
  && isSamePoint(prev.sourceY, next.sourceY)
82
81
  && isSamePoint(prev.targetX, next.targetX)
83
82
  && isSamePoint(prev.targetY, next.targetY)
84
- && 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
+ )
85
88
  )
86
89
 
87
- const curve = d3line<Point>().curve(curveCatmullRomOpen.alpha(1))
90
+ const curve = d3line<Point>()
91
+ .curve(curveCatmullRomOpen)
88
92
 
89
93
  export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(function RelationshipEdgeR({
90
94
  id,
@@ -99,14 +103,20 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
99
103
  const sourceNode = nonNullable(nodeLookup.get(source)!, `source node ${source} not found`)
100
104
  const targetNode = nonNullable(nodeLookup.get(target)!, `target node ${target} not found`)
101
105
 
102
- const isNotModified = isSamePosition(sourceNode.internals.positionAbsolute, sourceNode.data.element.position)
103
- && 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)
104
108
 
105
109
  const { edge: diagramEdge, controlPoints } = data
110
+
106
111
  // const edgePath = bezierPath(edge.points)
107
112
 
108
113
  const color = diagramEdge.color ?? 'gray'
109
- const isHovered = useDiagramState(s => s.hoveredEdgeId === id)
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
+ }))
110
120
 
111
121
  const line = diagramEdge.line ?? 'dashed'
112
122
  const isDotted = line === 'dotted'
@@ -123,8 +133,14 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
123
133
 
124
134
  let edgePath: string, labelX: number, labelY: number
125
135
 
126
- if (isNotModified) {
136
+ if (!isModified) {
127
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
+ // }
128
144
  labelX = data.label?.bbox.x ?? 0
129
145
  labelY = data.label?.bbox.y ?? 0
130
146
  } else {
@@ -147,28 +163,36 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
147
163
 
148
164
  edgePath = curve([
149
165
  [sourceCenterPos.positionAbsolute.x, sourceCenterPos.positionAbsolute.y],
150
- [sx, sy],
166
+ getNodeIntersectionFromCenterToPoint(sourceNode, first(controlPoints)!),
151
167
  ...controlPoints,
152
- [tx, ty],
168
+ getNodeIntersectionFromCenterToPoint(targetNode, last(controlPoints)!),
153
169
  [targetCenterPos.positionAbsolute.x, targetCenterPos.positionAbsolute.y]
154
170
  ])!
155
171
  }
156
172
 
157
173
  return (
158
- <g className={clsx(edgesCss.container)} data-likec4-color={color} data-edge-hovered={isHovered}>
159
- <g className={clsx(edgesCss.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}>
160
184
  <defs>
161
185
  <marker
162
186
  id={`arrow-${id}`}
163
- viewBox="0 0 10 8"
164
- refX="0"
165
- refY="4"
187
+ viewBox="0 0 16 10"
188
+ refX={isModified ? '12' : '10'}
189
+ refY="5"
166
190
  markerWidth="5"
167
- markerHeight="5"
191
+ markerHeight="4"
168
192
  markerUnits="strokeWidth"
169
193
  preserveAspectRatio="xMaxYMid meet"
170
194
  orient="auto-start-reverse">
171
- <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" />
172
196
  </marker>
173
197
  </defs>
174
198
  </g>
@@ -179,37 +203,49 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
179
203
  markerStart={diagramEdge.tailArrow ? marker : undefined}
180
204
  markerEnd={diagramEdge.headArrow ? marker : undefined}
181
205
  style={style} />
182
- {
183
- /* controlPoints.map((p, i) => (
206
+ {isModified && controlPoints.map((p, i) => (
184
207
  <circle
185
208
  className={edgesCss.controlPoint}
186
209
  key={i}
187
210
  cx={p[0]}
188
211
  cy={p[1]}
189
- r={5}
212
+ r={3}
190
213
  />
191
- )) */
192
- }
193
- {data.label && (
214
+ ))}
215
+ {(data.label || isStepEdge) && (
194
216
  <EdgeLabelRenderer>
195
217
  <Box
196
- className={clsx(edgesCss.container, edgesCss.edgeLabel)}
218
+ className={clsx([
219
+ edgesCss.container,
220
+ edgesCss.edgeLabel,
221
+ isDimmed && edgesCss.dimmed
222
+ ])}
197
223
  data-likec4-color={color}
198
224
  style={{
199
225
  ...assignInlineVars({
200
- [edgesCss.varLabelX]: toDomPrecision(labelX) + 'px',
201
- [edgesCss.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
202
231
  }),
203
- maxWidth: data.label.bbox.width + 10,
204
232
  zIndex: edgeLookup.get(id)?.zIndex ?? ZIndexes.Edge
205
233
  }}
206
234
  mod={{
207
- 'data-edge-hovered': isHovered
235
+ 'data-edge-hovered': isHovered,
236
+ 'data-edge-active': isActive
208
237
  }}
209
238
  >
210
- <Box className={edgesCss.edgeLabelBody}>
211
- {data.label.text}
212
- </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
+ )}
213
249
  {
214
250
  /* <Popover
215
251
  position="bottom"
@@ -1,5 +1,5 @@
1
1
  import { rem } from '@mantine/core'
2
- import { createVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
2
+ import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
3
3
  import { vars, xyvars } from '../../theme.css'
4
4
 
5
5
  const mixColor = createVar('mix-color')
@@ -15,6 +15,13 @@ export const container = style({
15
15
  }
16
16
  })
17
17
 
18
+ export const dimmed = style({})
19
+
20
+ globalStyle(`svg:has(${dimmed})`, {
21
+ filter: vars.filterDimmed,
22
+ transition: 'filter 800ms ease-out'
23
+ })
24
+
18
25
  globalStyle(`:where([data-mantine-color-scheme="dark"]) ${container}`, {
19
26
  vars: {
20
27
  [mixColor]: `white`,
@@ -32,7 +39,7 @@ globalStyle(`:where(${isSelected}) ${container}`, {
32
39
  }
33
40
  })
34
41
 
35
- globalStyle(`${container}[data-edge-hovered='true']`, {
42
+ globalStyle(`${container}:is([data-edge-hovered='true'],[data-edge-active='true'])`, {
36
43
  vars: {
37
44
  // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
38
45
  [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
@@ -54,39 +61,46 @@ globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
54
61
  globalStyle(`.react-flow__edges > svg`, {
55
62
  mixBlendMode: 'screen'
56
63
  })
57
- globalStyle(`:where([data-mantine-color-scheme="dark"]) .react-flow__edges > svg`, {
58
- mixBlendMode: 'lighten'
59
- })
64
+ // globalStyle(`:where([data-mantine-color-scheme="dark"]) .react-flow__edges > svg`, {
65
+ // mixBlendMode: 'lighten'
66
+ // })
60
67
 
61
68
  export const edgePathBg = style({
62
69
  strokeWidth: xyvars.edge.strokeWidth,
63
- strokeOpacity: 0.05,
70
+ strokeOpacity: 0.08,
64
71
  // transition: 'stroke-width 175ms ease-in-out',
65
72
  // transition: 'stroke-width 175ms ease-in-out, stroke-opacity 150ms ease-out',
66
73
  transitionProperty: 'stroke-width, stroke-opacity',
67
74
  transitionDuration: '155ms',
68
75
  transitionTimingFunction: 'ease-out',
69
76
  selectors: {
70
- [`:where(${isSelected}, [data-edge-hovered='true']) &`]: {
77
+ [`:where(${isSelected}, [data-edge-active='true'], [data-edge-hovered='true']) &`]: {
71
78
  strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8px)`,
72
- strokeOpacity: 0.11
79
+ strokeOpacity: 0.15
73
80
  }
74
81
  }
75
82
  })
76
83
 
77
- 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({
78
86
  fill: xyvars.edge.stroke,
79
- stroke: xyvars.edge.stroke
87
+ stroke: xyvars.edge.stroke,
88
+ pointerEvents: 'none'
80
89
  })
81
90
 
82
91
  export const controlPoint = style({
83
92
  fill: xyvars.edge.stroke,
84
- stroke: xyvars.edge.stroke
85
- })
86
-
87
- globalStyle(`:where(${isSelected}) ${fillStrokeCtx}`, {
88
- vars: {
89
- [xyvars.edge.stroke]: xyvars.edge.strokeSelected
93
+ stroke: xyvars.edge.stroke,
94
+ fillOpacity: 0.75,
95
+ strokeWidth: 1,
96
+ cursor: 'grab',
97
+ pointerEvents: 'visible',
98
+ selectors: {
99
+ [`:where(${isSelected}, [data-edge-active='true'], [data-edge-hovered='true']) &`]: {
100
+ transition: 'all 150ms ease-out',
101
+ fillOpacity: 1,
102
+ strokeWidth: 3
103
+ }
90
104
  }
91
105
  })
92
106
 
@@ -107,13 +121,16 @@ export const cssEdgePath = style({
107
121
  selectors: {
108
122
  [`:where([data-edge-hovered='true']) &`]: {
109
123
  animationName: strokeKeyframes,
110
- animationDelay: '500ms',
124
+ animationDelay: '350ms',
111
125
  transition: 'all 130ms ease-out'
112
126
  },
113
- [`:where(${isSelected}) &`]: {
127
+ [`:where(${isSelected}, [data-edge-active='true']) &`]: {
114
128
  animationName: strokeKeyframes,
115
129
  animationDelay: '0ms',
116
130
  transition: 'all 130ms ease-out'
131
+ },
132
+ [`:where([data-edge-dir='back']) &`]: {
133
+ animationDirection: 'reverse'
117
134
  }
118
135
  }
119
136
  })
@@ -131,35 +148,52 @@ export const edgeLabel = style({
131
148
  pointerEvents: 'all',
132
149
  cursor: 'pointer',
133
150
  transformOrigin: '50% 50%',
134
- textAlign: 'left',
135
151
  mixBlendMode: 'screen',
136
- padding: '2px 6px 4px 6px',
137
- width: 'max-content',
152
+ color: xyvars.edge.labelColor,
138
153
  backgroundColor: xyvars.edge.labelBgColor,
139
154
  borderRadius: '3px',
140
155
  transform: varTranslate,
141
- backfaceVisibility: 'hidden',
142
156
  vars: {
143
- // [varTranslate]: `translate(calc(${varLabelX} - 50%), calc(${varLabelY} - 50%))`,
144
- [varTranslate]: `translate(${varLabelX}, ${varLabelY})`,
145
- [varLabelX]: '50%',
146
- [varLabelY]: '50%'
157
+ [varTranslate]: `translate(${fallbackVar(varLabelX, '50%')}, ${fallbackVar(varLabelY, '50%')})`
147
158
  },
148
159
  selectors: {
149
- // [mantine.darkSelector]: {
150
- // mixBlendMode: 'luminosity'
151
- // },
152
160
  '&[data-edge-hovered="true"]': {
153
161
  transition: 'all 140ms ease-out',
154
162
  transform: `${varTranslate} scale(1.1)`
163
+ },
164
+ [`&:is(${dimmed})`]: {
165
+ transition: 'filter 800ms ease-out',
166
+ filter: vars.filterDimmed
155
167
  }
156
168
  }
157
169
  })
158
170
 
159
- export const edgeLabelBody = style({
160
- // display: 'inline',
161
- 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',
162
197
  fontSize: rem(12.5),
163
- lineHeight: 1.12,
164
- transition: 'color 120ms ease-out'
198
+ lineHeight: 1.2
165
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(
@@ -71,8 +76,7 @@ export const transparent = style({
71
76
  vars: {
72
77
  [bgTransparencyDelta]: '15%'
73
78
  }
74
- },
75
-
79
+ }
76
80
  }
77
81
  })
78
82
 
@@ -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,
@@ -68,7 +68,10 @@ export function ElementLink({
68
68
  <span>links</span>
69
69
  </UnstyledButton>
70
70
  </HoverCardTarget>
71
- <HoverCardDropdown onClick={e => e.stopPropagation()} p={'xs'}>
71
+ <HoverCardDropdown
72
+ onPointerDownCapture={e => e.stopPropagation()}
73
+ onClick={e => e.stopPropagation()}
74
+ p={'xs'}>
72
75
  <Stack gap={'xs'}>
73
76
  {element.links.map((link) => (
74
77
  <Group key={link} wrap="nowrap" gap={'sm'}>
@@ -79,8 +82,15 @@ export function ElementLink({
79
82
  </Box>
80
83
  <CopyButton value={link}>
81
84
  {({ copied, copy }) => (
82
- <Button size="compact-xs" fz={'12'} onClick={copy} color={copied ? 'teal' : 'gray'}>
83
- {copied ? 'Copied' : 'Copy'}
85
+ <Button
86
+ size="compact-xs"
87
+ fz={'10'}
88
+ variant="light"
89
+ onClick={copy}
90
+ color={copied
91
+ ? 'teal'
92
+ : 'gray'}>
93
+ {copied ? 'copied' : 'copy'}
84
94
  </Button>
85
95
  )}
86
96
  </CopyButton>
@@ -7,16 +7,7 @@ import { useDiagramState } from '../../../state'
7
7
  import type { ElementXYFlowNode } from '../../types'
8
8
  import { toDomPrecision } from '../../utils'
9
9
  import { NavigateToBtn } from '../shared/NavigateToBtn'
10
- import {
11
- container,
12
- cssElement,
13
- cssNavigateBtn,
14
- cssShapeSvg,
15
- description as cssdescription,
16
- indicator,
17
- technology as csstechnology,
18
- title as cssTitle
19
- } from './element.css'
10
+ import * as css from './element.css'
20
11
  import { ElementIcon } from './ElementIcon'
21
12
  import { ElementLink } from './ElementLink'
22
13
  import { ElementShapeSvg, SelectedIndicator } from './ElementShapeSvg'
@@ -38,6 +29,13 @@ const variants = {
38
29
  delay: isNumber(scale) && scale > selectedScale ? 0.075 : 0
39
30
  }
40
31
  }),
32
+ // dimmed: {
33
+ // filter: 'brightness(0.5)',
34
+ // transition: {
35
+ // duration: 0.8,
36
+ // ease: 'easeInOut',
37
+ // }
38
+ // },
41
39
  // dragging: {
42
40
  // scale: selectedScale
43
41
  // },
@@ -76,8 +74,9 @@ export function ElementNode({
76
74
  width,
77
75
  height
78
76
  }: ElementNodeProps) {
79
- const { isHovered, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
77
+ const { isHovered, isDimmed, hasOnNavigateTo, isHovercards, isInteractive } = useDiagramState(s => ({
80
78
  isHovered: s.hoveredNodeId === id,
79
+ isDimmed: s.dimmed.has(id),
81
80
  isInteractive: s.nodesDraggable || s.nodesSelectable || !!s.onNavigateTo,
82
81
  isHovercards: s.showElementLinks,
83
82
  hasOnNavigateTo: !!s.onNavigateTo
@@ -112,7 +111,11 @@ export function ElementNode({
112
111
  return (
113
112
  <motion.div
114
113
  id={id}
115
- className={clsx(container, 'likec4-element-node')}
114
+ className={clsx([
115
+ css.container,
116
+ isDimmed && css.dimmed,
117
+ 'likec4-element-node'
118
+ ])}
116
119
  data-likec4-color={element.color}
117
120
  data-likec4-shape={element.shape}
118
121
  variants={variants}
@@ -162,12 +165,14 @@ export function ElementNode({
162
165
  style={{ visibility: 'hidden' }}
163
166
  />
164
167
  <svg
165
- className={cssShapeSvg}
168
+ className={clsx(
169
+ css.cssShapeSvg
170
+ )}
166
171
  viewBox={`0 0 ${w} ${h}`}
167
172
  width={w}
168
173
  height={h}
169
174
  >
170
- <g className={indicator}>
175
+ <g className={css.indicator}>
171
176
  <SelectedIndicator
172
177
  shape={element.shape}
173
178
  w={w}
@@ -180,24 +185,24 @@ export function ElementNode({
180
185
  h={h}
181
186
  />
182
187
  </svg>
183
- <div className={clsx(cssElement, 'likec4-element')}>
188
+ <div className={clsx(css.element, 'likec4-element')}>
184
189
  {element.icon && <ElementIcon node={element} />}
185
190
  <Text
186
191
  component="div"
187
- className={clsx(cssTitle, 'likec4-element-title')}>
192
+ className={clsx(css.title, 'likec4-element-title')}>
188
193
  {element.title}
189
194
  </Text>
190
195
  {element.technology && (
191
196
  <Text
192
197
  component="div"
193
- className={clsx(csstechnology, 'likec4-element-technology')}>
198
+ className={clsx(css.technology, 'likec4-element-technology')}>
194
199
  {element.technology}
195
200
  </Text>
196
201
  )}
197
202
  {element.description && (
198
203
  <Text
199
204
  component="div"
200
- className={clsx(cssdescription, 'likec4-element-description')}>
205
+ className={clsx(css.description, 'likec4-element-description')}>
201
206
  {element.description}
202
207
  </Text>
203
208
  )}
@@ -219,7 +224,7 @@ export function ElementNode({
219
224
  ))} */
220
225
  }
221
226
  {isHovercards && element.links && <ElementLink element={element} />}
222
- {isNavigable && <NavigateToBtn xynodeId={id} className={cssNavigateBtn} />}
227
+ {isNavigable && <NavigateToBtn xynodeId={id} className={css.cssNavigateBtn} />}
223
228
  </motion.div>
224
229
  )
225
230
  }
@@ -19,6 +19,13 @@ export const container = style({
19
19
  }
20
20
  })
21
21
 
22
+ export const dimmed = style({})
23
+
24
+ globalStyle(`.react-flow__node:has(${dimmed})`, {
25
+ filter: vars.filterDimmed,
26
+ transition: 'filter 800ms ease-out'
27
+ })
28
+
22
29
  const indicatorKeyframes = generateIdentifier('indicator')
23
30
  globalKeyframes(indicatorKeyframes, {
24
31
  'from': {
@@ -92,7 +99,7 @@ export const description = style({
92
99
  fontStyle: 'normal',
93
100
  fontWeight: 400,
94
101
  fontSize: rem(14),
95
- lineHeight: 1.125,
102
+ lineHeight: 1.25,
96
103
  textAlign: 'center',
97
104
  textWrap: 'pretty',
98
105
  color: vars.element.loContrast,
@@ -120,7 +127,7 @@ export const technology = style({
120
127
  }
121
128
  })
122
129
 
123
- export const cssElement = style({
130
+ export const element = style({
124
131
  position: 'relative',
125
132
  width: '100%',
126
133
  height: '100%',
@@ -14,6 +14,7 @@ export function NavigateToBtn({ xynodeId, className }: NavigateToBtnProps) {
14
14
  <ActionIcon
15
15
  className={clsx('nodrag nopan', className)}
16
16
  radius="xl"
17
+ onPointerDownCapture={e => e.stopPropagation()}
17
18
  onClick={event => {
18
19
  event.stopPropagation()
19
20
  diagramApi.getState().triggerOnNavigateTo(xynodeId, event)
@@ -24,6 +24,10 @@ type TypedXYFlowNode<D extends Record<string, unknown>, T extends string> = SetR
24
24
  SetRequired<Node<D, T>, 'type'>,
25
25
  'id' | 'type'
26
26
  >
27
+ type TypedXYFlowEdge<D extends Record<string, unknown>, T extends string> = SetReadonly<
28
+ SetRequired<Edge<D, T>, 'type' | 'data'>,
29
+ 'id' | 'type'
30
+ >
27
31
 
28
32
  export type ElementXYFlowNode = TypedXYFlowNode<ElementNodeData, 'element'>
29
33
 
@@ -57,20 +61,15 @@ export type RelationshipData = {
57
61
  headPoint: Point | null
58
62
  tailPoint: Point | null
59
63
  type: 'bezier' | 'poly'
64
+ // if dynamic view
65
+ stepNum: number | null
60
66
  label: null | {
61
67
  bbox: BBox
62
68
  text: string
63
69
  }
64
70
  }
65
71
 
66
- export type RelationshipEdge = Simplify<
67
- SetRequired<
68
- Omit<Edge<RelationshipData>, 'type'>,
69
- 'data'
70
- > & {
71
- type: 'relationship'
72
- }
73
- >
72
+ export type RelationshipEdge = TypedXYFlowEdge<RelationshipData, 'relationship'>
74
73
 
75
74
  export type XYFlowEdge = RelationshipEdge
76
75