@pyreon/flow 0.10.0 → 0.11.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.
- package/lib/analysis/index.js.html +1 -1
- package/lib/{elk.bundled-B9dPTHTZ.js → elk.bundled-wgxWJMYd.js} +2 -2
- package/lib/elk.bundled-wgxWJMYd.js.map +1 -0
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +13 -13
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +14 -7
- package/src/components/background.tsx +9 -14
- package/src/components/controls.tsx +11 -18
- package/src/components/flow-component.tsx +57 -109
- package/src/components/handle.tsx +8 -8
- package/src/components/minimap.tsx +10 -27
- package/src/components/node-resizer.tsx +25 -25
- package/src/components/node-toolbar.tsx +12 -21
- package/src/components/panel.tsx +9 -9
- package/src/edges.ts +16 -47
- package/src/flow.ts +32 -88
- package/src/index.ts +17 -17
- package/src/layout.ts +23 -31
- package/src/tests/flow-advanced.test.ts +191 -200
- package/src/tests/flow.test.ts +418 -468
- package/src/types.ts +28 -42
- package/lib/elk.bundled-B9dPTHTZ.js.map +0 -1
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import { signal } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
getSmartHandlePositions,
|
|
7
|
-
getWaypointPath,
|
|
8
|
-
} from '../edges'
|
|
9
|
-
import type {
|
|
10
|
-
Connection,
|
|
11
|
-
FlowInstance,
|
|
12
|
-
FlowNode,
|
|
13
|
-
NodeComponentProps,
|
|
14
|
-
} from '../types'
|
|
15
|
-
import { Position } from '../types'
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import { signal } from "@pyreon/reactivity"
|
|
3
|
+
import { getEdgePath, getHandlePosition, getSmartHandlePositions, getWaypointPath } from "../edges"
|
|
4
|
+
import type { Connection, FlowInstance, FlowNode, NodeComponentProps } from "../types"
|
|
5
|
+
import { Position } from "../types"
|
|
16
6
|
|
|
17
7
|
// ─── Node type registry ──────────────────────────────────────────────────────
|
|
18
8
|
|
|
19
|
-
type NodeTypeMap = Record<
|
|
20
|
-
string,
|
|
21
|
-
(props: NodeComponentProps<any>) => VNodeChild
|
|
22
|
-
>
|
|
9
|
+
type NodeTypeMap = Record<string, (props: NodeComponentProps<any>) => VNodeChild>
|
|
23
10
|
|
|
24
11
|
/**
|
|
25
12
|
* Default node renderer — simple labeled box.
|
|
26
13
|
*/
|
|
27
14
|
function DefaultNode(props: NodeComponentProps) {
|
|
28
|
-
const borderColor = props.selected ?
|
|
29
|
-
const cursor = props.dragging ?
|
|
15
|
+
const borderColor = props.selected ? "#3b82f6" : "#ddd"
|
|
16
|
+
const cursor = props.dragging ? "grabbing" : "grab"
|
|
30
17
|
return (
|
|
31
18
|
<div
|
|
32
19
|
style={`padding: 8px 16px; background: white; border: 2px solid ${borderColor}; border-radius: 6px; font-size: 13px; min-width: 80px; text-align: center; cursor: ${cursor}; user-select: none;`}
|
|
@@ -51,8 +38,8 @@ interface ConnectionState {
|
|
|
51
38
|
|
|
52
39
|
const emptyConnection: ConnectionState = {
|
|
53
40
|
active: false,
|
|
54
|
-
sourceNodeId:
|
|
55
|
-
sourceHandleId:
|
|
41
|
+
sourceNodeId: "",
|
|
42
|
+
sourceHandleId: "",
|
|
56
43
|
sourcePosition: Position.Right,
|
|
57
44
|
sourceX: 0,
|
|
58
45
|
sourceY: 0,
|
|
@@ -91,7 +78,7 @@ interface DragState {
|
|
|
91
78
|
|
|
92
79
|
const emptyDrag: DragState = {
|
|
93
80
|
active: false,
|
|
94
|
-
nodeId:
|
|
81
|
+
nodeId: "",
|
|
95
82
|
startX: 0,
|
|
96
83
|
startY: 0,
|
|
97
84
|
startPositions: new Map(),
|
|
@@ -123,11 +110,11 @@ function EdgeLayer(props: {
|
|
|
123
110
|
<marker
|
|
124
111
|
id="flow-arrowhead"
|
|
125
112
|
{...{
|
|
126
|
-
markerWidth:
|
|
127
|
-
markerHeight:
|
|
128
|
-
refX:
|
|
129
|
-
refY:
|
|
130
|
-
orient:
|
|
113
|
+
markerWidth: "10",
|
|
114
|
+
markerHeight: "7",
|
|
115
|
+
refX: "10",
|
|
116
|
+
refY: "3.5",
|
|
117
|
+
orient: "auto",
|
|
131
118
|
}}
|
|
132
119
|
>
|
|
133
120
|
<polygon points="0 0, 10 3.5, 0 7" fill="#999" />
|
|
@@ -143,10 +130,7 @@ function EdgeLayer(props: {
|
|
|
143
130
|
const targetW = targetNode.width ?? 150
|
|
144
131
|
const targetH = targetNode.height ?? 40
|
|
145
132
|
|
|
146
|
-
const { sourcePosition, targetPosition } = getSmartHandlePositions(
|
|
147
|
-
sourceNode,
|
|
148
|
-
targetNode,
|
|
149
|
-
)
|
|
133
|
+
const { sourcePosition, targetPosition } = getSmartHandlePositions(sourceNode, targetNode)
|
|
150
134
|
|
|
151
135
|
const sourcePos = getHandlePosition(
|
|
152
136
|
sourcePosition,
|
|
@@ -172,7 +156,7 @@ function EdgeLayer(props: {
|
|
|
172
156
|
waypoints: edge.waypoints,
|
|
173
157
|
})
|
|
174
158
|
: getEdgePath(
|
|
175
|
-
edge.type ??
|
|
159
|
+
edge.type ?? "bezier",
|
|
176
160
|
sourcePos.x,
|
|
177
161
|
sourcePos.y,
|
|
178
162
|
sourcePosition,
|
|
@@ -188,10 +172,7 @@ function EdgeLayer(props: {
|
|
|
188
172
|
const CustomEdge = edge.type && edgeTypes?.[edge.type]
|
|
189
173
|
if (CustomEdge) {
|
|
190
174
|
return (
|
|
191
|
-
<g
|
|
192
|
-
key={edge.id}
|
|
193
|
-
onClick={() => edge.id && instance.selectEdge(edge.id)}
|
|
194
|
-
>
|
|
175
|
+
<g key={edge.id} onClick={() => edge.id && instance.selectEdge(edge.id)}>
|
|
195
176
|
<CustomEdge
|
|
196
177
|
edge={edge}
|
|
197
178
|
sourceX={sourcePos.x}
|
|
@@ -209,11 +190,11 @@ function EdgeLayer(props: {
|
|
|
209
190
|
<path
|
|
210
191
|
d={path}
|
|
211
192
|
fill="none"
|
|
212
|
-
stroke={isSelected ?
|
|
213
|
-
stroke-width={isSelected ?
|
|
193
|
+
stroke={isSelected ? "#3b82f6" : "#999"}
|
|
194
|
+
stroke-width={isSelected ? "2" : "1.5"}
|
|
214
195
|
marker-end="url(#flow-arrowhead)"
|
|
215
|
-
class={edge.animated ?
|
|
216
|
-
style={`pointer-events: stroke; cursor: pointer; ${edge.style ??
|
|
196
|
+
class={edge.animated ? "pyreon-flow-edge-animated" : ""}
|
|
197
|
+
style={`pointer-events: stroke; cursor: pointer; ${edge.style ?? ""}`}
|
|
217
198
|
onClick={() => {
|
|
218
199
|
if (edge.id) instance.selectEdge(edge.id)
|
|
219
200
|
instance._emit.edgeClick(edge)
|
|
@@ -237,7 +218,7 @@ function EdgeLayer(props: {
|
|
|
237
218
|
<path
|
|
238
219
|
d={
|
|
239
220
|
getEdgePath(
|
|
240
|
-
|
|
221
|
+
"bezier",
|
|
241
222
|
conn.sourceX,
|
|
242
223
|
conn.sourceY,
|
|
243
224
|
conn.sourcePosition,
|
|
@@ -272,13 +253,7 @@ function NodeLayer(props: {
|
|
|
272
253
|
position: Position,
|
|
273
254
|
) => void
|
|
274
255
|
}): VNodeChild {
|
|
275
|
-
const {
|
|
276
|
-
instance,
|
|
277
|
-
nodeTypes,
|
|
278
|
-
draggingNodeId,
|
|
279
|
-
onNodePointerDown,
|
|
280
|
-
onHandlePointerDown,
|
|
281
|
-
} = props
|
|
256
|
+
const { instance, nodeTypes, draggingNodeId, onNodePointerDown, onHandlePointerDown } = props
|
|
282
257
|
|
|
283
258
|
return () => {
|
|
284
259
|
const nodes = instance.nodes()
|
|
@@ -290,14 +265,13 @@ function NodeLayer(props: {
|
|
|
290
265
|
{nodes.map((node) => {
|
|
291
266
|
const isSelected = selectedIds.includes(node.id)
|
|
292
267
|
const isDragging = dragId === node.id
|
|
293
|
-
const NodeComponent =
|
|
294
|
-
(node.type && nodeTypes[node.type]) || nodeTypes.default!
|
|
268
|
+
const NodeComponent = (node.type && nodeTypes[node.type]) || nodeTypes.default!
|
|
295
269
|
|
|
296
270
|
return (
|
|
297
271
|
<div
|
|
298
272
|
key={node.id}
|
|
299
|
-
class={`pyreon-flow-node ${node.class ??
|
|
300
|
-
style={`position: absolute; transform: translate(${node.position.x}px, ${node.position.y}px); z-index: ${isDragging ? 1000 : isSelected ? 100 : 0}; ${node.style ??
|
|
273
|
+
class={`pyreon-flow-node ${node.class ?? ""} ${isSelected ? "selected" : ""} ${isDragging ? "dragging" : ""}`}
|
|
274
|
+
style={`position: absolute; transform: translate(${node.position.x}px, ${node.position.y}px); z-index: ${isDragging ? 1000 : isSelected ? 100 : 0}; ${node.style ?? ""}`}
|
|
301
275
|
data-nodeid={node.id}
|
|
302
276
|
onClick={(e: MouseEvent) => {
|
|
303
277
|
e.stopPropagation()
|
|
@@ -311,22 +285,17 @@ function NodeLayer(props: {
|
|
|
311
285
|
onPointerDown={(e: PointerEvent) => {
|
|
312
286
|
// Check if clicking a handle
|
|
313
287
|
const target = e.target as HTMLElement
|
|
314
|
-
const handle = target.closest(
|
|
288
|
+
const handle = target.closest(".pyreon-flow-handle")
|
|
315
289
|
if (handle) {
|
|
316
|
-
const hType =
|
|
317
|
-
|
|
318
|
-
const hId = handle.getAttribute('data-handleid') ?? 'source'
|
|
290
|
+
const hType = handle.getAttribute("data-handletype") ?? "source"
|
|
291
|
+
const hId = handle.getAttribute("data-handleid") ?? "source"
|
|
319
292
|
const hPos =
|
|
320
|
-
(handle.getAttribute(
|
|
321
|
-
Position.Right
|
|
293
|
+
(handle.getAttribute("data-handleposition") as Position) ?? Position.Right
|
|
322
294
|
onHandlePointerDown(e, node.id, hType, hId, hPos)
|
|
323
295
|
return
|
|
324
296
|
}
|
|
325
297
|
// Otherwise start dragging node
|
|
326
|
-
if (
|
|
327
|
-
node.draggable !== false &&
|
|
328
|
-
instance.config.nodesDraggable !== false
|
|
329
|
-
) {
|
|
298
|
+
if (node.draggable !== false && instance.config.nodesDraggable !== false) {
|
|
330
299
|
onNodePointerDown(e, node)
|
|
331
300
|
}
|
|
332
301
|
}}
|
|
@@ -350,7 +319,7 @@ function NodeLayer(props: {
|
|
|
350
319
|
type EdgeTypeMap = Record<
|
|
351
320
|
string,
|
|
352
321
|
(props: {
|
|
353
|
-
edge: import(
|
|
322
|
+
edge: import("../types").FlowEdge
|
|
354
323
|
sourceX: number
|
|
355
324
|
sourceY: number
|
|
356
325
|
targetX: number
|
|
@@ -409,7 +378,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
409
378
|
y: null,
|
|
410
379
|
})
|
|
411
380
|
|
|
412
|
-
const draggingNodeId = () => (dragState().active ? dragState().nodeId :
|
|
381
|
+
const draggingNodeId = () => (dragState().active ? dragState().nodeId : "")
|
|
413
382
|
|
|
414
383
|
// ── Node dragging ──────────────────────────────────────────────────────
|
|
415
384
|
|
|
@@ -447,9 +416,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
447
416
|
|
|
448
417
|
instance._emit.nodeDragStart(node)
|
|
449
418
|
|
|
450
|
-
const container = (e.currentTarget as HTMLElement).closest(
|
|
451
|
-
'.pyreon-flow',
|
|
452
|
-
) as HTMLElement
|
|
419
|
+
const container = (e.currentTarget as HTMLElement).closest(".pyreon-flow") as HTMLElement
|
|
453
420
|
if (container) container.setPointerCapture(e.pointerId)
|
|
454
421
|
}
|
|
455
422
|
|
|
@@ -470,13 +437,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
470
437
|
|
|
471
438
|
const w = node.width ?? 150
|
|
472
439
|
const h = node.height ?? 40
|
|
473
|
-
const handlePos = getHandlePosition(
|
|
474
|
-
position,
|
|
475
|
-
node.position.x,
|
|
476
|
-
node.position.y,
|
|
477
|
-
w,
|
|
478
|
-
h,
|
|
479
|
-
)
|
|
440
|
+
const handlePos = getHandlePosition(position, node.position.x, node.position.y, w, h)
|
|
480
441
|
|
|
481
442
|
connectionState.set({
|
|
482
443
|
active: true,
|
|
@@ -489,9 +450,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
489
450
|
currentY: handlePos.y,
|
|
490
451
|
})
|
|
491
452
|
|
|
492
|
-
const container = (e.target as HTMLElement).closest(
|
|
493
|
-
'.pyreon-flow',
|
|
494
|
-
) as HTMLElement
|
|
453
|
+
const container = (e.target as HTMLElement).closest(".pyreon-flow") as HTMLElement
|
|
495
454
|
if (container) container.setPointerCapture(e.pointerId)
|
|
496
455
|
}
|
|
497
456
|
|
|
@@ -503,10 +462,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
503
462
|
|
|
504
463
|
const delta = -e.deltaY * 0.001
|
|
505
464
|
const newZoom = Math.min(
|
|
506
|
-
Math.max(
|
|
507
|
-
instance.viewport.peek().zoom * (1 + delta),
|
|
508
|
-
instance.config.minZoom ?? 0.1,
|
|
509
|
-
),
|
|
465
|
+
Math.max(instance.viewport.peek().zoom * (1 + delta), instance.config.minZoom ?? 0.1),
|
|
510
466
|
instance.config.maxZoom ?? 4,
|
|
511
467
|
)
|
|
512
468
|
|
|
@@ -535,8 +491,8 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
535
491
|
if (instance.config.pannable === false) return
|
|
536
492
|
|
|
537
493
|
const target = e.target as HTMLElement
|
|
538
|
-
if (target.closest(
|
|
539
|
-
if (target.closest(
|
|
494
|
+
if (target.closest(".pyreon-flow-node")) return
|
|
495
|
+
if (target.closest(".pyreon-flow-handle")) return
|
|
540
496
|
|
|
541
497
|
// Shift+drag on empty space → selection box
|
|
542
498
|
if (e.shiftKey && instance.config.multiSelect !== false) {
|
|
@@ -681,11 +637,10 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
681
637
|
if (conn.active) {
|
|
682
638
|
// Check if we released over a handle target
|
|
683
639
|
const target = e.target as HTMLElement
|
|
684
|
-
const handle = target.closest(
|
|
640
|
+
const handle = target.closest(".pyreon-flow-handle")
|
|
685
641
|
if (handle) {
|
|
686
|
-
const targetNodeId =
|
|
687
|
-
|
|
688
|
-
const targetHandleId = handle.getAttribute('data-handleid') ?? 'target'
|
|
642
|
+
const targetNodeId = handle.closest(".pyreon-flow-node")?.getAttribute("data-nodeid") ?? ""
|
|
643
|
+
const targetHandleId = handle.getAttribute("data-handleid") ?? "target"
|
|
689
644
|
|
|
690
645
|
if (targetNodeId && targetNodeId !== conn.sourceNodeId) {
|
|
691
646
|
const connection: Connection = {
|
|
@@ -699,12 +654,8 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
699
654
|
instance.addEdge({
|
|
700
655
|
source: connection.source,
|
|
701
656
|
target: connection.target,
|
|
702
|
-
...(connection.sourceHandle != null
|
|
703
|
-
|
|
704
|
-
: {}),
|
|
705
|
-
...(connection.targetHandle != null
|
|
706
|
-
? { targetHandle: connection.targetHandle }
|
|
707
|
-
: {}),
|
|
657
|
+
...(connection.sourceHandle != null ? { sourceHandle: connection.sourceHandle } : {}),
|
|
658
|
+
...(connection.targetHandle != null ? { targetHandle: connection.targetHandle } : {}),
|
|
708
659
|
})
|
|
709
660
|
}
|
|
710
661
|
}
|
|
@@ -719,31 +670,31 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
719
670
|
// ── Keyboard ───────────────────────────────────────────────────────────
|
|
720
671
|
|
|
721
672
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
722
|
-
if (e.key ===
|
|
673
|
+
if (e.key === "Delete" || e.key === "Backspace") {
|
|
723
674
|
const target = e.target as HTMLElement
|
|
724
|
-
if (target.tagName ===
|
|
675
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return
|
|
725
676
|
instance.pushHistory()
|
|
726
677
|
instance.deleteSelected()
|
|
727
678
|
}
|
|
728
|
-
if (e.key ===
|
|
679
|
+
if (e.key === "Escape") {
|
|
729
680
|
instance.clearSelection()
|
|
730
681
|
connectionState.set({ ...emptyConnection })
|
|
731
682
|
}
|
|
732
|
-
if (e.key ===
|
|
683
|
+
if (e.key === "a" && (e.metaKey || e.ctrlKey)) {
|
|
733
684
|
e.preventDefault()
|
|
734
685
|
instance.selectAll()
|
|
735
686
|
}
|
|
736
|
-
if (e.key ===
|
|
687
|
+
if (e.key === "c" && (e.metaKey || e.ctrlKey)) {
|
|
737
688
|
instance.copySelected()
|
|
738
689
|
}
|
|
739
|
-
if (e.key ===
|
|
690
|
+
if (e.key === "v" && (e.metaKey || e.ctrlKey)) {
|
|
740
691
|
instance.paste()
|
|
741
692
|
}
|
|
742
|
-
if (e.key ===
|
|
693
|
+
if (e.key === "z" && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
|
743
694
|
e.preventDefault()
|
|
744
695
|
instance.undo()
|
|
745
696
|
}
|
|
746
|
-
if (e.key ===
|
|
697
|
+
if (e.key === "z" && (e.metaKey || e.ctrlKey) && e.shiftKey) {
|
|
747
698
|
e.preventDefault()
|
|
748
699
|
instance.redo()
|
|
749
700
|
}
|
|
@@ -759,10 +710,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
759
710
|
e.preventDefault()
|
|
760
711
|
const t1 = e.touches[0]!
|
|
761
712
|
const t2 = e.touches[1]!
|
|
762
|
-
lastTouchDist = Math.hypot(
|
|
763
|
-
t2.clientX - t1.clientX,
|
|
764
|
-
t2.clientY - t1.clientY,
|
|
765
|
-
)
|
|
713
|
+
lastTouchDist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
|
|
766
714
|
lastTouchCenter = {
|
|
767
715
|
x: (t1.clientX + t2.clientX) / 2,
|
|
768
716
|
y: (t1.clientY + t2.clientY) / 2,
|
|
@@ -832,12 +780,12 @@ export function Flow(props: FlowComponentProps): VNodeChild {
|
|
|
832
780
|
resizeObserver.observe(el)
|
|
833
781
|
}
|
|
834
782
|
|
|
835
|
-
const containerStyle = `position: relative; width: 100%; height: 100%; overflow: hidden; outline: none; touch-action: none; ${props.style ??
|
|
783
|
+
const containerStyle = `position: relative; width: 100%; height: 100%; overflow: hidden; outline: none; touch-action: none; ${props.style ?? ""}`
|
|
836
784
|
|
|
837
785
|
return (
|
|
838
786
|
<div
|
|
839
787
|
ref={containerRef}
|
|
840
|
-
class={`pyreon-flow ${props.class ??
|
|
788
|
+
class={`pyreon-flow ${props.class ?? ""}`}
|
|
841
789
|
style={containerStyle}
|
|
842
790
|
tabIndex={0}
|
|
843
791
|
onWheel={handleWheel}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import type { HandleProps } from
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import type { HandleProps } from "../types"
|
|
3
3
|
|
|
4
4
|
const positionOffset: Record<string, string> = {
|
|
5
|
-
top:
|
|
6
|
-
right:
|
|
7
|
-
bottom:
|
|
8
|
-
left:
|
|
5
|
+
top: "top: -4px; left: 50%; transform: translateX(-50%);",
|
|
6
|
+
right: "right: -4px; top: 50%; transform: translateY(-50%);",
|
|
7
|
+
bottom: "bottom: -4px; left: 50%; transform: translateX(-50%);",
|
|
8
|
+
left: "left: -4px; top: 50%; transform: translateY(-50%);",
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -26,13 +26,13 @@ const positionOffset: Record<string, string> = {
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export function Handle(props: HandleProps): VNodeChild {
|
|
29
|
-
const { type, position, id, style =
|
|
29
|
+
const { type, position, id, style = "" } = props
|
|
30
30
|
const posStyle = positionOffset[position] ?? positionOffset.bottom
|
|
31
31
|
const baseStyle = `position: absolute; ${posStyle} width: 8px; height: 8px; background: #555; border: 2px solid white; border-radius: 50%; cursor: crosshair; z-index: 1; ${style}`
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<div
|
|
35
|
-
class={`pyreon-flow-handle pyreon-flow-handle-${type} ${props.class ??
|
|
35
|
+
class={`pyreon-flow-handle pyreon-flow-handle-${type} ${props.class ?? ""}`}
|
|
36
36
|
style={baseStyle}
|
|
37
37
|
data-handletype={type}
|
|
38
38
|
data-handleid={id ?? type}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import type { FlowInstance, MiniMapProps } from
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import type { FlowInstance, MiniMapProps } from "../types"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Miniature overview of the flow diagram showing all nodes
|
|
@@ -12,14 +12,12 @@ import type { FlowInstance, MiniMapProps } from '../types'
|
|
|
12
12
|
* </Flow>
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
export function MiniMap(
|
|
16
|
-
props: MiniMapProps & { instance?: FlowInstance },
|
|
17
|
-
): VNodeChild {
|
|
15
|
+
export function MiniMap(props: MiniMapProps & { instance?: FlowInstance }): VNodeChild {
|
|
18
16
|
const {
|
|
19
17
|
width = 200,
|
|
20
18
|
height = 150,
|
|
21
|
-
nodeColor =
|
|
22
|
-
maskColor =
|
|
19
|
+
nodeColor = "#e2e8f0",
|
|
20
|
+
maskColor = "rgba(0, 0, 0, 0.08)",
|
|
23
21
|
instance,
|
|
24
22
|
} = props
|
|
25
23
|
|
|
@@ -29,8 +27,7 @@ export function MiniMap(
|
|
|
29
27
|
|
|
30
28
|
return () => {
|
|
31
29
|
const nodes = instance.nodes()
|
|
32
|
-
if (nodes.length === 0)
|
|
33
|
-
return <div class="pyreon-flow-minimap" style={containerStyle} />
|
|
30
|
+
if (nodes.length === 0) return <div class="pyreon-flow-minimap" style={containerStyle} />
|
|
34
31
|
|
|
35
32
|
// Calculate graph bounds
|
|
36
33
|
let minX = Number.POSITIVE_INFINITY
|
|
@@ -78,23 +75,10 @@ export function MiniMap(
|
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
return (
|
|
81
|
-
<div
|
|
82
|
-
|
|
83
|
-
style={containerStyle}
|
|
84
|
-
onClick={handleClick}
|
|
85
|
-
>
|
|
86
|
-
<svg
|
|
87
|
-
role="img"
|
|
88
|
-
aria-label="minimap"
|
|
89
|
-
width={String(width)}
|
|
90
|
-
height={String(height)}
|
|
91
|
-
>
|
|
78
|
+
<div class="pyreon-flow-minimap" style={containerStyle} onClick={handleClick}>
|
|
79
|
+
<svg role="img" aria-label="minimap" width={String(width)} height={String(height)}>
|
|
92
80
|
{/* Mask outside viewport */}
|
|
93
|
-
<rect
|
|
94
|
-
width={String(width)}
|
|
95
|
-
height={String(height)}
|
|
96
|
-
fill={maskColor}
|
|
97
|
-
/>
|
|
81
|
+
<rect width={String(width)} height={String(height)} fill={maskColor} />
|
|
98
82
|
|
|
99
83
|
{/* Nodes */}
|
|
100
84
|
{nodes.map((node) => {
|
|
@@ -102,8 +86,7 @@ export function MiniMap(
|
|
|
102
86
|
const h = (node.height ?? 40) * scale
|
|
103
87
|
const x = (node.position.x - minX + padding) * scale
|
|
104
88
|
const y = (node.position.y - minY + padding) * scale
|
|
105
|
-
const color =
|
|
106
|
-
typeof nodeColor === 'function' ? nodeColor(node) : nodeColor
|
|
89
|
+
const color = typeof nodeColor === "function" ? nodeColor(node) : nodeColor
|
|
107
90
|
|
|
108
91
|
return (
|
|
109
92
|
<rect
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import type { FlowInstance } from
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import type { FlowInstance } from "../types"
|
|
3
3
|
|
|
4
4
|
export interface NodeResizerProps {
|
|
5
5
|
nodeId: string
|
|
@@ -14,28 +14,28 @@ export interface NodeResizerProps {
|
|
|
14
14
|
showEdgeHandles?: boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
type ResizeDirection =
|
|
17
|
+
type ResizeDirection = "nw" | "ne" | "sw" | "se" | "n" | "s" | "e" | "w"
|
|
18
18
|
|
|
19
19
|
const directionCursors: Record<ResizeDirection, string> = {
|
|
20
|
-
nw:
|
|
21
|
-
ne:
|
|
22
|
-
sw:
|
|
23
|
-
se:
|
|
24
|
-
n:
|
|
25
|
-
s:
|
|
26
|
-
e:
|
|
27
|
-
w:
|
|
20
|
+
nw: "nw-resize",
|
|
21
|
+
ne: "ne-resize",
|
|
22
|
+
sw: "sw-resize",
|
|
23
|
+
se: "se-resize",
|
|
24
|
+
n: "n-resize",
|
|
25
|
+
s: "s-resize",
|
|
26
|
+
e: "e-resize",
|
|
27
|
+
w: "w-resize",
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const directionPositions: Record<ResizeDirection, string> = {
|
|
31
|
-
nw:
|
|
32
|
-
ne:
|
|
33
|
-
sw:
|
|
34
|
-
se:
|
|
35
|
-
n:
|
|
36
|
-
s:
|
|
37
|
-
e:
|
|
38
|
-
w:
|
|
31
|
+
nw: "top: -4px; left: -4px;",
|
|
32
|
+
ne: "top: -4px; right: -4px;",
|
|
33
|
+
sw: "bottom: -4px; left: -4px;",
|
|
34
|
+
se: "bottom: -4px; right: -4px;",
|
|
35
|
+
n: "top: -4px; left: 50%; transform: translateX(-50%);",
|
|
36
|
+
s: "bottom: -4px; left: 50%; transform: translateX(-50%);",
|
|
37
|
+
e: "right: -4px; top: 50%; transform: translateY(-50%);",
|
|
38
|
+
w: "left: -4px; top: 50%; transform: translateY(-50%);",
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -67,8 +67,8 @@ export function NodeResizer(props: NodeResizerProps): VNodeChild {
|
|
|
67
67
|
} = props
|
|
68
68
|
|
|
69
69
|
const directions: ResizeDirection[] = showEdgeHandles
|
|
70
|
-
? [
|
|
71
|
-
: [
|
|
70
|
+
? ["nw", "ne", "sw", "se", "n", "s", "e", "w"]
|
|
71
|
+
: ["nw", "ne", "sw", "se"]
|
|
72
72
|
|
|
73
73
|
const createHandler = (dir: ResizeDirection) => {
|
|
74
74
|
let startX = 0
|
|
@@ -112,19 +112,19 @@ export function NodeResizer(props: NodeResizerProps): VNodeChild {
|
|
|
112
112
|
let newY = startNodeY
|
|
113
113
|
|
|
114
114
|
// Horizontal
|
|
115
|
-
if (dir ===
|
|
115
|
+
if (dir === "e" || dir === "se" || dir === "ne") {
|
|
116
116
|
newW = Math.max(minWidth, startWidth + dx)
|
|
117
117
|
}
|
|
118
|
-
if (dir ===
|
|
118
|
+
if (dir === "w" || dir === "sw" || dir === "nw") {
|
|
119
119
|
newW = Math.max(minWidth, startWidth - dx)
|
|
120
120
|
newX = startNodeX + startWidth - newW
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// Vertical
|
|
124
|
-
if (dir ===
|
|
124
|
+
if (dir === "s" || dir === "se" || dir === "sw") {
|
|
125
125
|
newH = Math.max(minHeight, startHeight + dy)
|
|
126
126
|
}
|
|
127
|
-
if (dir ===
|
|
127
|
+
if (dir === "n" || dir === "ne" || dir === "nw") {
|
|
128
128
|
newH = Math.max(minHeight, startHeight - dy)
|
|
129
129
|
newY = startNodeY + startHeight - newH
|
|
130
130
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
2
|
|
|
3
3
|
export interface NodeToolbarProps {
|
|
4
4
|
/** Position relative to node — default: 'top' */
|
|
5
|
-
position?:
|
|
5
|
+
position?: "top" | "bottom" | "left" | "right"
|
|
6
6
|
/** Offset from node in px — default: 8 */
|
|
7
7
|
offset?: number
|
|
8
8
|
/** Only show when node is selected — default: true */
|
|
@@ -15,10 +15,10 @@ export interface NodeToolbarProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const positionStyles: Record<string, string> = {
|
|
18
|
-
top:
|
|
19
|
-
bottom:
|
|
20
|
-
left:
|
|
21
|
-
right:
|
|
18
|
+
top: "bottom: 100%; left: 50%; transform: translateX(-50%);",
|
|
19
|
+
bottom: "top: 100%; left: 50%; transform: translateX(-50%);",
|
|
20
|
+
left: "right: 100%; top: 50%; transform: translateY(-50%);",
|
|
21
|
+
right: "left: 100%; top: 50%; transform: translateY(-50%);",
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -41,33 +41,24 @@ const positionStyles: Record<string, string> = {
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
export function NodeToolbar(props: NodeToolbarProps): VNodeChild {
|
|
44
|
-
const {
|
|
45
|
-
position = 'top',
|
|
46
|
-
offset = 8,
|
|
47
|
-
showOnSelect = true,
|
|
48
|
-
selected = false,
|
|
49
|
-
children,
|
|
50
|
-
} = props
|
|
44
|
+
const { position = "top", offset = 8, showOnSelect = true, selected = false, children } = props
|
|
51
45
|
|
|
52
46
|
if (showOnSelect && !selected) return null
|
|
53
47
|
|
|
54
48
|
const posStyle = positionStyles[position] ?? positionStyles.top
|
|
55
49
|
const marginProp =
|
|
56
|
-
position ===
|
|
50
|
+
position === "top"
|
|
57
51
|
? `margin-bottom: ${offset}px;`
|
|
58
|
-
: position ===
|
|
52
|
+
: position === "bottom"
|
|
59
53
|
? `margin-top: ${offset}px;`
|
|
60
|
-
: position ===
|
|
54
|
+
: position === "left"
|
|
61
55
|
? `margin-right: ${offset}px;`
|
|
62
56
|
: `margin-left: ${offset}px;`
|
|
63
57
|
|
|
64
|
-
const baseStyle = `position: absolute; ${posStyle} ${marginProp} z-index: 10; display: flex; gap: 4px; background: white; border: 1px solid #ddd; border-radius: 6px; padding: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); ${props.style ??
|
|
58
|
+
const baseStyle = `position: absolute; ${posStyle} ${marginProp} z-index: 10; display: flex; gap: 4px; background: white; border: 1px solid #ddd; border-radius: 6px; padding: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); ${props.style ?? ""}`
|
|
65
59
|
|
|
66
60
|
return (
|
|
67
|
-
<div
|
|
68
|
-
class={`pyreon-flow-node-toolbar ${props.class ?? ''}`}
|
|
69
|
-
style={baseStyle}
|
|
70
|
-
>
|
|
61
|
+
<div class={`pyreon-flow-node-toolbar ${props.class ?? ""}`} style={baseStyle}>
|
|
71
62
|
{children}
|
|
72
63
|
</div>
|
|
73
64
|
)
|
package/src/components/panel.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import type { PanelProps } from
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import type { PanelProps } from "../types"
|
|
3
3
|
|
|
4
4
|
const positionStyles: Record<string, string> = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"top-left": "top: 10px; left: 10px;",
|
|
6
|
+
"top-right": "top: 10px; right: 10px;",
|
|
7
|
+
"bottom-left": "bottom: 10px; left: 10px;",
|
|
8
|
+
"bottom-right": "bottom: 10px; right: 10px;",
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -21,12 +21,12 @@ const positionStyles: Record<string, string> = {
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
export function Panel(props: PanelProps): VNodeChild {
|
|
24
|
-
const { position =
|
|
25
|
-
const posStyle = positionStyles[position] ?? positionStyles[
|
|
24
|
+
const { position = "top-left", style = "", children } = props
|
|
25
|
+
const posStyle = positionStyles[position] ?? positionStyles["top-left"]
|
|
26
26
|
const baseStyle = `position: absolute; ${posStyle} z-index: 5; ${style}`
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
<div class={`pyreon-flow-panel ${props.class ??
|
|
29
|
+
<div class={`pyreon-flow-panel ${props.class ?? ""}`} style={baseStyle}>
|
|
30
30
|
{children}
|
|
31
31
|
</div>
|
|
32
32
|
)
|