@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.
- package/dist/KeepAspectRatioContainer.d.ts +1 -1
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +22 -12
- package/dist/StaticLikeC4Diagram.d.ts +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +2209 -706
- package/dist/mantine/EnsureMantine.d.ts +1 -1
- package/dist/state/DiagramContext.d.ts +2143 -19
- package/dist/state/WhenInitialized.d.ts +1 -1
- package/dist/state/diagramStore.d.ts +2152 -19
- package/dist/state/useDiagramStore.d.ts +2142 -18
- package/dist/style.css +256 -118
- package/dist/theme.css.d.ts +1 -0
- package/dist/ui/DiagramTitlePanel.css.d.ts +4 -0
- package/dist/ui/DiagramTitlePanel.d.ts +1 -0
- package/dist/ui/DynamicViewWalkthrough.css.d.ts +3 -0
- package/dist/ui/DynamicViewWalkthrough.d.ts +1 -0
- package/dist/ui/OptionsPanel.d.ts +1 -3
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/options/NodeOptions.d.ts +1 -2
- package/dist/xyflow/FitviewOnDiagramChange.d.ts +4 -2
- package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +1 -0
- package/dist/xyflow/SyncWithDiagram.d.ts +1 -2
- package/dist/xyflow/XYFlow.d.ts +4 -7
- package/dist/xyflow/XYFlowBackground.d.ts +5 -4
- package/dist/xyflow/XYFlowEvents.d.ts +84 -84
- package/dist/xyflow/XYFlowInner.d.ts +11 -0
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +14 -14
- package/dist/xyflow/edges/edges.css.d.ts +5 -2
- package/dist/xyflow/edges/utils.d.ts +2 -0
- package/dist/xyflow/hooks/useXYFlow.d.ts +126 -126
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementLink.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -1
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +2 -2
- package/dist/xyflow/nodes/element/element.css.d.ts +2 -1
- package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +1 -2
- package/dist/xyflow/types.d.ts +18 -18
- package/package.json +21 -16
- package/src/KeepAspectRatioContainer.tsx +1 -1
- package/src/LikeC4Diagram.css.ts +4 -1
- package/src/LikeC4Diagram.props.ts +24 -14
- package/src/LikeC4Diagram.tsx +18 -9
- package/src/StaticLikeC4Diagram.tsx +2 -0
- package/src/globals.css.ts +7 -1
- package/src/index.ts +0 -3
- package/src/state/DiagramContext.tsx +21 -12
- package/src/state/diagramStore.ts +275 -98
- package/src/theme.css.ts +1 -0
- package/src/ui/DiagramTitlePanel.css.ts +33 -0
- package/src/ui/DiagramTitlePanel.tsx +138 -0
- package/src/ui/DynamicViewWalkthrough.css.ts +46 -0
- package/src/ui/DynamicViewWalkthrough.tsx +86 -0
- package/src/ui/OptionsPanel.tsx +7 -24
- package/src/ui/index.ts +1 -0
- package/src/ui/options/NodeOptions.tsx +103 -27
- package/src/xyflow/FitviewOnDiagramChange.tsx +89 -69
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +75 -0
- package/src/xyflow/SyncWithDiagram.tsx +55 -101
- package/src/xyflow/XYFlow.tsx +52 -32
- package/src/xyflow/XYFlowBackground.tsx +7 -5
- package/src/xyflow/XYFlowEvents.tsx +150 -104
- package/src/xyflow/XYFlowInner.tsx +50 -0
- package/src/xyflow/diagram-to-xyflow.ts +16 -21
- package/src/xyflow/edges/RelationshipEdge.tsx +102 -70
- package/src/xyflow/edges/edges.css.ts +128 -53
- package/src/xyflow/edges/utils.ts +31 -0
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +20 -12
- package/src/xyflow/nodes/compound/CompoundNode.tsx +6 -3
- package/src/xyflow/nodes/element/ElementLink.tsx +13 -3
- package/src/xyflow/nodes/element/ElementNode.tsx +24 -19
- package/src/xyflow/nodes/element/element.css.ts +10 -2
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -0
- 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
|
|
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
|
|
65
|
+
// If points are within 3px, consider them the same
|
|
74
66
|
const isSamePoint = (a: number, b: number) => {
|
|
75
|
-
return Math.abs(a - b) <
|
|
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
|
|
108
|
-
|
|
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 =
|
|
117
|
-
const
|
|
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 =
|
|
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 (
|
|
135
|
-
edgePath = bezierPath(
|
|
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
|
|
162
|
-
|
|
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
|
|
167
|
-
refX=
|
|
168
|
-
refY="
|
|
187
|
+
viewBox="0 0 16 10"
|
|
188
|
+
refX={isModified ? '12' : '10'}
|
|
189
|
+
refY="5"
|
|
169
190
|
markerWidth="5"
|
|
170
|
-
markerHeight="
|
|
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
|
|
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={
|
|
183
|
-
markerEnd={
|
|
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={
|
|
208
|
+
className={edgesCss.controlPoint}
|
|
190
209
|
key={i}
|
|
191
210
|
cx={p[0]}
|
|
192
211
|
cy={p[1]}
|
|
193
|
-
r={
|
|
212
|
+
r={3}
|
|
194
213
|
/>
|
|
195
|
-
))}
|
|
196
|
-
|
|
197
|
-
{data.label && (
|
|
214
|
+
))}
|
|
215
|
+
{(data.label || isStepEdge) && (
|
|
198
216
|
<EdgeLabelRenderer>
|
|
199
217
|
<Box
|
|
200
|
-
className={clsx(
|
|
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]:
|
|
205
|
-
[varLabelY]:
|
|
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
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
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 =
|
|
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},
|
|
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.
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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},
|
|
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
|
-
|
|
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:
|
|
57
|
-
strokeOpacity: 0.
|
|
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
|
-
|
|
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
|
-
'
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
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:
|
|
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(
|
|
62
|
-
transitionDelay: '
|
|
65
|
+
[`:where(.react-flow__node.selected) &`]: {
|
|
66
|
+
transitionDelay: '50ms',
|
|
67
|
+
// borderColor: 'transparent',
|
|
63
68
|
// transitionTimingFunction: 'ease-out',
|
|
64
69
|
vars: {
|
|
65
|
-
[bgTransparencyDelta]: '
|
|
70
|
+
[bgTransparencyDelta]: '5%'
|
|
66
71
|
}
|
|
67
72
|
},
|
|
68
|
-
[`:where(
|
|
69
|
-
transitionDelay: '
|
|
70
|
-
borderColor: 'transparent',
|
|
73
|
+
[`:where([data-hovered]) &`]: {
|
|
74
|
+
transitionDelay: '175ms',
|
|
71
75
|
// transitionTimingFunction: 'ease-out',
|
|
72
76
|
vars: {
|
|
73
|
-
[bgTransparencyDelta]: '
|
|
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:
|
|
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,
|