@likec4/diagram 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/KeepAspectRatioContainer.d.ts +9 -0
  2. package/dist/LikeC4Diagram.css.d.ts +1 -0
  3. package/dist/LikeC4Diagram.d.ts +1 -1
  4. package/dist/LikeC4Diagram.props.d.ts +16 -11
  5. package/dist/index.mjs +639 -762
  6. package/dist/state/DiagramContext.d.ts +54 -18
  7. package/dist/state/diagramStore.d.ts +61 -21
  8. package/dist/state/useDiagramStore.d.ts +54 -18
  9. package/dist/style.css +690 -79
  10. package/dist/ui/DiagramTitlePanel.css.d.ts +4 -0
  11. package/dist/ui/DiagramTitlePanel.d.ts +2 -0
  12. package/dist/ui/OptionsPanel.d.ts +1 -2
  13. package/dist/ui/index.d.ts +0 -1
  14. package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +1 -0
  15. package/dist/xyflow/{XYFlowWrapper.d.ts → XYFlow.d.ts} +2 -2
  16. package/dist/xyflow/XYFlowEvents.d.ts +133 -13
  17. package/dist/xyflow/edges/RelationshipEdge.d.ts +2 -2
  18. package/dist/xyflow/edges/edges.css.d.ts +1 -0
  19. package/dist/xyflow/hooks/useXYFlow.d.ts +20 -20
  20. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +7 -5
  21. package/dist/xyflow/types.d.ts +2 -2
  22. package/package.json +5 -3
  23. package/src/{ui/KeepAspectRatio.tsx → KeepAspectRatioContainer.tsx} +7 -6
  24. package/src/LikeC4Diagram.css.ts +8 -1
  25. package/src/LikeC4Diagram.props.ts +17 -12
  26. package/src/LikeC4Diagram.tsx +9 -6
  27. package/src/StaticLikeC4Diagram.tsx +1 -0
  28. package/src/globals.css.ts +48 -38
  29. package/src/state/diagramStore.ts +125 -68
  30. package/src/ui/DiagramTitlePanel.css.ts +32 -0
  31. package/src/ui/DiagramTitlePanel.tsx +139 -0
  32. package/src/ui/OptionsPanel.tsx +4 -23
  33. package/src/ui/index.ts +0 -1
  34. package/src/ui/options/NodeOptions.tsx +107 -30
  35. package/src/xyflow/FitviewOnDiagramChange.tsx +26 -20
  36. package/src/xyflow/SelectEdgesOnNodeFocus.tsx +57 -0
  37. package/src/xyflow/SyncWithDiagram.tsx +35 -26
  38. package/src/xyflow/{XYFlowWrapper.tsx → XYFlow.tsx} +24 -27
  39. package/src/xyflow/XYFlowEvents.tsx +175 -81
  40. package/src/xyflow/diagram-to-xyflow.ts +4 -4
  41. package/src/xyflow/edges/RelationshipEdge.tsx +43 -47
  42. package/src/xyflow/edges/edges.css.ts +77 -36
  43. package/src/xyflow/nodes/compound/CompoundNode.css.ts +94 -44
  44. package/src/xyflow/nodes/compound/CompoundNode.tsx +29 -31
  45. package/src/xyflow/nodes/element/ElementNode.tsx +2 -2
  46. package/src/xyflow/nodes/element/element.css.ts +1 -0
  47. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +2 -1
  48. package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
  49. package/dist/ui/KeepAspectRatio.d.ts +0 -9
  50. package/src/ui/KeepAspectRatio.css.ts +0 -7
@@ -1,20 +1,34 @@
1
- import type { EdgeMouseHandler, NodeMouseHandler, OnMoveEnd } from '@xyflow/react'
2
- import type { MouseEvent as ReactMouseEvent } from 'react'
3
- import { useMemo, useRef } from 'react'
1
+ import type { ReactFlowProps } from '@xyflow/react'
2
+ import { useEffect, useMemo, useRef } from 'react'
3
+ import type { Simplify } from 'type-fest'
4
4
  import { useDiagramStoreApi } from '../state'
5
5
  import { useXYStoreApi } from './hooks'
6
- import type { XYFlowEdge, XYFlowNode } from './types'
6
+ import type { XYFlowEdge, XYFlowNode, XYFlowState } from './types'
7
7
 
8
- type XYFlowEventHandlers = {
9
- onPaneClick: (event: ReactMouseEvent) => void
10
- onNodeContextMenu: NodeMouseHandler<XYFlowNode>
11
- onEdgeContextMenu: EdgeMouseHandler<XYFlowEdge>
12
- onPaneContextMenu: (event: ReactMouseEvent | MouseEvent) => void
13
- onNodeClick: NodeMouseHandler<XYFlowNode>
14
- onEdgeClick: EdgeMouseHandler<XYFlowEdge>
15
- // onMoveStart: OnMoveStart
16
- onMoveEnd: OnMoveEnd
17
- }
8
+ type XYFlowEventHandlers = Simplify<
9
+ Required<
10
+ Pick<
11
+ ReactFlowProps<XYFlowNode, XYFlowEdge>,
12
+ | 'onDoubleClick'
13
+ | 'onPaneClick'
14
+ | 'onNodeClick'
15
+ | 'onNodeDoubleClick'
16
+ | 'onEdgeClick'
17
+ | 'onMoveEnd'
18
+ | 'onNodeContextMenu'
19
+ | 'onEdgeContextMenu'
20
+ | 'onPaneContextMenu'
21
+ | 'onNodeMouseEnter'
22
+ | 'onNodeMouseLeave'
23
+ | 'onEdgeMouseEnter'
24
+ | 'onEdgeMouseLeave'
25
+ | 'onViewportChange'
26
+ >
27
+ >
28
+ >
29
+
30
+ const viewportDomNode = (state: XYFlowState) =>
31
+ state.domNode?.querySelector<HTMLDivElement>('.react-flow__viewport') ?? null
18
32
 
19
33
  export function useXYFlowEvents() {
20
34
  const diagramApi = useDiagramStoreApi()
@@ -22,79 +36,159 @@ export function useXYFlowEvents() {
22
36
 
23
37
  const dblclickTimeout = useRef<number>()
24
38
 
25
- return useMemo<XYFlowEventHandlers>(() => ({
26
- onPaneClick: (event) => {
27
- diagramApi.setState(
28
- {
29
- lastClickedNodeId: null,
30
- lastClickedEdgeId: null
31
- },
32
- false,
33
- 'onPaneClick'
34
- )
35
- const diagramState = diagramApi.getState()
39
+ const viewportRef = useRef(viewportDomNode(xyflowApi.getState()))
40
+
41
+ const dbclickGuarg = () => {
42
+ if (dblclickTimeout.current !== undefined) {
43
+ return true
44
+ }
45
+ dblclickTimeout.current = window.setTimeout(() => {
46
+ dblclickTimeout.current = undefined
47
+ }, 250)
48
+ return false
49
+ }
50
+
51
+ const transformRef = useRef('')
52
+ const cbRef = useRef<number>()
53
+
54
+ useEffect(() =>
55
+ xyflowApi.subscribe((state, prev) => {
56
+ if (state.domNode !== prev.domNode || (state.domNode && !viewportRef.current)) {
57
+ viewportRef.current = viewportDomNode(state)
58
+ }
59
+ }), [xyflowApi])
36
60
 
37
- if (dblclickTimeout.current) {
38
- window.clearTimeout(dblclickTimeout.current)
39
- dblclickTimeout.current = undefined
61
+ return useMemo(() =>
62
+ ({
63
+ onDoubleClick: (event) => {
64
+ diagramApi.getState().resetLastClicked()
65
+ const diagramState = diagramApi.getState()
40
66
  if (diagramState.onCanvasDblClick) {
41
67
  diagramState.onCanvasDblClick(event)
42
68
  return
43
69
  }
44
- diagramState.onCanvasClick?.(event)
45
- if (diagramState.fitViewEnabled && diagramState.viewportChanged) {
70
+ if (diagramState.fitViewEnabled && diagramState.zoomable) {
46
71
  diagramState.fitDiagram()
72
+ event.stopPropagation()
73
+ return
47
74
  }
48
- return
49
- }
50
-
51
- dblclickTimeout.current = window.setTimeout(() => {
52
- dblclickTimeout.current = undefined
53
- diagramApi.getState().onCanvasClick?.(event)
54
- }, 300)
55
- },
56
- onNodeContextMenu: (event, xynode) => {
57
- diagramApi.getState().setLastClickedNode(xynode.id)
58
- diagramApi.getState().onNodeContextMenu?.({
59
- element: xynode.data.element,
60
- xynode,
61
- event
62
- })
63
- },
64
- onPaneContextMenu: (event) => {
65
- diagramApi.getState().setLastClickedNode(null)
66
- diagramApi.getState().setLastClickedEdge(null)
67
- diagramApi.getState().onCanvasContextMenu?.(event as any)
68
- },
69
- onEdgeContextMenu: (event, xyedge) => {
70
- diagramApi.getState().setLastClickedEdge(xyedge.id)
71
- diagramApi.getState().onEdgeContextMenu?.({
72
- relation: xyedge.data.edge,
73
- xyedge,
74
- event
75
- })
76
- },
77
- onNodeClick: (event, xynode) => {
78
- diagramApi.getState().setLastClickedNode(xynode.id)
79
- diagramApi.getState().onNodeClick?.({
80
- element: xynode.data.element,
81
- xynode,
82
- event
83
- })
84
- },
85
- onEdgeClick: (event, xyedge) => {
86
- diagramApi.getState().setLastClickedEdge(xyedge.id)
87
- diagramApi.getState().onEdgeClick?.({
88
- relation: xyedge.data.edge,
89
- xyedge,
90
- event
91
- })
92
- },
93
- onMoveEnd: (event, _viewport) => {
94
- const viewportChanged = !!event
95
- if (viewportChanged !== diagramApi.getState().viewportChanged) {
96
- diagramApi.setState({ viewportChanged }, false, `viewport-changed: ${viewportChanged}`)
75
+ },
76
+ onPaneClick: (event) => {
77
+ if (dbclickGuarg()) {
78
+ return
79
+ }
80
+ const { focusedNodeId, fitDiagram, onCanvasClick, resetLastClicked } = diagramApi.getState()
81
+ resetLastClicked()
82
+ if (onCanvasClick) {
83
+ onCanvasClick(event)
84
+ return
85
+ }
86
+ // reset focus if clicked on empty space
87
+ if (focusedNodeId) {
88
+ fitDiagram()
89
+ event.stopPropagation()
90
+ }
91
+ },
92
+ onNodeContextMenu: (event, xynode) => {
93
+ diagramApi.getState().setLastClickedNode(xynode.id)
94
+ diagramApi.getState().onNodeContextMenu?.({
95
+ element: xynode.data.element,
96
+ xynode,
97
+ event
98
+ })
99
+ },
100
+ onPaneContextMenu: (event) => {
101
+ diagramApi.getState().resetLastClicked()
102
+ diagramApi.getState().onCanvasContextMenu?.(event as any)
103
+ },
104
+ onEdgeContextMenu: (event, xyedge) => {
105
+ diagramApi.getState().setLastClickedEdge(xyedge.id)
106
+ diagramApi.getState().onEdgeContextMenu?.({
107
+ relation: xyedge.data.edge,
108
+ xyedge,
109
+ event
110
+ })
111
+ },
112
+ onNodeClick: (event, xynode) => {
113
+ diagramApi.getState().setLastClickedNode(xynode.id)
114
+ const { focusedNodeId, fitDiagram, onNodeClick } = diagramApi.getState()
115
+ if (onNodeClick) {
116
+ onNodeClick({
117
+ element: xynode.data.element,
118
+ xynode,
119
+ event
120
+ })
121
+ return
122
+ }
123
+ // if we clicked on a node that is not focused, fit the diagram
124
+ if (!!focusedNodeId && focusedNodeId !== xynode.id) {
125
+ fitDiagram(xynode)
126
+ event.stopPropagation()
127
+ }
128
+ },
129
+ onNodeDoubleClick: (event, xynode) => {
130
+ diagramApi.getState().setLastClickedNode(xynode.id)
131
+ const { zoomable, fitViewEnabled, fitDiagram, onNodeClick } = diagramApi.getState()
132
+ if (onNodeClick) {
133
+ return
134
+ }
135
+ if (zoomable && fitViewEnabled) {
136
+ fitDiagram(xynode)
137
+ event.stopPropagation()
138
+ }
139
+ },
140
+ onEdgeClick: (event, xyedge) => {
141
+ diagramApi.getState().setLastClickedEdge(xyedge.id)
142
+ const { focusedNodeId, fitDiagram, onEdgeClick, xyflow } = diagramApi.getState()
143
+ if (onEdgeClick) {
144
+ onEdgeClick({
145
+ relation: xyedge.data.edge,
146
+ xyedge,
147
+ event
148
+ })
149
+ return
150
+ }
151
+ if (focusedNodeId && (focusedNodeId === xyedge.source || focusedNodeId === xyedge.target)) {
152
+ const nextFocus = focusedNodeId === xyedge.source ? xyedge.target : xyedge.source
153
+ fitDiagram(xyflow.getNode(nextFocus))
154
+ event.stopPropagation()
155
+ }
156
+ },
157
+ onMoveEnd: (event, _viewport) => {
158
+ // if event is present, the move was triggered by user
159
+ const viewportChanged = !!event
160
+ if (viewportChanged !== diagramApi.getState().viewportChanged) {
161
+ diagramApi.setState({ viewportChanged }, false, `viewport-changed: ${viewportChanged}`)
162
+ }
163
+ },
164
+ onNodeMouseEnter: (event, xynode) => {
165
+ diagramApi.getState().setHoveredNode(xynode.id)
166
+ },
167
+ onNodeMouseLeave: (event, xynode) => {
168
+ diagramApi.getState().setHoveredNode(null)
169
+ },
170
+ onEdgeMouseEnter: (event, xyedge) => {
171
+ diagramApi.getState().setHoveredEdge(xyedge.id)
172
+ },
173
+ onEdgeMouseLeave: (event, xyedge) => {
174
+ diagramApi.getState().setHoveredEdge(null)
175
+ },
176
+ /**
177
+ * WOKAROUND:
178
+ * Viewport transform is not rounded to integers which results in blurry nodes on some resolution
179
+ * https://github.com/xyflow/xyflow/issues/3282
180
+ */
181
+ onViewportChange: ({ x, y, zoom }) => {
182
+ const scale = zoom != 1 ? zoom.toFixed(6) : '1'
183
+ transformRef.current = `translate(${Math.round(x)}px, ${Math.round(y)}px) scale(${scale})`
184
+ cbRef.current ??= requestIdleCallback(() => {
185
+ cbRef.current = undefined
186
+ if (viewportRef.current && viewportRef.current.style.transform !== transformRef.current) {
187
+ viewportRef.current.style.transform = transformRef.current
188
+ }
189
+ }, {
190
+ timeout: 1000
191
+ })
97
192
  }
98
- }
99
- }), [diagramApi, xyflowApi])
193
+ }) satisfies XYFlowEventHandlers, [diagramApi, xyflowApi])
100
194
  }
@@ -118,10 +118,10 @@ export function diagramViewToXYFlowData(
118
118
  initialHeight: node.height, */
119
119
  width: node.width,
120
120
  height: node.height,
121
- measured: {
122
- width: node.width,
123
- height: node.height
124
- },
121
+ // measured: {
122
+ // width: node.width,
123
+ // height: node.height
124
+ // },
125
125
  ...(parent && {
126
126
  parentId: ns + parent.id
127
127
  })
@@ -3,7 +3,9 @@ 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
11
  import { hasAtLeast } from 'remeda'
@@ -12,16 +14,7 @@ import { ZIndexes } from '../const'
12
14
  import { useXYStoreApi } from '../hooks'
13
15
  import { type XYFlowEdge } from '../types'
14
16
  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'
17
+ import * as edgesCss from './edges.css'
25
18
  import { getEdgeParams } from './utils'
26
19
  // import { getEdgeParams } from './utils'
27
20
 
@@ -83,6 +76,7 @@ const isEqualProps = (prev: EdgeProps<XYFlowEdge>, next: EdgeProps<XYFlowEdge>)
83
76
  prev.id === next.id
84
77
  && prev.source === next.source
85
78
  && prev.target === next.target
79
+ && eq(prev.selected ?? false, next.selected ?? false)
86
80
  && isSamePoint(prev.sourceX, next.sourceX)
87
81
  && isSamePoint(prev.sourceY, next.sourceY)
88
82
  && isSamePoint(prev.targetX, next.targetX)
@@ -90,11 +84,12 @@ const isEqualProps = (prev: EdgeProps<XYFlowEdge>, next: EdgeProps<XYFlowEdge>)
90
84
  && eq(prev.data, next.data)
91
85
  )
92
86
 
87
+ const curve = d3line<Point>().curve(curveCatmullRomOpen.alpha(1))
88
+
93
89
  export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(function RelationshipEdgeR({
94
90
  id,
95
91
  data,
96
92
  selected: _selected,
97
- markerEnd: _markerEnd,
98
93
  style,
99
94
  source,
100
95
  target,
@@ -107,16 +102,13 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
107
102
  const isNotModified = isSamePosition(sourceNode.internals.positionAbsolute, sourceNode.data.element.position)
108
103
  && isSamePosition(targetNode.internals.positionAbsolute, targetNode.data.element.position)
109
104
 
110
- invariant(data, 'data is required')
111
- const {
112
- edge
113
- } = data
105
+ const { edge: diagramEdge, controlPoints } = data
114
106
  // const edgePath = bezierPath(edge.points)
115
107
 
116
- const color = edge.color ?? 'gray'
108
+ const color = diagramEdge.color ?? 'gray'
117
109
  const isHovered = useDiagramState(s => s.hoveredEdgeId === id)
118
110
 
119
- const line = edge.line ?? 'dashed'
111
+ const line = diagramEdge.line ?? 'dashed'
120
112
  const isDotted = line === 'dotted'
121
113
  const isDashed = isDotted || line === 'dashed'
122
114
 
@@ -132,7 +124,7 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
132
124
  let edgePath: string, labelX: number, labelY: number
133
125
 
134
126
  if (isNotModified) {
135
- edgePath = bezierPath(edge.points)
127
+ edgePath = bezierPath(diagramEdge.points)
136
128
  labelX = data.label?.bbox.x ?? 0
137
129
  labelY = data.label?.bbox.y ?? 0
138
130
  } else {
@@ -149,17 +141,22 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
149
141
  edgePath = _edgePath
150
142
  labelX = _labelX
151
143
  labelY = _labelY
144
+
145
+ const sourceCenterPos = getNodePositionWithOrigin(sourceNode, [-0.5, -0.5])
146
+ const targetCenterPos = getNodePositionWithOrigin(targetNode, [-0.5, -0.5])
147
+
148
+ edgePath = curve([
149
+ [sourceCenterPos.positionAbsolute.x, sourceCenterPos.positionAbsolute.y],
150
+ [sx, sy],
151
+ ...controlPoints,
152
+ [tx, ty],
153
+ [targetCenterPos.positionAbsolute.x, targetCenterPos.positionAbsolute.y]
154
+ ])!
152
155
  }
153
- // const deferredEdgePath = edgePath
154
- // useTilg()`
155
- // ${id}
156
- // path=${edgePath}
157
- // isModified=${isModified}
158
- // `
159
156
 
160
157
  return (
161
- <g className={clsx(container)} data-likec4-color={color} data-edge-hovered={isHovered}>
162
- <g className={clsx(fillStrokeCtx)}>
158
+ <g className={clsx(edgesCss.container)} data-likec4-color={color} data-edge-hovered={isHovered}>
159
+ <g className={clsx(edgesCss.fillStrokeCtx)}>
163
160
  <defs>
164
161
  <marker
165
162
  id={`arrow-${id}`}
@@ -179,30 +176,29 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
179
176
  edgePath={edgePath}
180
177
  interactionWidth={interactionWidth ?? 10}
181
178
  strokeDasharray={strokeDasharray}
182
- markerStart={edge.tailArrow ? marker : undefined}
183
- markerEnd={edge.headArrow ? marker : undefined}
179
+ markerStart={diagramEdge.tailArrow ? marker : undefined}
180
+ markerEnd={diagramEdge.headArrow ? marker : undefined}
184
181
  style={style} />
185
182
  {
186
- /*
187
- {controlPoints.map((p, i) => (
183
+ /* controlPoints.map((p, i) => (
188
184
  <circle
189
- className={styles.controlPoint}
185
+ className={edgesCss.controlPoint}
190
186
  key={i}
191
187
  cx={p[0]}
192
188
  cy={p[1]}
193
189
  r={5}
194
190
  />
195
- ))} */
191
+ )) */
196
192
  }
197
193
  {data.label && (
198
194
  <EdgeLabelRenderer>
199
195
  <Box
200
- className={clsx(container, edgeLabel)}
196
+ className={clsx(edgesCss.container, edgesCss.edgeLabel)}
201
197
  data-likec4-color={color}
202
198
  style={{
203
199
  ...assignInlineVars({
204
- [varLabelX]: toDomPrecision(labelX) + 'px',
205
- [varLabelY]: toDomPrecision(labelY) + 'px'
200
+ [edgesCss.varLabelX]: toDomPrecision(labelX) + 'px',
201
+ [edgesCss.varLabelY]: toDomPrecision(labelY) + 'px'
206
202
  }),
207
203
  maxWidth: data.label.bbox.width + 10,
208
204
  zIndex: edgeLookup.get(id)?.zIndex ?? ZIndexes.Edge
@@ -211,7 +207,7 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
211
207
  'data-edge-hovered': isHovered
212
208
  }}
213
209
  >
214
- <Box className={edgeLabelBody}>
210
+ <Box className={edgesCss.edgeLabelBody}>
215
211
  {data.label.text}
216
212
  </Box>
217
213
  {
@@ -246,30 +242,30 @@ export const RelationshipEdge = /* @__PURE__ */ memo<EdgeProps<XYFlowEdge>>(func
246
242
  )
247
243
  }, isEqualProps)
248
244
 
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
- }>(({
245
+ const RelationshipPath = ({
257
246
  edgePath,
258
247
  interactionWidth,
259
248
  strokeDasharray,
260
249
  markerStart,
261
250
  markerEnd,
262
251
  style
252
+ }: {
253
+ edgePath: string
254
+ interactionWidth: number
255
+ strokeDasharray: string | undefined
256
+ markerStart: string | undefined
257
+ markerEnd: string | undefined
258
+ style: CSSProperties | undefined
263
259
  }) => (
264
260
  <>
265
261
  <path
266
- className={clsx('react-flow__edge-path', edgePathBg)}
262
+ className={clsx('react-flow__edge-path', edgesCss.edgePathBg)}
267
263
  d={edgePath}
268
264
  style={style}
269
265
  strokeLinecap={'round'}
270
266
  />
271
267
  <path
272
- className={clsx('react-flow__edge-path', cssEdgePath)}
268
+ className={clsx('react-flow__edge-path', edgesCss.cssEdgePath)}
273
269
  d={edgePath}
274
270
  style={style}
275
271
  strokeLinecap={'round'}
@@ -285,4 +281,4 @@ const RelationshipPath = memo<{
285
281
  strokeWidth={interactionWidth}
286
282
  />
287
283
  </>
288
- ))
284
+ )
@@ -1,50 +1,56 @@
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, 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
+ globalStyle(`:where([data-mantine-color-scheme="dark"]) ${container}`, {
19
+ vars: {
20
+ [mixColor]: `white`,
21
+ [xyvars.edge.labelColor]: vars.relation.labelColor,
22
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 55%)`
23
+ }
24
+ })
25
+
26
+ const isSelected = '.react-flow__edge.selected'
27
+
28
+ globalStyle(`:where(${isSelected}) ${container}`, {
29
+ vars: {
30
+ [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
31
+ [xyvars.edge.strokeWidth]: '3px'
27
32
  }
28
33
  })
29
34
 
30
35
  globalStyle(`${container}[data-edge-hovered='true']`, {
31
36
  vars: {
32
- [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, white 30%)`,
37
+ // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
38
+ [xyvars.edge.stroke]: xyvars.edge.strokeSelected,
33
39
  [xyvars.edge.strokeWidth]: '3px'
34
40
  }
35
41
  })
42
+
43
+ globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
44
+ vars: {
45
+ [xyvars.edge.strokeWidth]: '3.6px'
46
+ }
47
+ })
36
48
  // globalStyle(`${container}[data-edge-hovered='true']`, {
37
49
  // vars: {
38
50
  // [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, white 35%)`
39
51
  // },
40
52
  // })
41
53
 
42
- globalStyle(`.react-flow__edge.selected ${container}`, {
43
- vars: {
44
- [xyvars.edge.strokeWidth]: '2.5'
45
- }
46
- })
47
-
48
54
  globalStyle(`.react-flow__edges > svg`, {
49
55
  mixBlendMode: 'screen'
50
56
  })
@@ -53,25 +59,60 @@ globalStyle(`:where([data-mantine-color-scheme="dark"]) .react-flow__edges > svg
53
59
  })
54
60
 
55
61
  export const edgePathBg = style({
56
- strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8)`,
57
- strokeOpacity: 0.1
62
+ strokeWidth: xyvars.edge.strokeWidth,
63
+ strokeOpacity: 0.05,
64
+ // transition: 'stroke-width 175ms ease-in-out',
65
+ // transition: 'stroke-width 175ms ease-in-out, stroke-opacity 150ms ease-out',
66
+ transitionProperty: 'stroke-width, stroke-opacity',
67
+ transitionDuration: '155ms',
68
+ transitionTimingFunction: 'ease-out',
69
+ selectors: {
70
+ [`:where(${isSelected}, [data-edge-hovered='true']) &`]: {
71
+ strokeWidth: `calc(${xyvars.edge.strokeWidth} + 8px)`,
72
+ strokeOpacity: 0.11
73
+ }
74
+ }
58
75
  })
59
76
 
60
77
  export const fillStrokeCtx = style({
61
78
  fill: xyvars.edge.stroke,
62
- stroke: xyvars.edge.stroke,
63
- selectors: {
64
- '.react-flow__edge.selected &': {
65
- vars: {
66
- [xyvars.edge.stroke]: xyvars.edge.strokeSelected
67
- }
68
- }
79
+ stroke: xyvars.edge.stroke
80
+ })
81
+
82
+ export const controlPoint = style({
83
+ 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
90
+ }
91
+ })
92
+
93
+ const strokeKeyframes = generateIdentifier('strokeKeyframes')
94
+ globalKeyframes(strokeKeyframes, {
95
+ 'from': {
96
+ strokeDashoffset: 18 * 2
97
+ },
98
+ 'to': {
99
+ strokeDashoffset: 1
69
100
  }
70
101
  })
71
102
 
72
103
  export const cssEdgePath = style({
104
+ animationDuration: '800ms',
105
+ animationIterationCount: 'infinite',
106
+ animationTimingFunction: 'linear',
73
107
  selectors: {
74
- [`${container}[data-edge-hovered='true'] &`]: {
108
+ [`:where([data-edge-hovered='true']) &`]: {
109
+ animationName: strokeKeyframes,
110
+ animationDelay: '500ms',
111
+ transition: 'all 130ms ease-out'
112
+ },
113
+ [`:where(${isSelected}) &`]: {
114
+ animationName: strokeKeyframes,
115
+ animationDelay: '0ms',
75
116
  transition: 'all 130ms ease-out'
76
117
  }
77
118
  }
@@ -97,7 +138,7 @@ export const edgeLabel = style({
97
138
  backgroundColor: xyvars.edge.labelBgColor,
98
139
  borderRadius: '3px',
99
140
  transform: varTranslate,
100
- willChange: 'transform',
141
+ backfaceVisibility: 'hidden',
101
142
  vars: {
102
143
  // [varTranslate]: `translate(calc(${varLabelX} - 50%), calc(${varLabelY} - 50%))`,
103
144
  [varTranslate]: `translate(${varLabelX}, ${varLabelY})`,