@particle-academy/fancy-flow 0.23.0 → 0.24.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/dist/index.d.cts CHANGED
@@ -9,10 +9,10 @@ import { WorkflowSchema, WorkflowMetadata } from './schema/index.cjs';
9
9
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.cjs';
10
10
  import { AutoLayoutOptions } from './layout/index.cjs';
11
11
  export { AutoLayoutDirection } from './layout/index.cjs';
12
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-DneUoivl.cjs';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-DneUoivl.cjs';
12
+ import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-BekE5JTG.cjs';
13
+ export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-BekE5JTG.cjs';
14
14
  import { FlowRunFeedEntry } from './runtime/index.cjs';
15
- export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
15
+ export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.cjs';
16
16
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-AXzOR-Wq.cjs';
17
17
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.cjs';
18
18
  import './capabilities-exVeADXz.cjs';
@@ -36,6 +36,8 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
36
36
  * disable. An `isValidConnection` you pass yourself always takes precedence.
37
37
  */
38
38
  validateConnections?: boolean | ConnectionValidatorOptions;
39
+ /** Show alignment guide lines while dragging, and snap to them. Default off. */
40
+ showHelperLines?: boolean;
39
41
  /** Optional toolbar / palette etc. rendered above the canvas. */
40
42
  toolbar?: ReactNode;
41
43
  className?: string;
@@ -50,7 +52,7 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
50
52
  * (xyflow's standard pattern). The surrounding `useFlowState` hook in
51
53
  * `runtime/use-flow-state.ts` is a convenience that wires those for you.
52
54
  */
53
- declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
55
+ declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
54
56
 
55
57
  /**
56
58
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
package/dist/index.d.ts CHANGED
@@ -9,10 +9,10 @@ import { WorkflowSchema, WorkflowMetadata } from './schema/index.js';
9
9
  export { ImportIssue, ImportOptions, ImportResult, WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, WorkflowSchemaEdge, WorkflowSchemaNode, exportWorkflow, importWorkflow, migrateSchema, workflowToBlob } from './schema/index.js';
10
10
  import { AutoLayoutOptions } from './layout/index.js';
11
11
  export { AutoLayoutDirection } from './layout/index.js';
12
- import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-CFEtuRWJ.js';
13
- export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-CFEtuRWJ.js';
12
+ import { N as NodeCategory, a as NodeKindDefinition, C as ConfigField } from './types-DwRpXXNy.js';
13
+ export { D as DocumentConfigField, K as KeyValueConfigField, P as PortSpec, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField } from './types-DwRpXXNy.js';
14
14
  import { FlowRunFeedEntry } from './runtime/index.js';
15
- export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
15
+ export { HistoryController, UseFlowHistoryReturn, UseFlowRunOptions, UseFlowRunReturn, UseFlowStateReturn, applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './runtime/index.js';
16
16
  export { R as RunOptions, a as RunResult, r as runFlow } from './run-flow-9U-gaYVF.js';
17
17
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from './pause-9iT4tCEV.js';
18
18
  import './capabilities-85Mjn4uW.js';
@@ -36,6 +36,8 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
36
36
  * disable. An `isValidConnection` you pass yourself always takes precedence.
37
37
  */
38
38
  validateConnections?: boolean | ConnectionValidatorOptions;
39
+ /** Show alignment guide lines while dragging, and snap to them. Default off. */
40
+ showHelperLines?: boolean;
39
41
  /** Optional toolbar / palette etc. rendered above the canvas. */
40
42
  toolbar?: ReactNode;
41
43
  className?: string;
@@ -50,7 +52,7 @@ type FlowCanvasProps = Omit<ReactFlowProps<FlowNode, Edge>, "nodes" | "edges" |
50
52
  * (xyflow's standard pattern). The surrounding `useFlowState` hook in
51
53
  * `runtime/use-flow-state.ts` is a convenience that wires those for you.
52
54
  */
53
- declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
55
+ declare function FlowCanvas({ nodes, edges, background, showControls, showMinimap, height, validateConnections, isValidConnection, colorMode, showHelperLines, onNodesChange, toolbar, nodeTypes, edgeTypes, className, style, ...rest }: FlowCanvasProps): react.JSX.Element;
54
56
 
55
57
  /**
56
58
  * Clone a set of nodes AND the edges internal to that set, remapping every id.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { useFlowState, useFlowRun, useFlowHistory, applyStatusesToNodes } from './chunk-O7F6SGV4.js';
2
- export { applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-O7F6SGV4.js';
3
- import { buildNodeTypes, registerBuiltinKinds, createConnectionValidator } from './chunk-AUL4LU63.js';
4
- export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds } from './chunk-AUL4LU63.js';
5
- import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap } from './chunk-ZB4HHQMR.js';
1
+ import { useFlowState, useFlowRun, useFlowHistory, applyOutputsToNodes, applyStatusesToNodes } from './chunk-ZS6DVGB3.js';
2
+ export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-ZS6DVGB3.js';
3
+ import { buildNodeTypes, registerBuiltinKinds, createConnectionValidator } from './chunk-QAA4CA22.js';
4
+ export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds } from './chunk-QAA4CA22.js';
5
+ import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap, ViewportPortal } from './chunk-OWENS2H5.js';
6
6
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
7
7
  import './chunk-USL4FMFU.js';
8
8
  export { runFlow } from './chunk-KDHLKBL2.js';
@@ -273,6 +273,97 @@ function distributeNodes(nodes, axis) {
273
273
  return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
274
274
  });
275
275
  }
276
+
277
+ // src/components/canvas/helper-lines.ts
278
+ var w = (n) => n.width ?? n.measured?.width ?? 0;
279
+ var h = (n) => n.height ?? n.measured?.height ?? 0;
280
+ function getHelperLines(change, nodes, distance = 6) {
281
+ const result = { snapPosition: { x: void 0, y: void 0 } };
282
+ const a = nodes.find((n) => n.id === change.id);
283
+ if (!a || !change.position) return result;
284
+ const A = {
285
+ left: change.position.x,
286
+ right: change.position.x + w(a),
287
+ top: change.position.y,
288
+ bottom: change.position.y + h(a),
289
+ width: w(a),
290
+ height: h(a)
291
+ };
292
+ let vDist = distance;
293
+ let hDist = distance;
294
+ for (const b of nodes) {
295
+ if (b.id === a.id) continue;
296
+ const B = { left: b.position.x, right: b.position.x + w(b), top: b.position.y, bottom: b.position.y + h(b) };
297
+ const ll = Math.abs(A.left - B.left);
298
+ if (ll < vDist) {
299
+ result.snapPosition.x = B.left;
300
+ result.vertical = B.left;
301
+ vDist = ll;
302
+ }
303
+ const rr = Math.abs(A.right - B.right);
304
+ if (rr < vDist) {
305
+ result.snapPosition.x = B.right - A.width;
306
+ result.vertical = B.right;
307
+ vDist = rr;
308
+ }
309
+ const lr = Math.abs(A.left - B.right);
310
+ if (lr < vDist) {
311
+ result.snapPosition.x = B.right;
312
+ result.vertical = B.right;
313
+ vDist = lr;
314
+ }
315
+ const rl = Math.abs(A.right - B.left);
316
+ if (rl < vDist) {
317
+ result.snapPosition.x = B.left - A.width;
318
+ result.vertical = B.left;
319
+ vDist = rl;
320
+ }
321
+ const tt = Math.abs(A.top - B.top);
322
+ if (tt < hDist) {
323
+ result.snapPosition.y = B.top;
324
+ result.horizontal = B.top;
325
+ hDist = tt;
326
+ }
327
+ const bb = Math.abs(A.bottom - B.bottom);
328
+ if (bb < hDist) {
329
+ result.snapPosition.y = B.bottom - A.height;
330
+ result.horizontal = B.bottom;
331
+ hDist = bb;
332
+ }
333
+ const tb = Math.abs(A.top - B.bottom);
334
+ if (tb < hDist) {
335
+ result.snapPosition.y = B.bottom;
336
+ result.horizontal = B.bottom;
337
+ hDist = tb;
338
+ }
339
+ const bt = Math.abs(A.bottom - B.top);
340
+ if (bt < hDist) {
341
+ result.snapPosition.y = B.top - A.height;
342
+ result.horizontal = B.top;
343
+ hDist = bt;
344
+ }
345
+ }
346
+ return result;
347
+ }
348
+ function HelperLines({ horizontal, vertical }) {
349
+ if (horizontal === void 0 && vertical === void 0) return null;
350
+ return /* @__PURE__ */ jsxs(ViewportPortal, { children: [
351
+ vertical !== void 0 && /* @__PURE__ */ jsx(
352
+ "div",
353
+ {
354
+ className: "ff-helper-line",
355
+ style: { position: "absolute", transform: `translateX(${vertical}px)`, top: -5e3, height: 1e4, width: 1, pointerEvents: "none" }
356
+ }
357
+ ),
358
+ horizontal !== void 0 && /* @__PURE__ */ jsx(
359
+ "div",
360
+ {
361
+ className: "ff-helper-line",
362
+ style: { position: "absolute", transform: `translateY(${horizontal}px)`, left: -5e3, width: 1e4, height: 1, pointerEvents: "none" }
363
+ }
364
+ )
365
+ ] });
366
+ }
276
367
  var DEFAULT_FIT_VIEW = { padding: 0.2 };
277
368
  var DEFAULT_EDGE_OPTIONS = {
278
369
  type: "smoothstep",
@@ -287,6 +378,9 @@ function FlowCanvas({
287
378
  height = 600,
288
379
  validateConnections = true,
289
380
  isValidConnection,
381
+ colorMode,
382
+ showHelperLines = false,
383
+ onNodesChange,
290
384
  toolbar,
291
385
  nodeTypes,
292
386
  edgeTypes,
@@ -313,31 +407,66 @@ function FlowCanvas({
313
407
  [edgeTypes]
314
408
  );
315
409
  const orderedNodes = useMemo(() => sortNodesParentFirst(nodes), [nodes]);
316
- return /* @__PURE__ */ jsxs("div", { className: ["ff-canvas", className ?? ""].filter(Boolean).join(" "), style: { height, ...style }, children: [
317
- toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
318
- /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
319
- index,
320
- {
321
- nodes: orderedNodes,
322
- edges,
323
- nodeTypes: mergedNodeTypes,
324
- edgeTypes: mergedEdgeTypes,
325
- fitView: true,
326
- fitViewOptions: DEFAULT_FIT_VIEW,
327
- defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
328
- proOptions: { hideAttribution: true },
329
- zoomActivationKeyCode: "Shift",
330
- preventScrolling: false,
331
- isValidConnection: resolvedIsValidConnection,
332
- ...rest,
333
- children: [
334
- background !== "none" && /* @__PURE__ */ jsx(Background, { variant: background, gap: 20, size: 1, color: "rgba(0,0,0,0.18)" }),
335
- showControls && /* @__PURE__ */ jsx(Controls, { className: "ff-controls", position: "bottom-right" }),
336
- showMinimap && /* @__PURE__ */ jsx(MiniMap, { className: "ff-minimap", pannable: true, zoomable: true })
337
- ]
410
+ const [helperLines, setHelperLines] = useState({});
411
+ const handleNodesChange = useCallback(
412
+ (changes) => {
413
+ if (showHelperLines) {
414
+ const pos = changes.filter((c) => c.type === "position" && c.position);
415
+ if (pos.length === 1 && pos[0].dragging) {
416
+ const lines = getHelperLines(pos[0], nodesRef.current);
417
+ if (lines.snapPosition.x !== void 0) pos[0].position.x = lines.snapPosition.x;
418
+ if (lines.snapPosition.y !== void 0) pos[0].position.y = lines.snapPosition.y;
419
+ setHelperLines({ horizontal: lines.horizontal, vertical: lines.vertical });
420
+ } else {
421
+ setHelperLines({});
422
+ }
338
423
  }
339
- ) })
340
- ] });
424
+ onNodesChange?.(changes);
425
+ },
426
+ [showHelperLines, onNodesChange]
427
+ );
428
+ return /* @__PURE__ */ jsxs(
429
+ "div",
430
+ {
431
+ className: [
432
+ "ff-canvas",
433
+ // colorMode drives BOTH react-flow's chrome (below) and our `ff-` styles
434
+ // via the shared `.dark` class / light opt-out — one theme signal.
435
+ colorMode === "dark" ? "dark" : "",
436
+ colorMode === "light" ? "ff-canvas--light" : "",
437
+ className ?? ""
438
+ ].filter(Boolean).join(" "),
439
+ style: { height, ...style },
440
+ children: [
441
+ toolbar && /* @__PURE__ */ jsx("div", { className: "ff-canvas__toolbar", children: toolbar }),
442
+ /* @__PURE__ */ jsx("div", { className: "ff-canvas__surface", children: /* @__PURE__ */ jsxs(
443
+ index,
444
+ {
445
+ nodes: orderedNodes,
446
+ edges,
447
+ onNodesChange: handleNodesChange,
448
+ colorMode,
449
+ nodeTypes: mergedNodeTypes,
450
+ edgeTypes: mergedEdgeTypes,
451
+ fitView: true,
452
+ fitViewOptions: DEFAULT_FIT_VIEW,
453
+ defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
454
+ proOptions: { hideAttribution: true },
455
+ zoomActivationKeyCode: "Shift",
456
+ preventScrolling: false,
457
+ isValidConnection: resolvedIsValidConnection,
458
+ ...rest,
459
+ children: [
460
+ background !== "none" && /* @__PURE__ */ jsx(Background, { variant: background, gap: 20, size: 1, color: "rgba(0,0,0,0.18)" }),
461
+ showControls && /* @__PURE__ */ jsx(Controls, { className: "ff-controls", position: "bottom-right" }),
462
+ showMinimap && /* @__PURE__ */ jsx(MiniMap, { className: "ff-minimap", pannable: true, zoomable: true }),
463
+ showHelperLines && /* @__PURE__ */ jsx(HelperLines, { horizontal: helperLines.horizontal, vertical: helperLines.vertical })
464
+ ]
465
+ }
466
+ ) })
467
+ ]
468
+ }
469
+ );
341
470
  }
342
471
  var CATEGORY_ORDER = ["trigger", "logic", "data", "ai", "io", "human", "output", "layout", "custom"];
343
472
  var CATEGORY_LABELS = {
@@ -993,8 +1122,11 @@ function FlowEditorInner({
993
1122
  useEffect(() => onNodeKindsChanged(() => force((n) => n + 1)), []);
994
1123
  const nodeTypes = useMemo(() => buildNodeTypes(), [listNodeKinds().length]);
995
1124
  const renderedNodes = useMemo(
996
- () => applyStatusesToNodes(flow.nodes, runner.statuses, runner.statusText),
997
- [flow.nodes, runner.statuses, runner.statusText]
1125
+ () => applyOutputsToNodes(
1126
+ applyStatusesToNodes(flow.nodes, runner.statuses, runner.statusText),
1127
+ runner.outputs
1128
+ ),
1129
+ [flow.nodes, runner.statuses, runner.statusText, runner.outputs]
998
1130
  );
999
1131
  const [selectedId, setSelectedId] = useState(null);
1000
1132
  const selected = useMemo(() => flow.nodes.find((n) => n.id === selectedId) ?? null, [flow.nodes, selectedId]);
@@ -1178,10 +1310,10 @@ function FlowEditorInner({
1178
1310
  (orientation = "horizontal", title) => {
1179
1311
  const vertical = orientation === "vertical";
1180
1312
  const lanes = flow.nodes.filter(isLaneNode);
1181
- const w = vertical ? 280 : 680;
1182
- const h = vertical ? 480 : 168;
1313
+ const w2 = vertical ? 280 : 680;
1314
+ const h2 = vertical ? 480 : 168;
1183
1315
  const end = lanes.reduce(
1184
- (m, l) => Math.max(m, vertical ? l.position.x + (l.width ?? w) : l.position.y + (l.height ?? h)),
1316
+ (m, l) => Math.max(m, vertical ? l.position.x + (l.width ?? w2) : l.position.y + (l.height ?? h2)),
1185
1317
  0
1186
1318
  );
1187
1319
  const id = newNodeId();
@@ -1190,8 +1322,8 @@ function FlowEditorInner({
1190
1322
  id,
1191
1323
  type: "@particle-academy/lane",
1192
1324
  position: vertical ? { x: lanes.length ? end + 12 : 0, y: 0 } : { x: 0, y: lanes.length ? end + 12 : 0 },
1193
- width: w,
1194
- height: h,
1325
+ width: w2,
1326
+ height: h2,
1195
1327
  data: { kind: "@particle-academy/lane", label: name, config: { title: name, orientation } }
1196
1328
  };
1197
1329
  flow.setNodes((all) => [...all, node]);