@pyreon/flow 0.9.0 → 0.11.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.
@@ -1,32 +1,19 @@
1
- import type { VNodeChild } from '@pyreon/core'
2
- import { signal } from '@pyreon/reactivity'
3
- import {
4
- getEdgePath,
5
- getHandlePosition,
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 ? '#3b82f6' : '#ddd'
29
- const cursor = props.dragging ? 'grabbing' : 'grab'
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(),
@@ -122,11 +109,13 @@ function EdgeLayer(props: {
122
109
  <defs>
123
110
  <marker
124
111
  id="flow-arrowhead"
125
- markerWidth="10"
126
- markerHeight="7"
127
- refX="10"
128
- refY="3.5"
129
- orient="auto"
112
+ {...{
113
+ markerWidth: "10",
114
+ markerHeight: "7",
115
+ refX: "10",
116
+ refY: "3.5",
117
+ orient: "auto",
118
+ }}
130
119
  >
131
120
  <polygon points="0 0, 10 3.5, 0 7" fill="#999" />
132
121
  </marker>
@@ -141,10 +130,7 @@ function EdgeLayer(props: {
141
130
  const targetW = targetNode.width ?? 150
142
131
  const targetH = targetNode.height ?? 40
143
132
 
144
- const { sourcePosition, targetPosition } = getSmartHandlePositions(
145
- sourceNode,
146
- targetNode,
147
- )
133
+ const { sourcePosition, targetPosition } = getSmartHandlePositions(sourceNode, targetNode)
148
134
 
149
135
  const sourcePos = getHandlePosition(
150
136
  sourcePosition,
@@ -170,7 +156,7 @@ function EdgeLayer(props: {
170
156
  waypoints: edge.waypoints,
171
157
  })
172
158
  : getEdgePath(
173
- edge.type ?? 'bezier',
159
+ edge.type ?? "bezier",
174
160
  sourcePos.x,
175
161
  sourcePos.y,
176
162
  sourcePosition,
@@ -186,10 +172,7 @@ function EdgeLayer(props: {
186
172
  const CustomEdge = edge.type && edgeTypes?.[edge.type]
187
173
  if (CustomEdge) {
188
174
  return (
189
- <g
190
- key={edge.id}
191
- onClick={() => edge.id && instance.selectEdge(edge.id)}
192
- >
175
+ <g key={edge.id} onClick={() => edge.id && instance.selectEdge(edge.id)}>
193
176
  <CustomEdge
194
177
  edge={edge}
195
178
  sourceX={sourcePos.x}
@@ -207,11 +190,11 @@ function EdgeLayer(props: {
207
190
  <path
208
191
  d={path}
209
192
  fill="none"
210
- stroke={isSelected ? '#3b82f6' : '#999'}
211
- stroke-width={isSelected ? '2' : '1.5'}
193
+ stroke={isSelected ? "#3b82f6" : "#999"}
194
+ stroke-width={isSelected ? "2" : "1.5"}
212
195
  marker-end="url(#flow-arrowhead)"
213
- class={edge.animated ? 'pyreon-flow-edge-animated' : ''}
214
- 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 ?? ""}`}
215
198
  onClick={() => {
216
199
  if (edge.id) instance.selectEdge(edge.id)
217
200
  instance._emit.edgeClick(edge)
@@ -235,7 +218,7 @@ function EdgeLayer(props: {
235
218
  <path
236
219
  d={
237
220
  getEdgePath(
238
- 'bezier',
221
+ "bezier",
239
222
  conn.sourceX,
240
223
  conn.sourceY,
241
224
  conn.sourcePosition,
@@ -270,13 +253,7 @@ function NodeLayer(props: {
270
253
  position: Position,
271
254
  ) => void
272
255
  }): VNodeChild {
273
- const {
274
- instance,
275
- nodeTypes,
276
- draggingNodeId,
277
- onNodePointerDown,
278
- onHandlePointerDown,
279
- } = props
256
+ const { instance, nodeTypes, draggingNodeId, onNodePointerDown, onHandlePointerDown } = props
280
257
 
281
258
  return () => {
282
259
  const nodes = instance.nodes()
@@ -288,43 +265,37 @@ function NodeLayer(props: {
288
265
  {nodes.map((node) => {
289
266
  const isSelected = selectedIds.includes(node.id)
290
267
  const isDragging = dragId === node.id
291
- const NodeComponent =
292
- (node.type && nodeTypes[node.type]) || nodeTypes.default!
268
+ const NodeComponent = (node.type && nodeTypes[node.type]) || nodeTypes.default!
293
269
 
294
270
  return (
295
271
  <div
296
272
  key={node.id}
297
- class={`pyreon-flow-node ${node.class ?? ''} ${isSelected ? 'selected' : ''} ${isDragging ? 'dragging' : ''}`}
298
- 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 ?? ""}`}
299
275
  data-nodeid={node.id}
300
276
  onClick={(e: MouseEvent) => {
301
277
  e.stopPropagation()
302
278
  instance.selectNode(node.id, e.shiftKey)
303
279
  instance._emit.nodeClick(node)
304
280
  }}
305
- onDblclick={(e: MouseEvent) => {
281
+ onDblClick={(e: MouseEvent) => {
306
282
  e.stopPropagation()
307
283
  instance._emit.nodeDoubleClick(node)
308
284
  }}
309
- onPointerdown={(e: PointerEvent) => {
285
+ onPointerDown={(e: PointerEvent) => {
310
286
  // Check if clicking a handle
311
287
  const target = e.target as HTMLElement
312
- const handle = target.closest('.pyreon-flow-handle')
288
+ const handle = target.closest(".pyreon-flow-handle")
313
289
  if (handle) {
314
- const hType =
315
- handle.getAttribute('data-handletype') ?? 'source'
316
- const hId = handle.getAttribute('data-handleid') ?? 'source'
290
+ const hType = handle.getAttribute("data-handletype") ?? "source"
291
+ const hId = handle.getAttribute("data-handleid") ?? "source"
317
292
  const hPos =
318
- (handle.getAttribute('data-handleposition') as Position) ??
319
- Position.Right
293
+ (handle.getAttribute("data-handleposition") as Position) ?? Position.Right
320
294
  onHandlePointerDown(e, node.id, hType, hId, hPos)
321
295
  return
322
296
  }
323
297
  // Otherwise start dragging node
324
- if (
325
- node.draggable !== false &&
326
- instance.config.nodesDraggable !== false
327
- ) {
298
+ if (node.draggable !== false && instance.config.nodesDraggable !== false) {
328
299
  onNodePointerDown(e, node)
329
300
  }
330
301
  }}
@@ -348,7 +319,7 @@ function NodeLayer(props: {
348
319
  type EdgeTypeMap = Record<
349
320
  string,
350
321
  (props: {
351
- edge: import('../types').FlowEdge
322
+ edge: import("../types").FlowEdge
352
323
  sourceX: number
353
324
  sourceY: number
354
325
  targetX: number
@@ -407,7 +378,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
407
378
  y: null,
408
379
  })
409
380
 
410
- const draggingNodeId = () => (dragState().active ? dragState().nodeId : '')
381
+ const draggingNodeId = () => (dragState().active ? dragState().nodeId : "")
411
382
 
412
383
  // ── Node dragging ──────────────────────────────────────────────────────
413
384
 
@@ -445,9 +416,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
445
416
 
446
417
  instance._emit.nodeDragStart(node)
447
418
 
448
- const container = (e.currentTarget as HTMLElement).closest(
449
- '.pyreon-flow',
450
- ) as HTMLElement
419
+ const container = (e.currentTarget as HTMLElement).closest(".pyreon-flow") as HTMLElement
451
420
  if (container) container.setPointerCapture(e.pointerId)
452
421
  }
453
422
 
@@ -468,13 +437,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
468
437
 
469
438
  const w = node.width ?? 150
470
439
  const h = node.height ?? 40
471
- const handlePos = getHandlePosition(
472
- position,
473
- node.position.x,
474
- node.position.y,
475
- w,
476
- h,
477
- )
440
+ const handlePos = getHandlePosition(position, node.position.x, node.position.y, w, h)
478
441
 
479
442
  connectionState.set({
480
443
  active: true,
@@ -487,9 +450,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
487
450
  currentY: handlePos.y,
488
451
  })
489
452
 
490
- const container = (e.target as HTMLElement).closest(
491
- '.pyreon-flow',
492
- ) as HTMLElement
453
+ const container = (e.target as HTMLElement).closest(".pyreon-flow") as HTMLElement
493
454
  if (container) container.setPointerCapture(e.pointerId)
494
455
  }
495
456
 
@@ -501,10 +462,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
501
462
 
502
463
  const delta = -e.deltaY * 0.001
503
464
  const newZoom = Math.min(
504
- Math.max(
505
- instance.viewport.peek().zoom * (1 + delta),
506
- instance.config.minZoom ?? 0.1,
507
- ),
465
+ Math.max(instance.viewport.peek().zoom * (1 + delta), instance.config.minZoom ?? 0.1),
508
466
  instance.config.maxZoom ?? 4,
509
467
  )
510
468
 
@@ -533,8 +491,8 @@ export function Flow(props: FlowComponentProps): VNodeChild {
533
491
  if (instance.config.pannable === false) return
534
492
 
535
493
  const target = e.target as HTMLElement
536
- if (target.closest('.pyreon-flow-node')) return
537
- if (target.closest('.pyreon-flow-handle')) return
494
+ if (target.closest(".pyreon-flow-node")) return
495
+ if (target.closest(".pyreon-flow-handle")) return
538
496
 
539
497
  // Shift+drag on empty space → selection box
540
498
  if (e.shiftKey && instance.config.multiSelect !== false) {
@@ -679,11 +637,10 @@ export function Flow(props: FlowComponentProps): VNodeChild {
679
637
  if (conn.active) {
680
638
  // Check if we released over a handle target
681
639
  const target = e.target as HTMLElement
682
- const handle = target.closest('.pyreon-flow-handle')
640
+ const handle = target.closest(".pyreon-flow-handle")
683
641
  if (handle) {
684
- const targetNodeId =
685
- handle.closest('.pyreon-flow-node')?.getAttribute('data-nodeid') ?? ''
686
- 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"
687
644
 
688
645
  if (targetNodeId && targetNodeId !== conn.sourceNodeId) {
689
646
  const connection: Connection = {
@@ -697,12 +654,8 @@ export function Flow(props: FlowComponentProps): VNodeChild {
697
654
  instance.addEdge({
698
655
  source: connection.source,
699
656
  target: connection.target,
700
- ...(connection.sourceHandle != null
701
- ? { sourceHandle: connection.sourceHandle }
702
- : {}),
703
- ...(connection.targetHandle != null
704
- ? { targetHandle: connection.targetHandle }
705
- : {}),
657
+ ...(connection.sourceHandle != null ? { sourceHandle: connection.sourceHandle } : {}),
658
+ ...(connection.targetHandle != null ? { targetHandle: connection.targetHandle } : {}),
706
659
  })
707
660
  }
708
661
  }
@@ -717,31 +670,31 @@ export function Flow(props: FlowComponentProps): VNodeChild {
717
670
  // ── Keyboard ───────────────────────────────────────────────────────────
718
671
 
719
672
  const handleKeyDown = (e: KeyboardEvent) => {
720
- if (e.key === 'Delete' || e.key === 'Backspace') {
673
+ if (e.key === "Delete" || e.key === "Backspace") {
721
674
  const target = e.target as HTMLElement
722
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return
675
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return
723
676
  instance.pushHistory()
724
677
  instance.deleteSelected()
725
678
  }
726
- if (e.key === 'Escape') {
679
+ if (e.key === "Escape") {
727
680
  instance.clearSelection()
728
681
  connectionState.set({ ...emptyConnection })
729
682
  }
730
- if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
683
+ if (e.key === "a" && (e.metaKey || e.ctrlKey)) {
731
684
  e.preventDefault()
732
685
  instance.selectAll()
733
686
  }
734
- if (e.key === 'c' && (e.metaKey || e.ctrlKey)) {
687
+ if (e.key === "c" && (e.metaKey || e.ctrlKey)) {
735
688
  instance.copySelected()
736
689
  }
737
- if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
690
+ if (e.key === "v" && (e.metaKey || e.ctrlKey)) {
738
691
  instance.paste()
739
692
  }
740
- if (e.key === 'z' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
693
+ if (e.key === "z" && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
741
694
  e.preventDefault()
742
695
  instance.undo()
743
696
  }
744
- if (e.key === 'z' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
697
+ if (e.key === "z" && (e.metaKey || e.ctrlKey) && e.shiftKey) {
745
698
  e.preventDefault()
746
699
  instance.redo()
747
700
  }
@@ -757,10 +710,7 @@ export function Flow(props: FlowComponentProps): VNodeChild {
757
710
  e.preventDefault()
758
711
  const t1 = e.touches[0]!
759
712
  const t2 = e.touches[1]!
760
- lastTouchDist = Math.hypot(
761
- t2.clientX - t1.clientX,
762
- t2.clientY - t1.clientY,
763
- )
713
+ lastTouchDist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
764
714
  lastTouchCenter = {
765
715
  x: (t1.clientX + t2.clientX) / 2,
766
716
  y: (t1.clientY + t2.clientY) / 2,
@@ -830,21 +780,21 @@ export function Flow(props: FlowComponentProps): VNodeChild {
830
780
  resizeObserver.observe(el)
831
781
  }
832
782
 
833
- 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 ?? ""}`
834
784
 
835
785
  return (
836
786
  <div
837
787
  ref={containerRef}
838
- class={`pyreon-flow ${props.class ?? ''}`}
788
+ class={`pyreon-flow ${props.class ?? ""}`}
839
789
  style={containerStyle}
840
790
  tabIndex={0}
841
791
  onWheel={handleWheel}
842
- onPointerdown={handlePointerDown}
843
- onPointermove={handlePointerMove}
844
- onPointerup={handlePointerUp}
845
- onTouchstart={handleTouchStart}
846
- onTouchmove={handleTouchMove}
847
- onKeydown={handleKeyDown}
792
+ onPointerDown={handlePointerDown}
793
+ onPointerMove={handlePointerMove}
794
+ onPointerUp={handlePointerUp}
795
+ onTouchStart={handleTouchStart}
796
+ onTouchMove={handleTouchMove}
797
+ onKeyDown={handleKeyDown}
848
798
  >
849
799
  {children}
850
800
  {() => {
@@ -1,11 +1,11 @@
1
- import type { VNodeChild } from '@pyreon/core'
2
- import type { HandleProps } from '../types'
1
+ import type { VNodeChild } from "@pyreon/core"
2
+ import type { HandleProps } from "../types"
3
3
 
4
4
  const positionOffset: Record<string, string> = {
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%);',
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 = '' } = props
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 '@pyreon/core'
2
- import type { FlowInstance, MiniMapProps } from '../types'
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 = '#e2e8f0',
22
- maskColor = 'rgba(0, 0, 0, 0.08)',
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
- class="pyreon-flow-minimap"
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 '@pyreon/core'
2
- import type { FlowInstance } from '../types'
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 = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 's' | 'e' | 'w'
17
+ type ResizeDirection = "nw" | "ne" | "sw" | "se" | "n" | "s" | "e" | "w"
18
18
 
19
19
  const directionCursors: Record<ResizeDirection, string> = {
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',
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: '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%);',
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
- ? ['nw', 'ne', 'sw', 'se', 'n', 's', 'e', 'w']
71
- : ['nw', 'ne', 'sw', 'se']
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 === 'e' || dir === 'se' || dir === 'ne') {
115
+ if (dir === "e" || dir === "se" || dir === "ne") {
116
116
  newW = Math.max(minWidth, startWidth + dx)
117
117
  }
118
- if (dir === 'w' || dir === 'sw' || dir === 'nw') {
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 === 's' || dir === 'se' || dir === 'sw') {
124
+ if (dir === "s" || dir === "se" || dir === "sw") {
125
125
  newH = Math.max(minHeight, startHeight + dy)
126
126
  }
127
- if (dir === 'n' || dir === 'ne' || dir === 'nw') {
127
+ if (dir === "n" || dir === "ne" || dir === "nw") {
128
128
  newH = Math.max(minHeight, startHeight - dy)
129
129
  newY = startNodeY + startHeight - newH
130
130
  }
@@ -158,9 +158,9 @@ export function NodeResizer(props: NodeResizerProps): VNodeChild {
158
158
  key={dir}
159
159
  class={`pyreon-flow-resizer pyreon-flow-resizer-${dir}`}
160
160
  style={`${baseStyle} ${directionPositions[dir]} cursor: ${directionCursors[dir]};`}
161
- onPointerdown={handler.onPointerDown}
162
- onPointermove={handler.onPointerMove}
163
- onPointerup={handler.onPointerUp}
161
+ onPointerDown={handler.onPointerDown}
162
+ onPointerMove={handler.onPointerMove}
163
+ onPointerUp={handler.onPointerUp}
164
164
  />
165
165
  )
166
166
  })}
@@ -1,8 +1,8 @@
1
- import type { VNodeChild } from '@pyreon/core'
1
+ import type { VNodeChild } from "@pyreon/core"
2
2
 
3
3
  export interface NodeToolbarProps {
4
4
  /** Position relative to node — default: 'top' */
5
- position?: 'top' | 'bottom' | 'left' | 'right'
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: '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%);',
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 === 'top'
50
+ position === "top"
57
51
  ? `margin-bottom: ${offset}px;`
58
- : position === 'bottom'
52
+ : position === "bottom"
59
53
  ? `margin-top: ${offset}px;`
60
- : position === 'left'
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
  )