@likec4/diagram 1.0.0-next.2 → 1.0.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 (124) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +0 -1
  2. package/dist/LikeC4Diagram.d.ts +1 -2
  3. package/dist/LikeC4Diagram.props.d.ts +24 -25
  4. package/dist/StaticLikeC4Diagram.d.ts +1 -2
  5. package/dist/globals.css.d.ts +1 -0
  6. package/dist/hooks/index.d.ts +2 -3
  7. package/dist/hooks/useSetState.d.ts +3 -0
  8. package/dist/hooks/useUpdateEffect.d.ts +4 -0
  9. package/dist/icons/Clipboard.d.ts +0 -1
  10. package/dist/icons/Link.d.ts +0 -1
  11. package/dist/icons/ZoomIn.d.ts +0 -1
  12. package/dist/icons/index.d.ts +0 -1
  13. package/dist/index.d.ts +1 -4
  14. package/dist/index.mjs +9388 -0
  15. package/dist/mantine/EnsureMantine.d.ts +2 -4
  16. package/dist/mantine/theme.d.ts +3 -0
  17. package/dist/mantine.css.d.ts +1 -0
  18. package/dist/state/DiagramContext.d.ts +386 -0
  19. package/dist/state/WhenInitialized.d.ts +3 -0
  20. package/dist/state/diagramStore.d.ts +428 -0
  21. package/dist/state/index.d.ts +4 -2
  22. package/dist/state/useDiagramStore.d.ts +387 -0
  23. package/dist/style.css +1212 -1
  24. package/dist/theme.css.d.ts +16 -2
  25. package/dist/ui/Camera.d.ts +0 -1
  26. package/dist/ui/KeepAspectRatio.css.d.ts +0 -1
  27. package/dist/ui/KeepAspectRatio.d.ts +0 -1
  28. package/dist/ui/OptionsPanel.css.d.ts +0 -1
  29. package/dist/ui/OptionsPanel.d.ts +0 -1
  30. package/dist/ui/index.d.ts +0 -1
  31. package/dist/ui/options/NodeOptions.d.ts +0 -1
  32. package/dist/xyflow/FitviewOnDiagramChange.d.ts +1 -5
  33. package/dist/xyflow/SyncWithDiagram.d.ts +5 -0
  34. package/dist/xyflow/XYFlowBackground.d.ts +0 -1
  35. package/dist/xyflow/XYFlowEvents.d.ts +5 -15
  36. package/dist/xyflow/XYFlowWrapper.d.ts +4 -5
  37. package/dist/xyflow/const.d.ts +1 -2
  38. package/dist/xyflow/diagram-to-xyflow.d.ts +4 -2
  39. package/dist/xyflow/edges/RelationshipEdge.d.ts +7 -7
  40. package/dist/xyflow/edges/edges.css.d.ts +2 -1
  41. package/dist/xyflow/edges/utils.d.ts +3 -4
  42. package/dist/xyflow/hooks/index.d.ts +2 -37
  43. package/dist/xyflow/hooks/{use-layout-/321/201onstraints.d.ts → useLayoutConstraints.d.ts} +2 -4
  44. package/dist/xyflow/hooks/useXYFlow.d.ts +266 -0
  45. package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -1
  46. package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -1
  47. package/dist/xyflow/nodes/compound/index.d.ts +0 -1
  48. package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -2
  49. package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -1
  50. package/dist/xyflow/nodes/element/ElementLink.d.ts +1 -2
  51. package/dist/xyflow/nodes/element/ElementNode.d.ts +1 -3
  52. package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +2 -3
  53. package/dist/xyflow/nodes/element/element.css.d.ts +0 -1
  54. package/dist/xyflow/nodes/element/index.d.ts +1 -2
  55. package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -1
  56. package/dist/xyflow/types.d.ts +20 -8
  57. package/dist/xyflow/utils.d.ts +0 -1
  58. package/package.json +36 -34
  59. package/src/LikeC4Diagram.css.ts +31 -0
  60. package/src/LikeC4Diagram.props.ts +154 -0
  61. package/src/LikeC4Diagram.tsx +119 -0
  62. package/src/StaticLikeC4Diagram.tsx +39 -0
  63. package/src/globals.css.ts +128 -0
  64. package/src/hooks/index.ts +2 -0
  65. package/src/hooks/useSetState.ts +19 -0
  66. package/src/hooks/useUpdateEffect.ts +28 -0
  67. package/src/icons/Clipboard.tsx +24 -0
  68. package/src/icons/Link.tsx +23 -0
  69. package/src/icons/ZoomIn.tsx +25 -0
  70. package/src/icons/index.ts +3 -0
  71. package/src/index.ts +11 -0
  72. package/src/mantine/EnsureMantine.tsx +14 -0
  73. package/src/mantine/theme.ts +19 -0
  74. package/src/mantine.css.ts +4 -0
  75. package/src/reset.d.ts +2 -0
  76. package/src/state/DiagramContext.tsx +42 -0
  77. package/src/state/WhenInitialized.tsx +24 -0
  78. package/src/state/diagramStore.ts +358 -0
  79. package/src/state/index.ts +4 -0
  80. package/src/state/useDiagramStore.ts +39 -0
  81. package/src/theme.css.ts +64 -0
  82. package/src/ui/Camera.tsx +174 -0
  83. package/src/ui/KeepAspectRatio.css.ts +7 -0
  84. package/src/ui/KeepAspectRatio.tsx +35 -0
  85. package/src/ui/OptionsPanel.css.ts +10 -0
  86. package/src/ui/OptionsPanel.tsx +65 -0
  87. package/src/ui/index.ts +3 -0
  88. package/src/ui/options/NodeOptions.tsx +253 -0
  89. package/src/xyflow/FitviewOnDiagramChange.tsx +93 -0
  90. package/src/xyflow/SyncWithDiagram.tsx +113 -0
  91. package/src/xyflow/XYFlowBackground.tsx +33 -0
  92. package/src/xyflow/XYFlowEvents.tsx +100 -0
  93. package/src/xyflow/XYFlowWrapper.tsx +166 -0
  94. package/src/xyflow/const.ts +7 -0
  95. package/src/xyflow/diagram-to-xyflow.ts +197 -0
  96. package/src/xyflow/edges/RelationshipEdge.tsx +288 -0
  97. package/src/xyflow/edges/edges.css.ts +124 -0
  98. package/src/xyflow/edges/utils.ts +110 -0
  99. package/src/xyflow/hooks/index.ts +2 -0
  100. package/src/xyflow/hooks/useLayoutConstraints.ts +289 -0
  101. package/src/xyflow/hooks/useXYFlow.ts +20 -0
  102. package/src/xyflow/nodes/compound/CompoundNode.css.ts +191 -0
  103. package/src/xyflow/nodes/compound/CompoundNode.tsx +105 -0
  104. package/src/xyflow/nodes/compound/index.ts +1 -0
  105. package/src/xyflow/nodes/element/ElementIcon.tsx +44 -0
  106. package/src/xyflow/nodes/element/ElementLink.css.ts +67 -0
  107. package/src/xyflow/nodes/element/ElementLink.tsx +94 -0
  108. package/src/xyflow/nodes/element/ElementNode.tsx +225 -0
  109. package/src/xyflow/nodes/element/ElementShapeSvg.tsx +181 -0
  110. package/src/xyflow/nodes/element/element.css.ts +261 -0
  111. package/src/xyflow/nodes/element/index.ts +1 -0
  112. package/src/xyflow/nodes/shared/NavigateToBtn.tsx +25 -0
  113. package/src/xyflow/types.ts +100 -0
  114. package/src/xyflow/utils.ts +12 -0
  115. package/dist/EmbeddedLikeC4Diagram.css.d.ts +0 -6
  116. package/dist/EmbeddedLikeC4Diagram.d.ts +0 -18
  117. package/dist/bundle.d.ts +0 -3
  118. package/dist/hooks/use-set-state.d.ts +0 -2
  119. package/dist/hooks/use-update-effect.d.ts +0 -4
  120. package/dist/index.css.d.ts +0 -2
  121. package/dist/index.js +0 -13043
  122. package/dist/mantine/index.d.ts +0 -3
  123. package/dist/state/DiagramState.d.ts +0 -162
  124. package/dist/xyflow/UpdateOnDiagramChange.d.ts +0 -10
@@ -0,0 +1,110 @@
1
+ import { Position, type XYPosition } from '@xyflow/react'
2
+ import { getNodeDimensions } from '@xyflow/system'
3
+ import type { InternalXYFlowNode } from '../types'
4
+
5
+ // this helper function returns the intersection point
6
+ // of the line between the center of the intersectionNode and the target node
7
+ function getNodeIntersection(intersectionNode: InternalXYFlowNode, targetNode: InternalXYFlowNode): XYPosition {
8
+ // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
9
+ const {
10
+ width: intersectionNodeWidth,
11
+ height: intersectionNodeHeight
12
+ } = getNodeDimensions(intersectionNode)
13
+ const {
14
+ width: targetNodeWidth,
15
+ height: targetNodeHeight
16
+ } = getNodeDimensions(targetNode)
17
+
18
+ const intersectionNodePosition = intersectionNode.internals.positionAbsolute
19
+ const targetPosition = targetNode.internals.positionAbsolute
20
+
21
+ const w = intersectionNodeWidth / 2
22
+ const h = intersectionNodeHeight / 2
23
+
24
+ const x2 = intersectionNodePosition.x + w
25
+ const y2 = intersectionNodePosition.y + h
26
+ const x1 = targetPosition.x + targetNodeWidth / 2
27
+ const y1 = targetPosition.y + targetNodeHeight / 2
28
+
29
+ const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h)
30
+ const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h)
31
+ const a = 1 / (Math.abs(xx1) + Math.abs(yy1))
32
+ const xx3 = a * xx1
33
+ const yy3 = a * yy1
34
+ const x = w * (xx3 + yy3) + x2
35
+ const y = h * (-xx3 + yy3) + y2
36
+
37
+ return { x, y }
38
+ }
39
+
40
+ // returns the position (top,right,bottom or right) passed node compared to the intersection point
41
+ export function getPointPosition(node: InternalXYFlowNode, intersectionPoint: XYPosition) {
42
+ const n = {
43
+ // x: node.data.element.position[0],
44
+ // y: node.data.element.position[1],
45
+ ...node.internals.positionAbsolute,
46
+ ...getNodeDimensions(node)
47
+ }
48
+ const nx = Math.round(n.x)
49
+ const ny = Math.round(n.y)
50
+ const px = Math.round(intersectionPoint.x)
51
+ const py = Math.round(intersectionPoint.y)
52
+
53
+ let handlePosition = Position.Top
54
+ if (px <= nx + 1) {
55
+ handlePosition = Position.Left
56
+ } else if (px >= nx + n.width - 1) {
57
+ handlePosition = Position.Right
58
+ } else if (py <= ny + 1) {
59
+ handlePosition = Position.Top
60
+ } else if (py >= n.y + n.height - 1) {
61
+ handlePosition = Position.Bottom
62
+ }
63
+
64
+ let handleWidth = 8, handleHeight = 8
65
+
66
+ let offsetX = 0
67
+ let offsetY = 0
68
+
69
+ // this is a tiny detail to make the markerEnd of an edge visible.
70
+ // The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
71
+ // when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position
72
+ switch (handlePosition) {
73
+ case Position.Left:
74
+ offsetX = -handleWidth
75
+ break
76
+ case Position.Right:
77
+ offsetX = handleWidth
78
+ break
79
+ case Position.Top:
80
+ offsetY = -handleHeight
81
+ break
82
+ case Position.Bottom:
83
+ offsetY = handleHeight
84
+ break
85
+ }
86
+
87
+ return [
88
+ intersectionPoint.x + offsetX,
89
+ intersectionPoint.y + offsetY,
90
+ handlePosition
91
+ ] as const
92
+ }
93
+
94
+ // returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
95
+ export function getEdgeParams(source: InternalXYFlowNode, target: InternalXYFlowNode) {
96
+ const sourceIntersectionPoint = getNodeIntersection(source, target)
97
+ const targetIntersectionPoint = getNodeIntersection(target, source)
98
+
99
+ const [sx, sy, sourcePos] = getPointPosition(source, sourceIntersectionPoint)
100
+ const [tx, ty, targetPos] = getPointPosition(target, targetIntersectionPoint)
101
+
102
+ return {
103
+ sx,
104
+ sy,
105
+ tx,
106
+ ty,
107
+ sourcePos,
108
+ targetPos
109
+ }
110
+ }
@@ -0,0 +1,2 @@
1
+ export * from './useLayoutConstraints'
2
+ export * from './useXYFlow'
@@ -0,0 +1,289 @@
1
+ import { invariant, isAncestor, nonNullable } from '@likec4/core'
2
+ import { Expression, Expression as Expr, Operator, Solver, Strength, Variable } from '@lume/kiwi'
3
+ import type { InternalNode, ReactFlowProps, XYPosition } from '@xyflow/react'
4
+ import { getNodeDimensions } from '@xyflow/system'
5
+ import { useMemo, useRef } from 'react'
6
+ import { isNullish } from 'remeda'
7
+ import { useDiagramStoreApi } from '../../state'
8
+ import { type XYFlowInstance, XYFlowNode } from '../types'
9
+ import { useXYStoreApi, type XYStoreApi } from './useXYFlow'
10
+
11
+ abstract class Rect {
12
+ id!: string
13
+ minX = new Variable()
14
+ minY = new Variable()
15
+ maxX: Expression | Variable = new Variable()
16
+ maxY: Expression | Variable = new Variable()
17
+
18
+ get x() {
19
+ return this.minX
20
+ }
21
+
22
+ get y() {
23
+ return this.minY
24
+ }
25
+
26
+ get positionAbsolute(): XYPosition {
27
+ return {
28
+ x: this.x.value(),
29
+ y: this.y.value()
30
+ }
31
+ }
32
+
33
+ get dimensions() {
34
+ return {
35
+ width: this.maxX.value() - this.minX.value(),
36
+ height: this.maxY.value() - this.minY.value()
37
+ }
38
+ }
39
+
40
+ // Position relative to parent
41
+ get position(): XYPosition {
42
+ const positionAbsolute = this.positionAbsolute
43
+ if (!this.parent) {
44
+ return positionAbsolute
45
+ }
46
+ const parentPosition = this.parent.positionAbsolute
47
+ return {
48
+ x: positionAbsolute.x - parentPosition.x,
49
+ y: positionAbsolute.y - parentPosition.y
50
+ }
51
+ }
52
+ protected abstract parent: Compound | null
53
+ }
54
+
55
+ class Compound extends Rect {
56
+ protected readonly children = [] as Rect[]
57
+
58
+ constructor(
59
+ protected solver: Solver,
60
+ xynode: InternalNode<XYFlowNode>,
61
+ protected readonly parent: Compound | null = null
62
+ ) {
63
+ super()
64
+ this.id = xynode.id
65
+
66
+ solver.createConstraint(this.maxX, Operator.Ge, this.minX)
67
+ solver.createConstraint(this.maxY, Operator.Ge, this.minY)
68
+
69
+ if (parent) {
70
+ parent.addChild(this)
71
+ }
72
+ }
73
+
74
+ addChild<R extends Rect>(rect: R) {
75
+ this.children.push(rect)
76
+
77
+ const leftPadding = new Expr(
78
+ rect.minX,
79
+ [-1, this.minX]
80
+ )
81
+ const { weak } = Strength
82
+ this.solver.createConstraint(leftPadding, Operator.Ge, 40)
83
+ this.solver.createConstraint(leftPadding, Operator.Eq, 40, weak)
84
+
85
+ const topPadding = new Expr(
86
+ rect.minY,
87
+ [-1, this.minY]
88
+ )
89
+ this.solver.createConstraint(topPadding, Operator.Ge, 60)
90
+ this.solver.createConstraint(topPadding, Operator.Eq, 60, weak)
91
+
92
+ const rightPadding = new Expr(
93
+ this.maxX,
94
+ [-1, rect.maxX]
95
+ )
96
+ this.solver.createConstraint(rightPadding, Operator.Ge, 40)
97
+ this.solver.createConstraint(rightPadding, Operator.Eq, 40, weak)
98
+
99
+ const bottomPadding = new Expr(
100
+ this.maxY,
101
+ [-1, rect.maxY]
102
+ )
103
+ this.solver.createConstraint(bottomPadding, Operator.Ge, 40)
104
+ this.solver.createConstraint(bottomPadding, Operator.Eq, 40, weak)
105
+ }
106
+ }
107
+
108
+ class Leaf extends Rect {
109
+ constructor(
110
+ solver: Solver,
111
+ xynode: InternalNode<XYFlowNode>,
112
+ public readonly parent: Compound | null = null,
113
+ public readonly isEditing: boolean = false
114
+ ) {
115
+ super()
116
+ this.id = xynode.id
117
+
118
+ // const isEditing: boolean = xynode.dragging ?? false
119
+ const x = Math.ceil(xynode.internals.positionAbsolute.x)
120
+ const y = Math.ceil(xynode.internals.positionAbsolute.y)
121
+
122
+ const { width, height } = getNodeDimensions(xynode)
123
+
124
+ this.maxX = new Expression(this.minX, Math.ceil(width))
125
+ this.maxY = new Expression(this.minY, Math.ceil(height))
126
+
127
+ // solver.createConstraint(this.width, Operator.Eq, width, Strength.required)
128
+ // solver.createConstraint(this.height, Operator.Eq, height, Strength.required)
129
+
130
+ if (isEditing) {
131
+ const superStrong = Strength.create(100, 0, 0)
132
+ solver.addEditVariable(this.minX, superStrong)
133
+ solver.addEditVariable(this.minY, superStrong)
134
+ solver.suggestValue(this.minX, x)
135
+ solver.suggestValue(this.minY, y)
136
+ } else {
137
+ const halfMedium = Strength.create(0.0, 1.0, 0.0, 0.5)
138
+ solver.createConstraint(this.minX, Operator.Eq, x, halfMedium)
139
+ solver.createConstraint(this.minY, Operator.Eq, y, halfMedium)
140
+ // const weaker = Strength.create(0.0, 0.0, 1.0, 0.5)
141
+ // const weak = Strength.create(0.0, 0.0, 1.0)
142
+ // const x = new Expr(
143
+ // this.minX,
144
+ // [-1, pos.x]
145
+ // )
146
+ // solver.createConstraint(x, Operator.Eq, 0, weaker)
147
+ // solver.createConstraint(x, Operator.Ge, -30, weak)
148
+ // solver.createConstraint(x, Operator.Le, 30, weak)
149
+
150
+ // const y = new Expr(
151
+ // this.minY,
152
+ // [-1, pos.y]
153
+ // )
154
+ // solver.createConstraint(y, Operator.Eq, 0, weaker)
155
+ // solver.createConstraint(y, Operator.Ge, -30, weak)
156
+ // solver.createConstraint(y, Operator.Le, 30, weak)
157
+ }
158
+
159
+ if (parent) {
160
+ parent.addChild(this)
161
+ }
162
+ }
163
+ }
164
+
165
+ function createLayoutConstraints(
166
+ xyflow: XYFlowInstance,
167
+ xyflowApi: XYStoreApi,
168
+ draggingNodeId: string
169
+ ) {
170
+ const { parentLookup, nodeLookup } = xyflowApi.getState()
171
+ const solver = new Solver()
172
+ const rects = new Map<string, Leaf | Compound>()
173
+
174
+ const traverse = new Array<{ xynode: InternalNode<XYFlowNode>; parent: Compound | null }>()
175
+
176
+ for (const [, xynode] of nodeLookup) {
177
+ if (isNullish(xynode.parentId)) {
178
+ traverse.push({
179
+ xynode,
180
+ parent: null
181
+ })
182
+ }
183
+ }
184
+
185
+ while (traverse.length > 0) {
186
+ const { xynode, parent } = traverse.shift()!
187
+ const isDragging = xynode.dragging === true || xynode.id === draggingNodeId
188
+
189
+ // Traverse children if the node is a compound, not dragging, and is an ancestor of the dragging node
190
+ const shouldTraverse = !isDragging && xynode.type === 'compound' && isAncestor(xynode.id, draggingNodeId)
191
+
192
+ const rect = shouldTraverse ? new Compound(solver, xynode, parent) : new Leaf(solver, xynode, parent, isDragging)
193
+ rects.set(xynode.id, rect)
194
+
195
+ if (shouldTraverse) {
196
+ parentLookup.get(xynode.id)?.forEach(child => {
197
+ traverse.push({
198
+ xynode: child,
199
+ parent: rect as Compound
200
+ })
201
+ })
202
+ }
203
+ }
204
+
205
+ solver.updateVariables()
206
+ solver.maxIterations = 1000
207
+
208
+ function updateXYFlowNodes() {
209
+ solver.updateVariables()
210
+
211
+ xyflow.setNodes(nodes =>
212
+ nodes.map((n) => {
213
+ if (n.id === draggingNodeId || n.dragging === true) {
214
+ return n
215
+ }
216
+ const rect = rects.get(n.id)
217
+ if (!rect) {
218
+ return n
219
+ }
220
+ const dimensions = rect.dimensions
221
+ const newXY = rect.position
222
+
223
+ if (newXY.x !== n.position.x || newXY.y !== n.position.y) {
224
+ return {
225
+ ...n,
226
+ position: rect.position,
227
+ width: dimensions.width,
228
+ height: dimensions.height,
229
+ measured: dimensions
230
+ }
231
+ }
232
+ if (dimensions.width !== n.width || dimensions.height !== n.height) {
233
+ return {
234
+ ...n,
235
+ width: dimensions.width,
236
+ height: dimensions.height,
237
+ measured: dimensions
238
+ }
239
+ }
240
+ return n
241
+ })
242
+ )
243
+ }
244
+
245
+ let animationFrameId: number | null = null
246
+
247
+ /**
248
+ * Move the editing node to the given position.
249
+ */
250
+ function onNodeDrag(xynode: XYFlowNode) {
251
+ const pos = nodeLookup.get(xynode.id)!.internals.positionAbsolute
252
+ const rect = nonNullable(rects.get(xynode.id))
253
+ solver.suggestValue(rect.minX, Math.ceil(pos.x))
254
+ solver.suggestValue(rect.minY, Math.ceil(pos.y))
255
+ animationFrameId ??= requestAnimationFrame(() => {
256
+ updateXYFlowNodes()
257
+ animationFrameId = null
258
+ })
259
+ }
260
+
261
+ return {
262
+ onNodeDrag,
263
+ updateXYFlowNodes
264
+ }
265
+ }
266
+
267
+ type LayoutConstraints = Required<Pick<ReactFlowProps<XYFlowNode>, 'onNodeDragStart' | 'onNodeDrag' | 'onNodeDragStop'>>
268
+ /**
269
+ * Keeps the layout constraints (parent nodes and children) when dragging a node
270
+ */
271
+ export function useLayoutConstraints(): LayoutConstraints {
272
+ const diagramApi = useDiagramStoreApi()
273
+ const xyflowApi = useXYStoreApi()
274
+ const solverRef = useRef<ReturnType<typeof createLayoutConstraints>>()
275
+
276
+ return useMemo((): LayoutConstraints => ({
277
+ onNodeDragStart: (_event, xynode) => {
278
+ const { xyflow } = diagramApi.getState()
279
+ solverRef.current = createLayoutConstraints(xyflow, xyflowApi, xynode.id)
280
+ },
281
+ onNodeDrag: (_event, xynode) => {
282
+ invariant(solverRef.current, 'solverRef.current should be defined')
283
+ solverRef.current?.onNodeDrag(xynode)
284
+ },
285
+ onNodeDragStop: (_event, _xynode) => {
286
+ solverRef.current = undefined
287
+ }
288
+ }), [xyflowApi])
289
+ }
@@ -0,0 +1,20 @@
1
+ import { useNodesData, useReactFlow, useStore, useStoreApi } from '@xyflow/react'
2
+ import { shallowEqual } from 'fast-equals'
3
+ import type { XYFlowEdge, XYFlowNode, XYFlowState } from '../types'
4
+
5
+ export const useXYFlow = useReactFlow<XYFlowNode, XYFlowEdge>
6
+
7
+ export const useXYNodesData = useNodesData<XYFlowNode>
8
+
9
+ export function useXYStore<StateSlice = unknown>(
10
+ selector: (state: XYFlowState) => StateSlice,
11
+ equalityFn?: (a: StateSlice, b: StateSlice) => boolean
12
+ ): StateSlice {
13
+ return useStore(
14
+ // @ts-expect-error - types are not correct
15
+ selector,
16
+ equalityFn ?? shallowEqual
17
+ )
18
+ }
19
+ export const useXYStoreApi = useStoreApi<XYFlowNode, XYFlowEdge>
20
+ export type XYStoreApi = ReturnType<typeof useXYStoreApi>
@@ -0,0 +1,191 @@
1
+ import { rem } from '@mantine/core'
2
+ import { createVar, fallbackVar, generateIdentifier, globalKeyframes, globalStyle, style } from '@vanilla-extract/css'
3
+ import { mantine } from '../../../mantine.css'
4
+ import { vars } from '../../../theme.css'
5
+
6
+ export const cssContainer = style({
7
+ width: '100%',
8
+ height: '100%',
9
+ position: 'relative',
10
+ padding: 0,
11
+ margin: 0
12
+ })
13
+
14
+ const bgTransparency = createVar('bgTransparency')
15
+
16
+ const outlineColor = fallbackVar(
17
+ mantine.colors.primaryColors.outline,
18
+ mantine.colors.primaryColors.filled,
19
+ vars.element.stroke
20
+ )
21
+
22
+ export const cssCompound = style({
23
+ width: '100%',
24
+ height: '100%',
25
+ position: 'relative',
26
+ borderRadius: rem(6),
27
+ boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 0 2px 2px -1px rgba(0,0,0,0.4)',
28
+ padding: rem(8),
29
+ background: vars.element.fill,
30
+ transition: 'all 300ms ease-out',
31
+ selectors: {
32
+ [`:where(.react-flow__node.selected) &`]: {
33
+ boxShadow: 'none'
34
+ },
35
+ ':where(.react-flow__node:focus-visible) &': {
36
+ transitionDuration: '0ms',
37
+ outline: `3px solid ${outlineColor}`,
38
+ outlineOffset: rem(1.5)
39
+ }
40
+ }
41
+ })
42
+
43
+ // @ts-ignore
44
+ const cssTransparentCompound = style({
45
+ width: '100%',
46
+ height: '100%',
47
+ position: 'relative',
48
+ borderRadius: rem(8),
49
+ borderStyle: 'dashed',
50
+ borderWidth: rem(3.5),
51
+ borderColor: vars.element.fill,
52
+ boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.05) , 0 2px 2px -1px rgba(0,0,0,0.04)',
53
+ padding: rem(8),
54
+ background: `color-mix(in srgb , ${vars.element.fill}, transparent ${bgTransparency})`,
55
+ transition: 'all 200ms ease-out',
56
+ vars: {
57
+ [bgTransparency]: '90%'
58
+ },
59
+ selectors: {
60
+ [`:where(${cssContainer}[data-hovered]) &`]: {
61
+ transitionDelay: '300ms',
62
+ vars: {
63
+ [bgTransparency]: '65%'
64
+ }
65
+ },
66
+ [`:where(.react-flow__node.selected) &`]: {
67
+ transitionDelay: '50ms',
68
+ boxShadow: 'none',
69
+ borderColor: 'transparent',
70
+ vars: {
71
+ [bgTransparency]: '15%'
72
+ }
73
+ }
74
+ }
75
+ })
76
+
77
+ export const cssTitle = style({
78
+ fontFamily: vars.compound.font,
79
+ fontOpticalSizing: 'auto',
80
+ fontStyle: 'normal',
81
+ textAlign: 'left',
82
+ display: 'inline-block',
83
+ fontWeight: 600,
84
+ fontSize: '13px',
85
+ textTransform: 'uppercase',
86
+ letterSpacing: '0.2px',
87
+ lineHeight: 1,
88
+ opacity: 0.75,
89
+ color: vars.compound.titleColor,
90
+ paddingLeft: rem(12),
91
+ mixBlendMode: 'lighten',
92
+ selectors: {
93
+ [`:where([data-likec4-navigable='true']) &`]: {
94
+ paddingLeft: rem(26)
95
+ }
96
+ }
97
+ })
98
+
99
+ const indicatorKeyframes = generateIdentifier('indicator')
100
+ globalKeyframes(indicatorKeyframes, {
101
+ 'from': {
102
+ opacity: 0.6
103
+ },
104
+ 'to': {
105
+ opacity: 0.3
106
+ }
107
+ })
108
+
109
+ export const cssIndicator = style({
110
+ position: 'absolute',
111
+ top: '0',
112
+ left: '0',
113
+ width: '100%',
114
+ height: '100%',
115
+ pointerEvents: 'none',
116
+ overflow: 'visible',
117
+ visibility: 'hidden',
118
+ selectors: {
119
+ ':where(.react-flow__node.selected:not(:focus-visible)) &': {
120
+ visibility: 'visible'
121
+ }
122
+ }
123
+ })
124
+
125
+ globalStyle(`${cssIndicator} rect`, {
126
+ stroke: vars.element.loContrast,
127
+ transformOrigin: 'center center',
128
+ strokeWidth: 6,
129
+ animationDuration: '800ms',
130
+ animationName: indicatorKeyframes,
131
+ animationIterationCount: 'infinite',
132
+ animationDirection: 'alternate',
133
+ fill: 'none'
134
+ })
135
+
136
+ globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${cssIndicator} rect`, {
137
+ stroke: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
138
+ strokeWidth: 8
139
+ })
140
+
141
+ export const cssNavigateBtn = style({
142
+ position: 'absolute',
143
+ pointerEvents: 'all',
144
+ left: 3,
145
+ top: 7,
146
+ cursor: 'pointer',
147
+ color: vars.element.loContrast,
148
+ transformOrigin: '90% 70%',
149
+ opacity: 0.75,
150
+ transition: 'all 150ms ease-out',
151
+ transitionDelay: '0ms',
152
+ backgroundColor: 'var(--ai-bg)',
153
+ vars: {
154
+ '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
155
+ '--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill} 65%, ${vars.element.stroke})`,
156
+ '--ai-hover': `color-mix(in srgb , ${vars.element.fill} 50%, ${vars.element.stroke})`
157
+ },
158
+ ':hover': {
159
+ boxShadow: mantine.shadows.lg,
160
+ transform: 'scale(1.466)',
161
+ opacity: 1,
162
+ transitionDelay: '0'
163
+ },
164
+ ':active': {
165
+ transform: 'scale(1.1)',
166
+ opacity: 1,
167
+ transitionDelay: '0'
168
+ },
169
+ selectors: {
170
+ [`:where(.react-flow__node:not(.dragging) ${cssContainer}:hover) &:not(:hover)`]: {
171
+ boxShadow: mantine.shadows.lg,
172
+ transform: 'scale(1.223)',
173
+ opacity: 1,
174
+ transitionDelay: '250ms',
175
+ vars: {
176
+ '--ai-bg': 'var(--ai-bg-hover)'
177
+ }
178
+ }
179
+ }
180
+ })
181
+ globalStyle(`${cssNavigateBtn} svg.icon`, {
182
+ width: '70%',
183
+ height: '70%',
184
+ strokeWidth: 1.5
185
+ })
186
+
187
+ // globalStyle(`:where(.react-flow__node:not(.dragging)) ${cssIndicator}`, {
188
+ // visibility: 'visible',
189
+ // ":hover": {
190
+ // }
191
+ // })