@pyreon/flow 0.5.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/LICENSE +21 -0
- package/lib/analysis/index.js.html +5406 -0
- package/lib/chunk-C8JhGJ3N.js +33 -0
- package/lib/elk.bundled-B9dPTHTZ.js +88591 -0
- package/lib/elk.bundled-B9dPTHTZ.js.map +1 -0
- package/lib/index.js +2526 -0
- package/lib/index.js.map +1 -0
- package/lib/types/chunk.d.ts +2 -0
- package/lib/types/elk.bundled.d.ts +7 -0
- package/lib/types/elk.bundled.d.ts.map +1 -0
- package/lib/types/index.d.ts +2342 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +708 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/components/background.tsx +128 -0
- package/src/components/controls.tsx +158 -0
- package/src/components/flow-component.tsx +918 -0
- package/src/components/handle.tsx +42 -0
- package/src/components/minimap.tsx +136 -0
- package/src/components/node-resizer.tsx +169 -0
- package/src/components/node-toolbar.tsx +74 -0
- package/src/components/panel.tsx +33 -0
- package/src/edges.ts +369 -0
- package/src/flow.ts +1141 -0
- package/src/index.ts +83 -0
- package/src/layout.ts +152 -0
- package/src/styles.ts +115 -0
- package/src/tests/flow-advanced.test.ts +802 -0
- package/src/tests/flow.test.ts +1284 -0
- package/src/types.ts +483 -0
package/src/edges.ts
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import type { EdgePathResult, FlowNode, XYPosition } from './types'
|
|
2
|
+
import { Position } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auto-detect the best handle position based on relative node positions.
|
|
6
|
+
* If the node has configured handles, uses those. Otherwise picks the
|
|
7
|
+
* closest edge (top/right/bottom/left) based on direction to the other node.
|
|
8
|
+
*/
|
|
9
|
+
export function getSmartHandlePositions(
|
|
10
|
+
sourceNode: FlowNode,
|
|
11
|
+
targetNode: FlowNode,
|
|
12
|
+
): { sourcePosition: Position; targetPosition: Position } {
|
|
13
|
+
const sw = sourceNode.width ?? 150
|
|
14
|
+
const sh = sourceNode.height ?? 40
|
|
15
|
+
const tw = targetNode.width ?? 150
|
|
16
|
+
const th = targetNode.height ?? 40
|
|
17
|
+
|
|
18
|
+
const dx = targetNode.position.x + tw / 2 - (sourceNode.position.x + sw / 2)
|
|
19
|
+
const dy = targetNode.position.y + th / 2 - (sourceNode.position.y + sh / 2)
|
|
20
|
+
|
|
21
|
+
const sourceHandle = sourceNode.sourceHandles?.[0]
|
|
22
|
+
const targetHandle = targetNode.targetHandles?.[0]
|
|
23
|
+
|
|
24
|
+
const sourcePosition = sourceHandle
|
|
25
|
+
? sourceHandle.position
|
|
26
|
+
: Math.abs(dx) > Math.abs(dy)
|
|
27
|
+
? dx > 0
|
|
28
|
+
? Position.Right
|
|
29
|
+
: Position.Left
|
|
30
|
+
: dy > 0
|
|
31
|
+
? Position.Bottom
|
|
32
|
+
: Position.Top
|
|
33
|
+
|
|
34
|
+
const targetPosition = targetHandle
|
|
35
|
+
? targetHandle.position
|
|
36
|
+
: Math.abs(dx) > Math.abs(dy)
|
|
37
|
+
? dx > 0
|
|
38
|
+
? Position.Left
|
|
39
|
+
: Position.Right
|
|
40
|
+
: dy > 0
|
|
41
|
+
? Position.Top
|
|
42
|
+
: Position.Bottom
|
|
43
|
+
|
|
44
|
+
return { sourcePosition, targetPosition }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the center point between source and target positions.
|
|
49
|
+
*/
|
|
50
|
+
function getCenter(source: XYPosition, target: XYPosition): XYPosition {
|
|
51
|
+
return {
|
|
52
|
+
x: (source.x + target.x) / 2,
|
|
53
|
+
y: (source.y + target.y) / 2,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the handle position offset for a given position (top/right/bottom/left).
|
|
59
|
+
*/
|
|
60
|
+
export function getHandlePosition(
|
|
61
|
+
position: Position,
|
|
62
|
+
nodeX: number,
|
|
63
|
+
nodeY: number,
|
|
64
|
+
nodeWidth: number,
|
|
65
|
+
nodeHeight: number,
|
|
66
|
+
_handleId?: string,
|
|
67
|
+
): XYPosition {
|
|
68
|
+
switch (position) {
|
|
69
|
+
case Position.Top:
|
|
70
|
+
return { x: nodeX + nodeWidth / 2, y: nodeY }
|
|
71
|
+
case Position.Right:
|
|
72
|
+
return { x: nodeX + nodeWidth, y: nodeY + nodeHeight / 2 }
|
|
73
|
+
case Position.Bottom:
|
|
74
|
+
return { x: nodeX + nodeWidth / 2, y: nodeY + nodeHeight }
|
|
75
|
+
case Position.Left:
|
|
76
|
+
return { x: nodeX, y: nodeY + nodeHeight / 2 }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Calculate a cubic bezier edge path between two points.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const { path, labelX, labelY } = getBezierPath({
|
|
86
|
+
* sourceX: 0, sourceY: 0, sourcePosition: Position.Right,
|
|
87
|
+
* targetX: 200, targetY: 100, targetPosition: Position.Left,
|
|
88
|
+
* })
|
|
89
|
+
* // path = "M0,0 C100,0 100,100 200,100"
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function getBezierPath(params: {
|
|
93
|
+
sourceX: number
|
|
94
|
+
sourceY: number
|
|
95
|
+
sourcePosition?: Position
|
|
96
|
+
targetX: number
|
|
97
|
+
targetY: number
|
|
98
|
+
targetPosition?: Position
|
|
99
|
+
curvature?: number
|
|
100
|
+
}): EdgePathResult {
|
|
101
|
+
const {
|
|
102
|
+
sourceX,
|
|
103
|
+
sourceY,
|
|
104
|
+
sourcePosition = Position.Bottom,
|
|
105
|
+
targetX,
|
|
106
|
+
targetY,
|
|
107
|
+
targetPosition = Position.Top,
|
|
108
|
+
curvature = 0.25,
|
|
109
|
+
} = params
|
|
110
|
+
|
|
111
|
+
const distX = Math.abs(targetX - sourceX)
|
|
112
|
+
const distY = Math.abs(targetY - sourceY)
|
|
113
|
+
const dist = Math.sqrt(distX * distX + distY * distY)
|
|
114
|
+
const offset = dist * curvature
|
|
115
|
+
|
|
116
|
+
let sourceControlX = sourceX
|
|
117
|
+
let sourceControlY = sourceY
|
|
118
|
+
let targetControlX = targetX
|
|
119
|
+
let targetControlY = targetY
|
|
120
|
+
|
|
121
|
+
switch (sourcePosition) {
|
|
122
|
+
case Position.Top:
|
|
123
|
+
sourceControlY = sourceY - offset
|
|
124
|
+
break
|
|
125
|
+
case Position.Bottom:
|
|
126
|
+
sourceControlY = sourceY + offset
|
|
127
|
+
break
|
|
128
|
+
case Position.Left:
|
|
129
|
+
sourceControlX = sourceX - offset
|
|
130
|
+
break
|
|
131
|
+
case Position.Right:
|
|
132
|
+
sourceControlX = sourceX + offset
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
switch (targetPosition) {
|
|
137
|
+
case Position.Top:
|
|
138
|
+
targetControlY = targetY - offset
|
|
139
|
+
break
|
|
140
|
+
case Position.Bottom:
|
|
141
|
+
targetControlY = targetY + offset
|
|
142
|
+
break
|
|
143
|
+
case Position.Left:
|
|
144
|
+
targetControlX = targetX - offset
|
|
145
|
+
break
|
|
146
|
+
case Position.Right:
|
|
147
|
+
targetControlX = targetX + offset
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const center = getCenter(
|
|
152
|
+
{ x: sourceX, y: sourceY },
|
|
153
|
+
{ x: targetX, y: targetY },
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
path: `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
|
|
158
|
+
labelX: center.x,
|
|
159
|
+
labelY: center.y,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Calculate a smoothstep edge path — horizontal/vertical segments with rounded corners.
|
|
165
|
+
*/
|
|
166
|
+
export function getSmoothStepPath(params: {
|
|
167
|
+
sourceX: number
|
|
168
|
+
sourceY: number
|
|
169
|
+
sourcePosition?: Position
|
|
170
|
+
targetX: number
|
|
171
|
+
targetY: number
|
|
172
|
+
targetPosition?: Position
|
|
173
|
+
borderRadius?: number
|
|
174
|
+
offset?: number
|
|
175
|
+
}): EdgePathResult {
|
|
176
|
+
const {
|
|
177
|
+
sourceX,
|
|
178
|
+
sourceY,
|
|
179
|
+
sourcePosition = Position.Bottom,
|
|
180
|
+
targetX,
|
|
181
|
+
targetY,
|
|
182
|
+
targetPosition = Position.Top,
|
|
183
|
+
borderRadius = 5,
|
|
184
|
+
offset = 20,
|
|
185
|
+
} = params
|
|
186
|
+
|
|
187
|
+
const isHorizontalSource =
|
|
188
|
+
sourcePosition === Position.Left || sourcePosition === Position.Right
|
|
189
|
+
const isHorizontalTarget =
|
|
190
|
+
targetPosition === Position.Left || targetPosition === Position.Right
|
|
191
|
+
|
|
192
|
+
// Calculate offset points
|
|
193
|
+
const sourceOffsetX =
|
|
194
|
+
sourcePosition === Position.Right
|
|
195
|
+
? offset
|
|
196
|
+
: sourcePosition === Position.Left
|
|
197
|
+
? -offset
|
|
198
|
+
: 0
|
|
199
|
+
const sourceOffsetY =
|
|
200
|
+
sourcePosition === Position.Bottom
|
|
201
|
+
? offset
|
|
202
|
+
: sourcePosition === Position.Top
|
|
203
|
+
? -offset
|
|
204
|
+
: 0
|
|
205
|
+
const targetOffsetX =
|
|
206
|
+
targetPosition === Position.Right
|
|
207
|
+
? offset
|
|
208
|
+
: targetPosition === Position.Left
|
|
209
|
+
? -offset
|
|
210
|
+
: 0
|
|
211
|
+
const targetOffsetY =
|
|
212
|
+
targetPosition === Position.Bottom
|
|
213
|
+
? offset
|
|
214
|
+
: targetPosition === Position.Top
|
|
215
|
+
? -offset
|
|
216
|
+
: 0
|
|
217
|
+
|
|
218
|
+
const sX = sourceX + sourceOffsetX
|
|
219
|
+
const sY = sourceY + sourceOffsetY
|
|
220
|
+
const tX = targetX + targetOffsetX
|
|
221
|
+
const tY = targetY + targetOffsetY
|
|
222
|
+
|
|
223
|
+
const center = getCenter(
|
|
224
|
+
{ x: sourceX, y: sourceY },
|
|
225
|
+
{ x: targetX, y: targetY },
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// Simple smoothstep: source → midpoint → target with rounded corners
|
|
229
|
+
const midX = (sX + tX) / 2
|
|
230
|
+
const midY = (sY + tY) / 2
|
|
231
|
+
const r = borderRadius
|
|
232
|
+
|
|
233
|
+
let path: string
|
|
234
|
+
|
|
235
|
+
if (isHorizontalSource && !isHorizontalTarget) {
|
|
236
|
+
// Horizontal source → vertical target
|
|
237
|
+
const cornerY = tY
|
|
238
|
+
path = `M${sourceX},${sourceY} L${sX},${sY} L${sX},${cornerY > sY ? cornerY - r : cornerY + r} Q${sX},${cornerY} ${sX + (tX > sX ? r : -r)},${cornerY} L${tX},${cornerY} L${targetX},${targetY}`
|
|
239
|
+
} else if (!isHorizontalSource && isHorizontalTarget) {
|
|
240
|
+
// Vertical source → horizontal target
|
|
241
|
+
const cornerX = tX
|
|
242
|
+
path = `M${sourceX},${sourceY} L${sX},${sY} L${cornerX > sX ? cornerX - r : cornerX + r},${sY} Q${cornerX},${sY} ${cornerX},${sY + (tY > sY ? r : -r)} L${cornerX},${tY} L${targetX},${targetY}`
|
|
243
|
+
} else if (isHorizontalSource && isHorizontalTarget) {
|
|
244
|
+
// Both horizontal — go through middle Y
|
|
245
|
+
path = `M${sourceX},${sourceY} L${sX},${sourceY} L${midX},${sourceY} Q${midX},${sourceY} ${midX},${midY} L${midX},${targetY} L${tX},${targetY} L${targetX},${targetY}`
|
|
246
|
+
} else {
|
|
247
|
+
// Both vertical — go through middle X
|
|
248
|
+
path = `M${sourceX},${sourceY} L${sourceX},${sY} L${sourceX},${midY} Q${sourceX},${midY} ${midX},${midY} L${targetX},${midY} L${targetX},${tY} L${targetX},${targetY}`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { path, labelX: center.x, labelY: center.y }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Calculate a straight edge path — direct line between two points.
|
|
256
|
+
*/
|
|
257
|
+
export function getStraightPath(params: {
|
|
258
|
+
sourceX: number
|
|
259
|
+
sourceY: number
|
|
260
|
+
targetX: number
|
|
261
|
+
targetY: number
|
|
262
|
+
}): EdgePathResult {
|
|
263
|
+
const { sourceX, sourceY, targetX, targetY } = params
|
|
264
|
+
const center = getCenter(
|
|
265
|
+
{ x: sourceX, y: sourceY },
|
|
266
|
+
{ x: targetX, y: targetY },
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
path: `M${sourceX},${sourceY} L${targetX},${targetY}`,
|
|
271
|
+
labelX: center.x,
|
|
272
|
+
labelY: center.y,
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Calculate a step edge path — right-angle segments with no rounding.
|
|
278
|
+
*/
|
|
279
|
+
export function getStepPath(params: {
|
|
280
|
+
sourceX: number
|
|
281
|
+
sourceY: number
|
|
282
|
+
sourcePosition?: Position
|
|
283
|
+
targetX: number
|
|
284
|
+
targetY: number
|
|
285
|
+
targetPosition?: Position
|
|
286
|
+
}): EdgePathResult {
|
|
287
|
+
return getSmoothStepPath({ ...params, borderRadius: 0 })
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Calculate an edge path that passes through waypoints.
|
|
292
|
+
* Uses line segments with optional smoothing.
|
|
293
|
+
*/
|
|
294
|
+
export function getWaypointPath(params: {
|
|
295
|
+
sourceX: number
|
|
296
|
+
sourceY: number
|
|
297
|
+
targetX: number
|
|
298
|
+
targetY: number
|
|
299
|
+
waypoints: XYPosition[]
|
|
300
|
+
}): EdgePathResult {
|
|
301
|
+
const { sourceX, sourceY, targetX, targetY, waypoints } = params
|
|
302
|
+
|
|
303
|
+
if (waypoints.length === 0) {
|
|
304
|
+
return getStraightPath({ sourceX, sourceY, targetX, targetY })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const allPoints = [
|
|
308
|
+
{ x: sourceX, y: sourceY },
|
|
309
|
+
...waypoints,
|
|
310
|
+
{ x: targetX, y: targetY },
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
const segments = allPoints.map((p) => `${p.x},${p.y}`)
|
|
314
|
+
const path = `M${segments.join(' L')}`
|
|
315
|
+
|
|
316
|
+
// Label at the middle waypoint
|
|
317
|
+
const midIdx = Math.floor(waypoints.length / 2)
|
|
318
|
+
const midPoint = waypoints[midIdx] ?? {
|
|
319
|
+
x: (sourceX + targetX) / 2,
|
|
320
|
+
y: (sourceY + targetY) / 2,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { path, labelX: midPoint.x, labelY: midPoint.y }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get the edge path for a given edge type.
|
|
328
|
+
*/
|
|
329
|
+
export function getEdgePath(
|
|
330
|
+
type: string,
|
|
331
|
+
sourceX: number,
|
|
332
|
+
sourceY: number,
|
|
333
|
+
sourcePosition: Position,
|
|
334
|
+
targetX: number,
|
|
335
|
+
targetY: number,
|
|
336
|
+
targetPosition: Position,
|
|
337
|
+
): EdgePathResult {
|
|
338
|
+
switch (type) {
|
|
339
|
+
case 'smoothstep':
|
|
340
|
+
return getSmoothStepPath({
|
|
341
|
+
sourceX,
|
|
342
|
+
sourceY,
|
|
343
|
+
sourcePosition,
|
|
344
|
+
targetX,
|
|
345
|
+
targetY,
|
|
346
|
+
targetPosition,
|
|
347
|
+
})
|
|
348
|
+
case 'straight':
|
|
349
|
+
return getStraightPath({ sourceX, sourceY, targetX, targetY })
|
|
350
|
+
case 'step':
|
|
351
|
+
return getStepPath({
|
|
352
|
+
sourceX,
|
|
353
|
+
sourceY,
|
|
354
|
+
sourcePosition,
|
|
355
|
+
targetX,
|
|
356
|
+
targetY,
|
|
357
|
+
targetPosition,
|
|
358
|
+
})
|
|
359
|
+
default:
|
|
360
|
+
return getBezierPath({
|
|
361
|
+
sourceX,
|
|
362
|
+
sourceY,
|
|
363
|
+
sourcePosition,
|
|
364
|
+
targetX,
|
|
365
|
+
targetY,
|
|
366
|
+
targetPosition,
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
}
|