@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/src/types.ts ADDED
@@ -0,0 +1,483 @@
1
+ import type { VNodeChild } from '@pyreon/core'
2
+ import type { Computed, Signal } from '@pyreon/reactivity'
3
+
4
+ // ─── Position & Geometry ─────────────────────────────────────────────────────
5
+
6
+ export interface XYPosition {
7
+ x: number
8
+ y: number
9
+ }
10
+
11
+ export interface Dimensions {
12
+ width: number
13
+ height: number
14
+ }
15
+
16
+ export interface Rect extends XYPosition, Dimensions {}
17
+
18
+ // ─── Viewport ────────────────────────────────────────────────────────────────
19
+
20
+ export interface Viewport {
21
+ x: number
22
+ y: number
23
+ zoom: number
24
+ }
25
+
26
+ // ─── Handle ──────────────────────────────────────────────────────────────────
27
+
28
+ export type HandleType = 'source' | 'target'
29
+
30
+ export enum Position {
31
+ Top = 'top',
32
+ Right = 'right',
33
+ Bottom = 'bottom',
34
+ Left = 'left',
35
+ }
36
+
37
+ export interface HandleConfig {
38
+ id?: string
39
+ type: HandleType
40
+ position: Position
41
+ }
42
+
43
+ // ─── Node ────────────────────────────────────────────────────────────────────
44
+
45
+ export interface FlowNode<TData = Record<string, unknown>> {
46
+ id: string
47
+ type?: string
48
+ position: XYPosition
49
+ data: TData
50
+ width?: number
51
+ height?: number
52
+ /** Whether the node can be dragged */
53
+ draggable?: boolean
54
+ /** Whether the node can be selected */
55
+ selectable?: boolean
56
+ /** Whether the node can be connected to */
57
+ connectable?: boolean
58
+ /** Custom class name */
59
+ class?: string
60
+ /** Custom style */
61
+ style?: string
62
+ /** Source handles */
63
+ sourceHandles?: HandleConfig[]
64
+ /** Target handles */
65
+ targetHandles?: HandleConfig[]
66
+ /** Parent node id for grouping */
67
+ parentId?: string
68
+ /** Whether this node is a group */
69
+ group?: boolean
70
+ }
71
+
72
+ // ─── Edge ────────────────────────────────────────────────────────────────────
73
+
74
+ export type EdgeType = 'bezier' | 'smoothstep' | 'straight' | 'step'
75
+
76
+ export interface FlowEdge {
77
+ id?: string
78
+ source: string
79
+ target: string
80
+ sourceHandle?: string
81
+ targetHandle?: string
82
+ type?: EdgeType
83
+ label?: string
84
+ animated?: boolean
85
+ class?: string
86
+ style?: string
87
+ /** Custom data attached to the edge */
88
+ data?: Record<string, unknown>
89
+ /** Waypoints — intermediate points the edge passes through */
90
+ waypoints?: XYPosition[]
91
+ }
92
+
93
+ // ─── Connection ──────────────────────────────────────────────────────────────
94
+
95
+ export interface Connection {
96
+ source: string
97
+ target: string
98
+ sourceHandle?: string
99
+ targetHandle?: string
100
+ }
101
+
102
+ export type ConnectionRule = Record<string, { outputs: string[] }>
103
+
104
+ // ─── Node Change Events ──────────────────────────────────────────────────────
105
+
106
+ export type NodeChange =
107
+ | { type: 'position'; id: string; position: XYPosition }
108
+ | { type: 'dimensions'; id: string; dimensions: Dimensions }
109
+ | { type: 'select'; id: string; selected: boolean }
110
+ | { type: 'remove'; id: string }
111
+
112
+ // ─── Edge path result ────────────────────────────────────────────────────────
113
+
114
+ export interface EdgePathResult {
115
+ path: string
116
+ labelX: number
117
+ labelY: number
118
+ }
119
+
120
+ // ─── Flow config ─────────────────────────────────────────────────────────────
121
+
122
+ export interface FlowConfig {
123
+ nodes?: FlowNode[]
124
+ edges?: FlowEdge[]
125
+ /** Default edge type */
126
+ defaultEdgeType?: EdgeType
127
+ /** Min zoom level — default: 0.1 */
128
+ minZoom?: number
129
+ /** Max zoom level — default: 4 */
130
+ maxZoom?: number
131
+ /** Snap to grid */
132
+ snapToGrid?: boolean
133
+ /** Grid size for snapping — default: 15 */
134
+ snapGrid?: number
135
+ /** Connection rules — which node types can connect */
136
+ connectionRules?: ConnectionRule
137
+ /** Whether nodes are draggable by default — default: true */
138
+ nodesDraggable?: boolean
139
+ /** Whether nodes are connectable by default — default: true */
140
+ nodesConnectable?: boolean
141
+ /** Whether nodes are selectable by default — default: true */
142
+ nodesSelectable?: boolean
143
+ /** Whether to allow multi-selection — default: true */
144
+ multiSelect?: boolean
145
+ /** Drag boundaries for nodes — [[minX, minY], [maxX, maxY]] */
146
+ nodeExtent?: [[number, number], [number, number]]
147
+ /** Whether panning is enabled — default: true */
148
+ pannable?: boolean
149
+ /** Whether zooming is enabled — default: true */
150
+ zoomable?: boolean
151
+ /** Fit view on initial render — default: false */
152
+ fitView?: boolean
153
+ /** Padding for fitView — default: 0.1 */
154
+ fitViewPadding?: number
155
+ }
156
+
157
+ // ─── Flow instance ───────────────────────────────────────────────────────────
158
+
159
+ export interface FlowInstance {
160
+ // ── State (signals) ──────────────────────────────────────────────────────
161
+
162
+ /** All nodes — reactive */
163
+ nodes: Signal<FlowNode[]>
164
+ /** All edges — reactive */
165
+ edges: Signal<FlowEdge[]>
166
+ /** Viewport state — reactive */
167
+ viewport: Signal<Viewport>
168
+ /** Current zoom level — computed */
169
+ zoom: Computed<number>
170
+ /** Selected node ids — computed */
171
+ selectedNodes: Computed<string[]>
172
+ /** Selected edge ids — computed */
173
+ selectedEdges: Computed<string[]>
174
+ /** Container dimensions — updated by the Flow component via ResizeObserver */
175
+ containerSize: Signal<{ width: number; height: number }>
176
+
177
+ // ── Node operations ──────────────────────────────────────────────────────
178
+
179
+ /** Get a single node by id */
180
+ getNode: (id: string) => FlowNode | undefined
181
+ /** Add a node */
182
+ addNode: (node: FlowNode) => void
183
+ /** Remove a node and its connected edges */
184
+ removeNode: (id: string) => void
185
+ /** Update a node's properties */
186
+ updateNode: (id: string, update: Partial<FlowNode>) => void
187
+ /** Update a node's position */
188
+ updateNodePosition: (id: string, position: XYPosition) => void
189
+
190
+ // ── Edge operations ──────────────────────────────────────────────────────
191
+
192
+ /** Get a single edge by id */
193
+ getEdge: (id: string) => FlowEdge | undefined
194
+ /** Add an edge */
195
+ addEdge: (edge: FlowEdge) => void
196
+ /** Remove an edge */
197
+ removeEdge: (id: string) => void
198
+ /** Check if a connection is valid (based on rules) */
199
+ isValidConnection: (connection: Connection) => boolean
200
+
201
+ // ── Selection ────────────────────────────────────────────────────────────
202
+
203
+ /** Select a node */
204
+ selectNode: (id: string, additive?: boolean) => void
205
+ /** Deselect a node */
206
+ deselectNode: (id: string) => void
207
+ /** Select an edge */
208
+ selectEdge: (id: string, additive?: boolean) => void
209
+ /** Clear all selection */
210
+ clearSelection: () => void
211
+ /** Select all nodes */
212
+ selectAll: () => void
213
+ /** Delete selected nodes/edges */
214
+ deleteSelected: () => void
215
+
216
+ // ── Viewport ─────────────────────────────────────────────────────────────
217
+
218
+ /** Fit view to show all nodes */
219
+ fitView: (nodeIds?: string[], padding?: number) => void
220
+ /** Set zoom level */
221
+ zoomTo: (zoom: number) => void
222
+ /** Zoom in */
223
+ zoomIn: () => void
224
+ /** Zoom out */
225
+ zoomOut: () => void
226
+ /** Pan to position */
227
+ panTo: (position: XYPosition) => void
228
+ /** Check if a node is visible in the current viewport */
229
+ isNodeVisible: (id: string) => boolean
230
+
231
+ // ── Layout ───────────────────────────────────────────────────────────────
232
+
233
+ /** Apply auto-layout using elkjs */
234
+ layout: (
235
+ algorithm?: LayoutAlgorithm,
236
+ options?: LayoutOptions,
237
+ ) => Promise<void>
238
+
239
+ // ── Batch ────────────────────────────────────────────────────────────────
240
+
241
+ /** Batch multiple operations */
242
+ batch: (fn: () => void) => void
243
+
244
+ // ── Graph queries ────────────────────────────────────────────────────────
245
+
246
+ /** Get edges connected to a node */
247
+ getConnectedEdges: (nodeId: string) => FlowEdge[]
248
+ /** Get incoming edges for a node */
249
+ getIncomers: (nodeId: string) => FlowNode[]
250
+ /** Get outgoing edges from a node */
251
+ getOutgoers: (nodeId: string) => FlowNode[]
252
+
253
+ // ── Listeners ────────────────────────────────────────────────────────────
254
+
255
+ /** Called when a connection is made */
256
+ onConnect: (callback: (connection: Connection) => void) => () => void
257
+ /** Called when nodes change */
258
+ onNodesChange: (callback: (changes: NodeChange[]) => void) => () => void
259
+ /** Called when a node is clicked */
260
+ onNodeClick: (callback: (node: FlowNode) => void) => () => void
261
+ /** Called when an edge is clicked */
262
+ onEdgeClick: (callback: (edge: FlowEdge) => void) => () => void
263
+ /** Called when a node starts being dragged */
264
+ onNodeDragStart: (callback: (node: FlowNode) => void) => () => void
265
+ /** Called when a node stops being dragged */
266
+ onNodeDragEnd: (callback: (node: FlowNode) => void) => () => void
267
+ /** Called when a node is double-clicked */
268
+ onNodeDoubleClick: (callback: (node: FlowNode) => void) => () => void
269
+
270
+ // ── Copy / Paste ─────────────────────────────────────────────────────────
271
+
272
+ /** Copy selected nodes and their edges to clipboard */
273
+ copySelected: () => void
274
+ /** Paste clipboard contents with offset */
275
+ paste: (offset?: XYPosition) => void
276
+
277
+ // ── Undo / Redo ─────────────────────────────────────────────────────────
278
+
279
+ /** Save current state to undo history */
280
+ pushHistory: () => void
281
+ /** Undo last change */
282
+ undo: () => void
283
+ /** Redo last undone change */
284
+ redo: () => void
285
+
286
+ // ── Multi-node drag ─────────────────────────────────────────────────────
287
+
288
+ /** Move all selected nodes by dx/dy */
289
+ moveSelectedNodes: (dx: number, dy: number) => void
290
+
291
+ // ── Helper lines ────────────────────────────────────────────────────────
292
+
293
+ /** Get snap guide lines for a dragged node */
294
+ getSnapLines: (
295
+ dragNodeId: string,
296
+ position: XYPosition,
297
+ threshold?: number,
298
+ ) => { x: number | null; y: number | null; snappedPosition: XYPosition }
299
+
300
+ // ── Sub-flows / Groups ───────────────────────────────────────────────────
301
+
302
+ /** Get child nodes of a group node */
303
+ getChildNodes: (parentId: string) => FlowNode[]
304
+ /** Get absolute position of a node (accounting for parent offsets) */
305
+ getAbsolutePosition: (nodeId: string) => XYPosition
306
+
307
+ // ── Edge reconnecting ──────────────────────────────────────────────────
308
+
309
+ // ── Edge waypoints ──────────────────────────────────────────────────────
310
+
311
+ /** Add a waypoint (bend point) to an edge */
312
+ addEdgeWaypoint: (edgeId: string, point: XYPosition, index?: number) => void
313
+ /** Remove a waypoint from an edge */
314
+ removeEdgeWaypoint: (edgeId: string, index: number) => void
315
+ /** Update a waypoint position */
316
+ updateEdgeWaypoint: (edgeId: string, index: number, point: XYPosition) => void
317
+
318
+ // ── Edge reconnecting ──────────────────────────────────────────────────
319
+
320
+ /** Reconnect an edge to a new source/target */
321
+ reconnectEdge: (
322
+ edgeId: string,
323
+ newConnection: {
324
+ source?: string
325
+ target?: string
326
+ sourceHandle?: string
327
+ targetHandle?: string
328
+ },
329
+ ) => void
330
+
331
+ // ── Proximity connect ────────────────────────────────────────────────────
332
+
333
+ /** Find the nearest unconnected node within threshold distance */
334
+ getProximityConnection: (
335
+ nodeId: string,
336
+ threshold?: number,
337
+ ) => Connection | null
338
+
339
+ // ── Collision detection ─────────────────────────────────────────────────
340
+
341
+ /** Get nodes that overlap with the given node */
342
+ getOverlappingNodes: (nodeId: string) => FlowNode[]
343
+ /** Push overlapping nodes apart */
344
+ resolveCollisions: (nodeId: string, spacing?: number) => void
345
+
346
+ // ── Node extent ─────────────────────────────────────────────────────────
347
+
348
+ /** Set drag boundaries for all nodes — [[minX, minY], [maxX, maxY]] or null to remove */
349
+ setNodeExtent: (extent: [[number, number], [number, number]] | null) => void
350
+ /** Clamp a position to the current node extent */
351
+ clampToExtent: (
352
+ position: XYPosition,
353
+ nodeWidth?: number,
354
+ nodeHeight?: number,
355
+ ) => XYPosition
356
+
357
+ // ── Search / Filter ─────────────────────────────────────────────────────
358
+
359
+ /** Find nodes matching a predicate */
360
+ findNodes: (predicate: (node: FlowNode) => boolean) => FlowNode[]
361
+ /** Find nodes by label text (case-insensitive) */
362
+ searchNodes: (query: string) => FlowNode[]
363
+ /** Focus viewport on a specific node (pan + optional zoom) */
364
+ focusNode: (nodeId: string, zoom?: number) => void
365
+
366
+ // ── Export ─────────────────────────────────────────────────────────────
367
+
368
+ /** Export the flow as a JSON-serializable object */
369
+ toJSON: () => { nodes: FlowNode[]; edges: FlowEdge[]; viewport: Viewport }
370
+ /** Import flow state from a JSON object */
371
+ fromJSON: (data: {
372
+ nodes: FlowNode[]
373
+ edges: FlowEdge[]
374
+ viewport?: Viewport
375
+ }) => void
376
+
377
+ // ── Viewport animation ─────────────────────────────────────────────────
378
+
379
+ /** Animate viewport to a new position/zoom */
380
+ animateViewport: (target: Partial<Viewport>, duration?: number) => void
381
+
382
+ // ── Internal emitters (used by Flow component) ──────────────────────────
383
+
384
+ /** @internal */
385
+ _emit: {
386
+ nodeDragStart: (node: FlowNode) => void
387
+ nodeDragEnd: (node: FlowNode) => void
388
+ nodeDoubleClick: (node: FlowNode) => void
389
+ nodeClick: (node: FlowNode) => void
390
+ edgeClick: (edge: FlowEdge) => void
391
+ }
392
+
393
+ // ── Config ───────────────────────────────────────────────────────────────
394
+
395
+ /** The flow configuration */
396
+ config: FlowConfig
397
+
398
+ // ── Cleanup ──────────────────────────────────────────────────────────────
399
+
400
+ /** Dispose all listeners and clean up */
401
+ dispose: () => void
402
+ }
403
+
404
+ // ─── Layout ──────────────────────────────────────────────────────────────────
405
+
406
+ export type LayoutAlgorithm =
407
+ | 'layered'
408
+ | 'force'
409
+ | 'stress'
410
+ | 'tree'
411
+ | 'radial'
412
+ | 'box'
413
+ | 'rectpacking'
414
+
415
+ export interface LayoutOptions {
416
+ /** Layout direction — default: 'DOWN' */
417
+ direction?: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
418
+ /** Spacing between nodes — default: 50 */
419
+ nodeSpacing?: number
420
+ /** Spacing between layers — default: 80 */
421
+ layerSpacing?: number
422
+ /** Edge routing — default: 'orthogonal' */
423
+ edgeRouting?: 'orthogonal' | 'splines' | 'polyline'
424
+ /** Whether to animate the layout transition — default: true */
425
+ animate?: boolean
426
+ /** Animation duration in ms — default: 300 */
427
+ animationDuration?: number
428
+ }
429
+
430
+ // ─── Component props ─────────────────────────────────────────────────────────
431
+
432
+ export interface FlowProps {
433
+ instance: FlowInstance
434
+ style?: string
435
+ class?: string
436
+ children?: VNodeChild
437
+ }
438
+
439
+ export interface BackgroundProps {
440
+ variant?: 'dots' | 'lines' | 'cross'
441
+ gap?: number
442
+ size?: number
443
+ color?: string
444
+ }
445
+
446
+ export interface MiniMapProps {
447
+ style?: string
448
+ class?: string
449
+ nodeColor?: string | ((node: FlowNode) => string)
450
+ maskColor?: string
451
+ width?: number
452
+ height?: number
453
+ }
454
+
455
+ export interface ControlsProps {
456
+ showZoomIn?: boolean
457
+ showZoomOut?: boolean
458
+ showFitView?: boolean
459
+ showLock?: boolean
460
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
461
+ }
462
+
463
+ export interface PanelProps {
464
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
465
+ style?: string
466
+ class?: string
467
+ children?: VNodeChild
468
+ }
469
+
470
+ export interface HandleProps {
471
+ type: HandleType
472
+ position: Position
473
+ id?: string
474
+ style?: string
475
+ class?: string
476
+ }
477
+
478
+ export type NodeComponentProps<TData = Record<string, unknown>> = {
479
+ id: string
480
+ data: TData
481
+ selected: boolean
482
+ dragging: boolean
483
+ }