@octaviaflow/core 3.0.18-beta.8 → 3.0.18-beta.9

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.
Files changed (37) hide show
  1. package/dist/chunk-CEUP4NK2.js +2850 -0
  2. package/dist/chunk-CEUP4NK2.js.map +1 -0
  3. package/dist/chunk-EERNYLFL.js +2860 -0
  4. package/dist/chunk-EERNYLFL.js.map +1 -0
  5. package/dist/chunk-EKFDJX4G.js +2872 -0
  6. package/dist/chunk-EKFDJX4G.js.map +1 -0
  7. package/dist/chunk-GJA3GJUZ.js +2844 -0
  8. package/dist/chunk-GJA3GJUZ.js.map +1 -0
  9. package/dist/chunk-J7YASALS.js +2859 -0
  10. package/dist/chunk-J7YASALS.js.map +1 -0
  11. package/dist/chunk-JIEUYBQT.js +2658 -0
  12. package/dist/chunk-JIEUYBQT.js.map +1 -0
  13. package/dist/chunk-S2SSBMWJ.js +2658 -0
  14. package/dist/chunk-S2SSBMWJ.js.map +1 -0
  15. package/dist/chunk-WEPTBLWX.js +2847 -0
  16. package/dist/chunk-WEPTBLWX.js.map +1 -0
  17. package/dist/chunk-WG4ZQMPS.js +2844 -0
  18. package/dist/chunk-WG4ZQMPS.js.map +1 -0
  19. package/dist/chunk-XEPEBHAW.js +2808 -0
  20. package/dist/chunk-XEPEBHAW.js.map +1 -0
  21. package/dist/chunk-XG2OYFX6.js +2925 -0
  22. package/dist/chunk-XG2OYFX6.js.map +1 -0
  23. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts.map +1 -1
  24. package/dist/index.cjs +736 -441
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +6 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/styles.css +1 -1
  29. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +27 -0
  30. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -1
  31. package/dist/workflow/components/kinds/index.d.ts +4 -0
  32. package/dist/workflow/components/kinds/index.d.ts.map +1 -1
  33. package/dist/workflow.cjs +433 -312
  34. package/dist/workflow.cjs.map +1 -1
  35. package/dist/workflow.js +5 -149
  36. package/dist/workflow.js.map +1 -1
  37. package/package.json +1 -1
package/dist/workflow.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  GroupNode,
13
13
  Handle,
14
14
  HttpRequestNode,
15
+ NodeResizer,
15
16
  OutputNode,
16
17
  ParallelNode,
17
18
  StickyNode,
@@ -49,7 +50,7 @@ import {
49
50
  useSelection,
50
51
  useViewport,
51
52
  useViewportOrNull
52
- } from "./chunk-IOKUV7FD.js";
53
+ } from "./chunk-XG2OYFX6.js";
53
54
  import {
54
55
  cn
55
56
  } from "./chunk-ZAUUGK2Y.js";
@@ -609,153 +610,8 @@ function ConfigPanel({
609
610
  );
610
611
  }
611
612
 
612
- // src/workflow/components/NodeResizer/NodeResizer.tsx
613
- import { useRef as useRef4 } from "react";
614
- import { jsx as jsx2 } from "react/jsx-runtime";
615
- function NodeResizer({
616
- isVisible,
617
- minWidth = 80,
618
- minHeight = 60,
619
- maxWidth,
620
- maxHeight,
621
- keepAspectRatio = false,
622
- onResize,
623
- onResizeEnd,
624
- color
625
- }) {
626
- const { node, selected } = useFlowNodeContext();
627
- const viewport = useViewport();
628
- const flow = useFlow();
629
- const dragRef = useRef4(null);
630
- const show = isVisible ?? selected;
631
- if (!show) return null;
632
- const beginResize = (e, corner) => {
633
- e.preventDefault();
634
- e.stopPropagation();
635
- e.target.setPointerCapture(e.pointerId);
636
- const w = node.width ?? DEFAULT_NODE_WIDTH;
637
- const h = node.height ?? DEFAULT_NODE_HEIGHT;
638
- dragRef.current = {
639
- pointerId: e.pointerId,
640
- corner,
641
- startClientX: e.clientX,
642
- startClientY: e.clientY,
643
- startWidth: w,
644
- startHeight: h,
645
- startX: node.position.x,
646
- startY: node.position.y,
647
- aspect: w / Math.max(1, h)
648
- };
649
- };
650
- const onMove = (e) => {
651
- const drag = dragRef.current;
652
- if (!drag || drag.pointerId !== e.pointerId) return;
653
- const dx = (e.clientX - drag.startClientX) / viewport.zoom;
654
- const dy = (e.clientY - drag.startClientY) / viewport.zoom;
655
- let nextW = drag.startWidth;
656
- let nextH = drag.startHeight;
657
- let nextX = drag.startX;
658
- let nextY = drag.startY;
659
- switch (drag.corner) {
660
- case "se":
661
- nextW = drag.startWidth + dx;
662
- nextH = drag.startHeight + dy;
663
- break;
664
- case "sw":
665
- nextW = drag.startWidth - dx;
666
- nextH = drag.startHeight + dy;
667
- nextX = drag.startX + dx;
668
- break;
669
- case "ne":
670
- nextW = drag.startWidth + dx;
671
- nextH = drag.startHeight - dy;
672
- nextY = drag.startY + dy;
673
- break;
674
- case "nw":
675
- nextW = drag.startWidth - dx;
676
- nextH = drag.startHeight - dy;
677
- nextX = drag.startX + dx;
678
- nextY = drag.startY + dy;
679
- break;
680
- }
681
- if (keepAspectRatio) {
682
- nextH = nextW / drag.aspect;
683
- if (drag.corner === "nw" || drag.corner === "ne") {
684
- nextY = drag.startY + (drag.startHeight - nextH);
685
- }
686
- }
687
- nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);
688
- nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);
689
- flow.updateNode(node.id, {
690
- width: nextW,
691
- height: nextH,
692
- position: { x: nextX, y: nextY }
693
- });
694
- onResize?.({ width: nextW, height: nextH });
695
- };
696
- const onUp = (e) => {
697
- if (dragRef.current?.pointerId === e.pointerId) {
698
- const cur = flow.getNode(node.id);
699
- if (cur) {
700
- onResizeEnd?.({
701
- width: cur.width ?? DEFAULT_NODE_WIDTH,
702
- height: cur.height ?? DEFAULT_NODE_HEIGHT
703
- });
704
- }
705
- dragRef.current = null;
706
- }
707
- };
708
- const handleColor = color ?? "var(--ods-accent)";
709
- const handleStyle = (corner) => {
710
- const base = {
711
- position: "absolute",
712
- width: 12,
713
- height: 12,
714
- background: "var(--ods-surface-canvas)",
715
- border: `2px solid ${handleColor}`,
716
- borderRadius: 2,
717
- cursor: cursorFor(corner),
718
- touchAction: "none",
719
- // Place each handle so its CENTRE sits on the corresponding corner.
720
- transform: "translate(-50%, -50%)"
721
- };
722
- switch (corner) {
723
- case "nw":
724
- return { ...base, top: 0, left: 0 };
725
- case "ne":
726
- return { ...base, top: 0, left: "100%" };
727
- case "sw":
728
- return { ...base, top: "100%", left: 0 };
729
- case "se":
730
- return { ...base, top: "100%", left: "100%" };
731
- }
732
- };
733
- return /* @__PURE__ */ jsx2("div", { className: cn("ods-node-resizer"), "data-flow-no-drag": "true", children: ["nw", "ne", "sw", "se"].map((corner) => /* @__PURE__ */ jsx2(
734
- "div",
735
- {
736
- style: handleStyle(corner),
737
- onPointerDown: (e) => beginResize(e, corner),
738
- onPointerMove: onMove,
739
- onPointerUp: onUp,
740
- onPointerCancel: onUp,
741
- "aria-label": `Resize ${corner}`
742
- },
743
- corner
744
- )) });
745
- }
746
- function cursorFor(corner) {
747
- switch (corner) {
748
- case "nw":
749
- case "se":
750
- return "nwse-resize";
751
- case "ne":
752
- case "sw":
753
- return "nesw-resize";
754
- }
755
- }
756
-
757
613
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx
758
- import { jsx as jsx3 } from "react/jsx-runtime";
614
+ import { jsx as jsx2 } from "react/jsx-runtime";
759
615
  function NodeToolbar({
760
616
  isVisible,
761
617
  position = "top",
@@ -770,7 +626,7 @@ function NodeToolbar({
770
626
  const show = isVisible ?? node.selected;
771
627
  if (!show) return null;
772
628
  const inverseScale = 1 / viewport.zoom;
773
- return /* @__PURE__ */ jsx3(
629
+ return /* @__PURE__ */ jsx2(
774
630
  "div",
775
631
  {
776
632
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -782,7 +638,7 @@ function NodeToolbar({
782
638
  onPointerDown: (e) => e.stopPropagation(),
783
639
  onMouseDown: (e) => e.stopPropagation(),
784
640
  onClick: (e) => e.stopPropagation(),
785
- children: /* @__PURE__ */ jsx3(
641
+ children: /* @__PURE__ */ jsx2(
786
642
  "div",
787
643
  {
788
644
  className: "ods-node-toolbar__inner",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/workflow/hooks/useAutoLayout.ts","../src/workflow/hooks/useFlowState.ts","../src/workflow/layout/dagre.ts","../src/workflow/layout/elk.ts","../src/workflow/components/ConfigPanel/ConfigPanel.tsx","../src/workflow/components/NodeResizer/NodeResizer.tsx","../src/workflow/components/NodeToolbar/NodeToolbar.tsx","../src/workflow/utils/collapse.ts"],"sourcesContent":["// ════════════════════════════════════════════════════════════════════════\n// useAutoLayout — run a layout engine and apply its output via the\n// canvas's `onNodesChange` callback.\n// ════════════════════════════════════════════════════════════════════════\n// Two trigger modes:\n//\n// 1. Imperative — call the returned `runLayout()` function (e.g. from a\n// toolbar button). This is the recommended default; users press\n// \"Auto-arrange\" and the engine runs once.\n//\n// 2. Reactive — pass `auto: true`. The hook debounces 250ms on every\n// node/edge add/remove and runs the engine. Useful for \"always\n// auto-layout\" surfaces; expensive in graphs where users drag nodes\n// around (their positions get clobbered).\n//\n// The hook does NOT subscribe to selection / drag / viewport — it watches\n// only the *shape* of the graph (node ids + edge connections). That\n// keeps reactive mode from re-running on every pointermove.\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n LayoutEngine,\n LayoutOptions,\n NodeChange,\n WorkflowEdge,\n WorkflowNode,\n} from \"../editor\";\nimport { change } from \"../store/changes\";\n\nexport interface UseAutoLayoutOptions {\n /** The engine. Required. */\n engine: LayoutEngine | null | undefined;\n /** Current nodes (controlled). */\n nodes: WorkflowNode[];\n /** Current edges (controlled). */\n edges: WorkflowEdge[];\n /** Where to send position changes. Required. */\n onNodesChange?: (changes: NodeChange[]) => void;\n /** Layout options forwarded to the engine. */\n options?: LayoutOptions;\n /** Re-run on every graph-shape change (debounced 250ms). Default false. */\n auto?: boolean;\n /** Debounce delay for `auto` mode (ms). Default 250. */\n debounceMs?: number;\n /** Called after each successful run. */\n onLayout?: (positions: { id: string; position: { x: number; y: number } }[]) => void;\n /** Called when the engine throws (network / worker / library). */\n onError?: (err: unknown) => void;\n}\n\nexport interface UseAutoLayoutResult {\n /** Manually trigger a layout pass. Returns the promise of the engine. */\n runLayout(): Promise<void>;\n /** True while a layout pass is in flight. */\n isLaying: boolean;\n}\n\n/**\n * Build a stable graph-shape signature so reactive mode only re-runs when\n * the structure (node ids + edges) changes, not on position/select/drag.\n */\nfunction signature(nodes: WorkflowNode[], edges: WorkflowEdge[]): string {\n // Sorted to be invariant under array re-ordering.\n const nodeKeys = nodes\n .map((n) => `${n.id}:${n.parentId ?? \"\"}`)\n .sort()\n .join(\"|\");\n const edgeKeys = edges\n .map((e) => `${e.id}:${e.source}>${e.target}`)\n .sort()\n .join(\"|\");\n return `${nodeKeys}::${edgeKeys}`;\n}\n\nexport function useAutoLayout({\n engine,\n nodes,\n edges,\n onNodesChange,\n options,\n auto = false,\n debounceMs = 250,\n onLayout,\n onError,\n}: UseAutoLayoutOptions): UseAutoLayoutResult {\n const [isLaying, setIsLaying] = useState(false);\n const runningRef = useRef(false);\n const optionsRef = useRef(options);\n const onLayoutRef = useRef(onLayout);\n const onErrorRef = useRef(onError);\n const onNodesChangeRef = useRef(onNodesChange);\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n useEffect(() => {\n onLayoutRef.current = onLayout;\n }, [onLayout]);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n useEffect(() => {\n onNodesChangeRef.current = onNodesChange;\n }, [onNodesChange]);\n\n // Snapshot refs of nodes/edges so the runLayout callback can read fresh\n // data without re-creating itself on every render.\n const nodesRef = useRef(nodes);\n const edgesRef = useRef(edges);\n useEffect(() => {\n nodesRef.current = nodes;\n }, [nodes]);\n useEffect(() => {\n edgesRef.current = edges;\n }, [edges]);\n\n const runLayout = useCallback(async () => {\n if (!engine) return;\n if (runningRef.current) return;\n runningRef.current = true;\n setIsLaying(true);\n try {\n const positions = await engine.layout(nodesRef.current, edgesRef.current, optionsRef.current);\n\n // Index engine output by id + remember per-node deltas so we can\n // transitively apply them to descendants (children move with the\n // parent — otherwise a parent moves and its children get stranded\n // outside the new bbox, which is the v1 subflow auto-layout bug).\n const positionedIds = new Set(positions.map((p) => p.id));\n const deltas = new Map<string, { dx: number; dy: number }>();\n\n const cs: NodeChange[] = [];\n for (const p of positions) {\n const cur = nodesRef.current.find((n) => n.id === p.id);\n if (!cur) continue;\n if (cur.position.x === p.position.x && cur.position.y === p.position.y) continue;\n cs.push(change.node.position(p.id, p.position, false));\n deltas.set(p.id, {\n dx: p.position.x - cur.position.x,\n dy: p.position.y - cur.position.y,\n });\n }\n\n // For every node the engine did NOT reposition (typically because\n // it sat in a sub-flow that the engine treated as opaque), walk up\n // the parentId chain to find the nearest moved ancestor. Shift the\n // descendant by that ancestor's delta. The first moved ancestor\n // wins — we don't sum deltas because each child's position is\n // already relative to its ancestor's old position.\n const findParent = (id: string): WorkflowNode | undefined =>\n nodesRef.current.find((x) => x.id === id);\n\n for (const n of nodesRef.current) {\n if (positionedIds.has(n.id)) continue;\n if (!n.parentId) continue;\n let cursor: string | undefined = n.parentId;\n const seen = new Set<string>();\n let delta: { dx: number; dy: number } | undefined;\n while (cursor) {\n if (seen.has(cursor)) break; // cycle guard\n seen.add(cursor);\n const found = deltas.get(cursor);\n if (found) {\n delta = found;\n break;\n }\n cursor = findParent(cursor)?.parentId;\n }\n if (!delta) continue;\n cs.push(\n change.node.position(\n n.id,\n { x: n.position.x + delta.dx, y: n.position.y + delta.dy },\n false,\n ),\n );\n }\n\n if (cs.length) onNodesChangeRef.current?.(cs);\n onLayoutRef.current?.(positions);\n } catch (err) {\n onErrorRef.current?.(err);\n } finally {\n runningRef.current = false;\n setIsLaying(false);\n }\n }, [engine]);\n\n // Reactive auto-mode — debounce on graph-shape changes.\n const sig = signature(nodes, edges);\n const sigRef = useRef(sig);\n useEffect(() => {\n if (!auto) return;\n if (sigRef.current === sig) return; // first render after toggling auto\n sigRef.current = sig;\n const timer = setTimeout(() => void runLayout(), debounceMs);\n return () => clearTimeout(timer);\n }, [sig, auto, debounceMs, runLayout]);\n\n return { runLayout, isLaying };\n}\n","// ════════════════════════════════════════════════════════════════════════\n// useFlowState — the uncontrolled-mode convenience.\n// ════════════════════════════════════════════════════════════════════════\n// Consumers that don't want to thread state through their own reducer\n// can use this hook and spread the result into <FlowCanvas>:\n//\n// const flow = useFlowState({ initialNodes, initialEdges });\n// <FlowCanvas {...flow} />\n//\n// In addition to the canonical change-stream contract, `useFlowState`\n// exposes per-node helpers — `updateNodeData(id, patch)` and the\n// companion `useNodeData(state, id)` selector — so individual node\n// updates can be expressed without re-mapping the entire nodes array\n// in caller code. Each helper writes ONLY the targeted node's slice;\n// React.memo'd kind components for other nodes never re-render.\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { applyEdgeChanges, applyNodeChanges } from \"../store/changes\";\n\nexport interface UseFlowStateOptions<TNData = unknown, TEData = unknown> {\n initialNodes?: WorkflowNode<TNData>[];\n initialEdges?: WorkflowEdge<TEData>[];\n}\n\n/**\n * Patch type for `updateNodeData` — accepts a partial object that gets\n * shallow-merged, or a producer function `(prev) => next`.\n */\nexport type NodeDataPatch<TNData> =\n | Partial<TNData>\n | ((prev: TNData | undefined) => TNData | undefined);\n\nexport interface FlowStateBag<TNData = unknown, TEData = unknown> {\n nodes: WorkflowNode<TNData>[];\n edges: WorkflowEdge<TEData>[];\n onNodesChange: (changes: NodeChange<TNData>[]) => void;\n onEdgesChange: (changes: EdgeChange<TEData>[]) => void;\n setNodes: (\n update: WorkflowNode<TNData>[] | ((prev: WorkflowNode<TNData>[]) => WorkflowNode<TNData>[]),\n ) => void;\n setEdges: (\n update: WorkflowEdge<TEData>[] | ((prev: WorkflowEdge<TEData>[]) => WorkflowEdge<TEData>[]),\n ) => void;\n /**\n * Update the `data` slice of a single node by id. Only that node's\n * reference changes — every other node keeps its previous reference,\n * so React.memo'd kind components for unrelated nodes do not re-render.\n * No-ops when `id` is not present in the current nodes array.\n */\n updateNodeData: (id: string, patch: NodeDataPatch<TNData>) => void;\n /**\n * Read a single node by id from the latest snapshot. Returns undefined\n * if the id is unknown. Useful inside imperative callbacks that need\n * the freshest value without subscribing to re-renders.\n */\n getNode: (id: string) => WorkflowNode<TNData> | undefined;\n}\n\nexport function useFlowState<TNData = unknown, TEData = unknown>(\n options: UseFlowStateOptions<TNData, TEData> = {},\n): FlowStateBag<TNData, TEData> {\n const [nodes, setNodes] = useState<WorkflowNode<TNData>[]>(options.initialNodes ?? []);\n const [edges, setEdges] = useState<WorkflowEdge<TEData>[]>(options.initialEdges ?? []);\n\n // Ref-mirror of the latest nodes array. `getNode` reads through it so\n // imperative callers see fresh values without depending on render scope.\n const nodesRef = useRef(nodes);\n nodesRef.current = nodes;\n\n const onNodesChange = useCallback((changes: NodeChange<TNData>[]) => {\n setNodes((prev) => applyNodeChanges(prev, changes));\n }, []);\n\n const onEdgesChange = useCallback((changes: EdgeChange<TEData>[]) => {\n setEdges((prev) => applyEdgeChanges(prev, changes));\n }, []);\n\n const updateNodeData = useCallback((id: string, patch: NodeDataPatch<TNData>) => {\n setNodes((prev) => {\n let touched = false;\n const next: WorkflowNode<TNData>[] = prev.map((n) => {\n if (n.id !== id) return n;\n const nextData =\n typeof patch === \"function\"\n ? (patch as (p: TNData | undefined) => TNData | undefined)(n.data)\n : ({ ...(n.data ?? ({} as TNData)), ...patch } as TNData);\n if (Object.is(nextData, n.data)) return n;\n touched = true;\n return { ...n, data: nextData as TNData };\n });\n return touched ? next : prev;\n });\n }, []);\n\n const getNode = useCallback((id: string) => nodesRef.current.find((n) => n.id === id), []);\n\n return useMemo(\n () => ({\n nodes,\n edges,\n onNodesChange,\n onEdgesChange,\n setNodes,\n setEdges,\n updateNodeData,\n getNode,\n }),\n [nodes, edges, onNodesChange, onEdgesChange, updateNodeData, getNode],\n );\n}\n","// ════════════════════════════════════════════════════════════════════════\n// Dagre adapter — wraps a consumer-supplied `dagre` module.\n// ════════════════════════════════════════════════════════════════════════\n// Usage:\n//\n// import dagre from \"dagre\";\n// import { createDagreEngine } from \"@octaviaflow/core/workflow\";\n//\n// const engine = createDagreEngine(dagre);\n// useAutoLayout({ engine, onLayout });\n//\n// Dagre is synchronous — `layout()` blocks the main thread. Good for\n// small graphs (≤ ~200 nodes). For larger graphs, see `createElkEngine`.\n\nimport type { LayoutEngine, LayoutOptions, Position, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from \"../utils/geometry\";\nimport type { DagreLike } from \"./types\";\n\nexport interface CreateDagreEngineOptions {\n /** Default spacing between nodes within a rank (default 60). */\n nodeSpacing?: number;\n /** Default spacing between ranks (default 80). */\n rankSpacing?: number;\n /** Default direction — TB, BT, LR, RL. Defaults to TB. */\n direction?: LayoutOptions[\"direction\"];\n}\n\n/**\n * Build a `LayoutEngine` backed by a consumer-supplied `dagre` module.\n *\n * `dagre` can be passed as either the default export or the namespace\n * import (`import * as dagre from \"dagre\"`). The adapter probes both\n * shapes so consumers don't have to think about it.\n */\nexport function createDagreEngine(\n dagre:\n | DagreLike\n | { default: DagreLike }\n | { graphlib: DagreLike[\"graphlib\"]; layout: DagreLike[\"layout\"] },\n defaults: CreateDagreEngineOptions = {},\n): LayoutEngine {\n const lib = resolveDagre(dagre);\n const defaultDir = defaults.direction ?? \"TB\";\n const defaultNodeSpacing = defaults.nodeSpacing ?? 60;\n const defaultRankSpacing = defaults.rankSpacing ?? 80;\n\n return {\n name: \"dagre\",\n async layout(nodes: WorkflowNode[], edges: WorkflowEdge[], options?: LayoutOptions) {\n const direction = options?.direction ?? defaultDir;\n const nodesep = options?.nodeSpacing ?? defaultNodeSpacing;\n const ranksep = options?.rankSpacing ?? defaultRankSpacing;\n\n // Dagre requires every node to have width/height. Use the measured\n // values from the canvas; fall back to our default footprint.\n const g = new lib.graphlib.Graph({ multigraph: true, compound: true });\n g.setGraph({\n rankdir: direction,\n nodesep,\n ranksep,\n ...(options?.engine ?? {}),\n });\n g.setDefaultEdgeLabel(() => ({}));\n\n for (const n of nodes) {\n if (n.hidden) continue;\n g.setNode(n.id, {\n width: n.width ?? DEFAULT_NODE_WIDTH,\n height: n.height ?? DEFAULT_NODE_HEIGHT,\n ...(n.parentId ? { parent: n.parentId } : {}),\n });\n // Compound graph: register the parent–child relationship.\n if (n.parentId) {\n // `setParent` is on the graph but not strongly typed in our\n // narrow surface — call via index access. dagre tolerates a\n // missing parent node (it auto-creates).\n (g as unknown as { setParent: (id: string, parent: string) => void }).setParent(\n n.id,\n n.parentId,\n );\n }\n }\n\n for (const e of edges) {\n if (e.hidden) continue;\n g.setEdge(e.source, e.target, { id: e.id });\n }\n\n lib.layout(g);\n\n // Dagre stores node centres; our model uses top-left. Translate.\n const result: { id: string; position: Position }[] = [];\n for (const id of g.nodes()) {\n const out = g.node(id);\n if (!out) continue;\n result.push({\n id,\n position: { x: out.x - out.width / 2, y: out.y - out.height / 2 },\n });\n }\n return result;\n },\n };\n}\n\nfunction resolveDagre(input: unknown): DagreLike {\n const asAny = input as Partial<DagreLike> & { default?: DagreLike };\n if (asAny && typeof asAny.layout === \"function\" && asAny.graphlib?.Graph) {\n return asAny as DagreLike;\n }\n if (asAny?.default && typeof asAny.default.layout === \"function\") {\n return asAny.default;\n }\n throw new Error(\n \"[@octaviaflow/core/workflow] createDagreEngine: input does not look like the dagre module. \" +\n \"Pass `dagre` (the default export) or `import * as dagre from 'dagre'`.\",\n );\n}\n","// ════════════════════════════════════════════════════════════════════════\n// ELK adapter — wraps a consumer-supplied `elkjs` instance.\n// ════════════════════════════════════════════════════════════════════════\n// Usage (Web Worker — recommended for large graphs):\n//\n// import ELK from \"elkjs/lib/elk-api\";\n// import { createElkEngine } from \"@octaviaflow/core/workflow\";\n//\n// const elk = new ELK({ workerUrl: \"/elk-worker.min.js\" });\n// const engine = createElkEngine(elk);\n//\n// Usage (in-process, no worker):\n//\n// import ELK from \"elkjs/lib/elk.bundled\";\n// const elk = new ELK();\n// const engine = createElkEngine(elk);\n//\n// ELK supports compound graphs natively — nodes with `parentId` get\n// nested into the right `children[]` so subflow layouts just work.\n\nimport type { LayoutEngine, LayoutOptions, Position, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from \"../utils/geometry\";\nimport type { ElkEdge, ElkGraph, ElkLike } from \"./types\";\n\nexport interface CreateElkEngineOptions {\n /** Default direction — TB / BT / LR / RL. Defaults to TB. */\n direction?: LayoutOptions[\"direction\"];\n /** Default node-to-node spacing within a layer (default 60). */\n nodeSpacing?: number;\n /** Default layer-to-layer spacing (default 80). */\n rankSpacing?: number;\n /** Override the algorithm. Defaults to `layered` (Sugiyama). */\n algorithm?:\n | \"layered\"\n | \"mrtree\"\n | \"force\"\n | \"stress\"\n | \"radial\"\n | \"disco\"\n | \"rectpacking\"\n | \"box\";\n}\n\nconst ELK_DIRECTION: Record<NonNullable<LayoutOptions[\"direction\"]>, string> = {\n TB: \"DOWN\",\n BT: \"UP\",\n LR: \"RIGHT\",\n RL: \"LEFT\",\n};\n\n/**\n * Build a `LayoutEngine` backed by a consumer-supplied `elkjs` instance.\n *\n * The consumer is responsible for constructing the ELK instance (which\n * may or may not run in a Web Worker — that's a `workerUrl` constructor\n * option on their end). Our adapter just calls `elk.layout(graph)` and\n * patches positions back.\n */\nexport function createElkEngine(elk: ElkLike, defaults: CreateElkEngineOptions = {}): LayoutEngine {\n const algorithm = defaults.algorithm ?? \"layered\";\n const defaultDir = defaults.direction ?? \"TB\";\n const defaultNodeSpacing = defaults.nodeSpacing ?? 60;\n const defaultRankSpacing = defaults.rankSpacing ?? 80;\n\n return {\n name: \"elk\",\n async layout(nodes: WorkflowNode[], edges: WorkflowEdge[], options?: LayoutOptions) {\n const direction = options?.direction ?? defaultDir;\n const nodeSpacing = options?.nodeSpacing ?? defaultNodeSpacing;\n const rankSpacing = options?.rankSpacing ?? defaultRankSpacing;\n\n // Build a nested ELK graph respecting `parentId` so compound layout\n // works. We walk the node list once into a parent → children map,\n // then recursively build ElkGraph.children[].\n const childrenByParent = new Map<string | undefined, WorkflowNode[]>();\n for (const n of nodes) {\n if (n.hidden) continue;\n const key = n.parentId ?? undefined;\n const list = childrenByParent.get(key);\n if (list) list.push(n);\n else childrenByParent.set(key, [n]);\n }\n\n const buildChildren = (parentId: string | undefined): ElkGraph[] => {\n const list = childrenByParent.get(parentId) ?? [];\n return list.map<ElkGraph>((n) => ({\n id: n.id,\n width: n.width ?? DEFAULT_NODE_WIDTH,\n height: n.height ?? DEFAULT_NODE_HEIGHT,\n children: childrenByParent.has(n.id) ? buildChildren(n.id) : undefined,\n layoutOptions: childrenByParent.has(n.id)\n ? {\n \"elk.algorithm\": algorithm,\n \"elk.direction\": ELK_DIRECTION[direction],\n \"elk.spacing.nodeNode\": String(nodeSpacing),\n \"elk.layered.spacing.nodeNodeBetweenLayers\": String(rankSpacing),\n }\n : undefined,\n }));\n };\n\n const elkEdges: ElkEdge[] = edges\n .filter((e) => !e.hidden)\n .map((e) => ({\n id: e.id,\n sources: [e.source],\n targets: [e.target],\n }));\n\n const graph: ElkGraph = {\n id: \"root\",\n layoutOptions: {\n \"elk.algorithm\": algorithm,\n \"elk.direction\": ELK_DIRECTION[direction],\n \"elk.spacing.nodeNode\": String(nodeSpacing),\n \"elk.layered.spacing.nodeNodeBetweenLayers\": String(rankSpacing),\n ...Object.fromEntries(\n Object.entries(options?.engine ?? {}).map(([k, v]) => [k, String(v)]),\n ),\n },\n children: buildChildren(undefined),\n edges: elkEdges,\n };\n\n const laidOut = await elk.layout(graph);\n\n // Walk the resulting graph and collect (id, position). ELK returns\n // positions relative to the parent — we walk parents to flatten\n // back to absolute flow-space coordinates (matches our ADR-003\n // \"positions are always absolute\" rule).\n const result: { id: string; position: Position }[] = [];\n const walk = (g: ElkGraph, ox: number, oy: number) => {\n const ax = ox + (g.x ?? 0);\n const ay = oy + (g.y ?? 0);\n if (g.id !== \"root\") {\n result.push({ id: g.id, position: { x: ax, y: ay } });\n }\n if (g.children) {\n for (const c of g.children) walk(c, ax, ay);\n }\n };\n walk(laidOut, 0, 0);\n return result;\n },\n };\n}\n","\"use client\";\n\n// ════════════════════════════════════════════════════════════════════════\n// ConfigPanel v2 — right-side editor for the selected node.\n// ════════════════════════════════════════════════════════════════════════\n// Improvements over v1:\n// - Tabbed body (config / input / output schema; consumer can override)\n// - Resizable width (drag the left edge)\n// - Pinned vs floating modes\n// - Breadcrumb header showing node kind → title\n// - Footer with save-state pill (dirty / saving / saved)\n// - Slot for inline status banner (validation errors, etc.)\n// - Inline-edit-the-title affordance (double-click)\n// - Sticky footer with save / cancel buttons\n\nimport {\n type CSSProperties,\n type PointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { cn } from \"../../../utils/cn\";\n\nexport type ConfigPanelSaveState = \"clean\" | \"dirty\" | \"saving\" | \"saved\" | \"error\";\n\nexport interface ConfigPanelTab {\n id: string;\n label: ReactNode;\n /** Optional badge (e.g. count of errors). */\n badge?: ReactNode;\n /** When true, the tab is rendered but disabled. */\n disabled?: boolean;\n content: ReactNode;\n}\n\nexport interface ConfigPanelProps {\n open?: boolean;\n /** Title shown in the header. Double-click opens the inline editor. */\n title?: string;\n /** Optional breadcrumb prefix (e.g. \"Action\"). */\n kindLabel?: string;\n /** Optional icon rendered before the title. */\n icon?: ReactNode;\n /** Inline status banner — error / warning / hint, shown at the top of the body. */\n banner?: ReactNode;\n /** Tab definitions. When omitted, `children` renders directly (no tabs). */\n tabs?: ConfigPanelTab[];\n /** Initial active tab. Controlled if `onTabChange` is provided. */\n activeTab?: string;\n defaultActiveTab?: string;\n onTabChange?: (id: string) => void;\n /** Save-state pill in the footer. */\n saveState?: ConfigPanelSaveState;\n /** Fires on the title editor's commit. Omit to disable title editing. */\n onTitleChange?: (next: string) => void;\n onClose?: () => void;\n onSave?: () => void;\n onCancel?: () => void;\n /** Resizable width — pass `resizable: false` to lock. */\n resizable?: boolean;\n /** Initial width (px). Defaults to 360. */\n defaultWidth?: number;\n /** Lower clamp during resize. Defaults to 280. */\n minWidth?: number;\n /**\n * Upper clamp during resize. Defaults to 720. The panel ALSO clamps\n * to its parent container's measured width minus\n * `maxWidthContainerInset` (default 80 px) so the panel can never\n * cover the whole canvas — whichever is smaller wins.\n */\n maxWidth?: number;\n /**\n * How much of the parent container to leave visible at the panel's\n * widest. Subtracted from the parent's clientWidth before clamping\n * `maxWidth`. Defaults to 80 px.\n */\n maxWidthContainerInset?: number;\n /** \"pinned\" sits flush against the canvas; \"floating\" raises with shadow. */\n variant?: \"pinned\" | \"floating\";\n /** Body slot used only when `tabs` is omitted. */\n children?: ReactNode;\n /** Custom footer slot — replaces the default save/cancel buttons. */\n footer?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SAVE_STATE_LABEL: Record<ConfigPanelSaveState, string> = {\n clean: \"Saved\",\n dirty: \"Unsaved changes\",\n saving: \"Saving…\",\n saved: \"Saved\",\n error: \"Save failed\",\n};\n\nconst SAVE_STATE_DOT: Record<ConfigPanelSaveState, string> = {\n clean: \"var(--ods-text-tertiary)\",\n dirty: \"var(--ods-status-warning, #d97706)\",\n saving: \"var(--ods-status-running, #2563eb)\",\n saved: \"var(--ods-status-success, #16a34a)\",\n error: \"var(--ods-status-failed, #dc2626)\",\n};\n\nexport function ConfigPanel({\n open = true,\n title,\n kindLabel,\n icon,\n banner,\n tabs,\n activeTab: controlledActive,\n defaultActiveTab,\n onTabChange,\n saveState = \"clean\",\n onTitleChange,\n onClose,\n onSave,\n onCancel,\n resizable = true,\n defaultWidth = 360,\n minWidth = 280,\n maxWidth = 720,\n maxWidthContainerInset = 80,\n variant = \"pinned\",\n children,\n footer,\n className,\n style,\n}: ConfigPanelProps) {\n const [width, setWidth] = useState(defaultWidth);\n // Effective upper bound — `min(maxWidth, parentClientWidth - inset)`.\n // Recomputed on every parent resize so the panel can never cover the\n // canvas. Starts at `maxWidth` so the first paint is sane; the\n // ResizeObserver below tightens it as soon as the layout settles.\n const [effectiveMax, setEffectiveMax] = useState(maxWidth);\n const panelRef = useRef<HTMLElement>(null);\n useEffect(() => {\n const el = panelRef.current;\n if (!el) return;\n const parent = el.parentElement;\n if (!parent || typeof ResizeObserver === \"undefined\") return;\n const update = () => {\n const room = parent.clientWidth - maxWidthContainerInset;\n const next = Math.max(minWidth, Math.min(maxWidth, room));\n setEffectiveMax(next);\n // If the current width exceeds the new ceiling (window shrank),\n // snap the panel down so it remains inside the canvas.\n setWidth((w) => (w > next ? next : w));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(parent);\n return () => ro.disconnect();\n }, [maxWidth, minWidth, maxWidthContainerInset]);\n const [internalActive, setInternalActive] = useState<string | undefined>(\n defaultActiveTab ?? tabs?.[0]?.id,\n );\n const activeTabId = controlledActive ?? internalActive;\n const selectTab = (id: string) => {\n if (controlledActive === undefined) setInternalActive(id);\n onTabChange?.(id);\n };\n\n // ── Title editor ────────────────────────────────────────────────────\n const [editingTitle, setEditingTitle] = useState(false);\n const [draftTitle, setDraftTitle] = useState(title ?? \"\");\n useEffect(() => {\n if (!editingTitle) setDraftTitle(title ?? \"\");\n }, [title, editingTitle]);\n const commitTitle = (next: string) => {\n setEditingTitle(false);\n if (next !== title) onTitleChange?.(next);\n };\n const titleKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n commitTitle(draftTitle);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setEditingTitle(false);\n setDraftTitle(title ?? \"\");\n }\n };\n\n // ── Resize handle ───────────────────────────────────────────────────\n // We capture pointer events on the drag handle; the panel width is\n // updated on every pointermove. Width is clamped to [min, max].\n const resizeRef = useRef<{ pointerId: number; startX: number; startWidth: number } | null>(null);\n const onResizeDown = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (!resizable) return;\n e.preventDefault();\n e.stopPropagation();\n resizeRef.current = { pointerId: e.pointerId, startX: e.clientX, startWidth: width };\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n },\n [resizable, width],\n );\n const onResizeMove = (e: PointerEvent<HTMLDivElement>) => {\n const r = resizeRef.current;\n if (!r || r.pointerId !== e.pointerId) return;\n // Dragging LEFT widens the panel (it sticks to the right edge of\n // the canvas). Upper bound is `effectiveMax` — the smaller of the\n // consumer's `maxWidth` and the parent container's available room,\n // so the panel can never grow past the canvas itself.\n const delta = r.startX - e.clientX;\n const next = Math.max(minWidth, Math.min(effectiveMax, r.startWidth + delta));\n setWidth(next);\n };\n const onResizeUp = (e: PointerEvent<HTMLDivElement>) => {\n if (resizeRef.current?.pointerId === e.pointerId) resizeRef.current = null;\n };\n\n if (!open) return null;\n\n const activeTab = tabs?.find((t) => t.id === activeTabId);\n\n return (\n <aside\n ref={panelRef}\n className={cn(\n \"ods-flow-config-panel\",\n `ods-flow-config-panel--${variant}`,\n editingTitle && \"ods-flow-config-panel--editing-title\",\n className,\n )}\n style={{ width, ...style }}\n aria-label={title ?? \"Configuration\"}\n >\n {resizable && (\n <div\n className=\"ods-flow-config-panel__resize\"\n onPointerDown={onResizeDown}\n onPointerMove={onResizeMove}\n onPointerUp={onResizeUp}\n onPointerCancel={onResizeUp}\n aria-hidden=\"true\"\n />\n )}\n\n <header className=\"ods-flow-config-panel__header\">\n {kindLabel && <span className=\"ods-flow-config-panel__breadcrumb\">{kindLabel}</span>}\n <div className=\"ods-flow-config-panel__title-row\">\n {icon && <span className=\"ods-flow-config-panel__icon\">{icon}</span>}\n {editingTitle ? (\n <input\n className=\"ods-flow-config-panel__title-input\"\n value={draftTitle}\n autoFocus\n onChange={(e) => setDraftTitle(e.target.value)}\n onBlur={() => commitTitle(draftTitle)}\n onKeyDown={titleKeyDown}\n aria-label=\"Edit title\"\n />\n ) : (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__title\"\n onDoubleClick={() => {\n if (!onTitleChange) return;\n setDraftTitle(title ?? \"\");\n setEditingTitle(true);\n }}\n title={onTitleChange ? \"Double-click to rename\" : undefined}\n >\n {title ?? \"Untitled\"}\n </button>\n )}\n {onClose && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__close\"\n onClick={onClose}\n aria-label=\"Close panel\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <path\n d=\"M3 3l8 8M11 3l-8 8\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n )}\n </div>\n </header>\n\n {tabs && tabs.length > 1 && (\n <nav className=\"ods-flow-config-panel__tabs\" role=\"tablist\">\n {tabs.map((t) => (\n <button\n key={t.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTabId === t.id}\n disabled={t.disabled}\n className={cn(\n \"ods-flow-config-panel__tab\",\n activeTabId === t.id && \"ods-flow-config-panel__tab--active\",\n )}\n onClick={() => selectTab(t.id)}\n >\n <span>{t.label}</span>\n {t.badge !== undefined && t.badge !== null && (\n <span className=\"ods-flow-config-panel__tab-badge\">{t.badge}</span>\n )}\n </button>\n ))}\n </nav>\n )}\n\n {banner && <div className=\"ods-flow-config-panel__banner\">{banner}</div>}\n\n <div className=\"ods-flow-config-panel__body\" role=\"tabpanel\">\n {tabs ? activeTab?.content : children}\n </div>\n\n <footer className=\"ods-flow-config-panel__footer\">\n <span\n className={cn(\n \"ods-flow-config-panel__save-state\",\n `ods-flow-config-panel__save-state--${saveState}`,\n )}\n >\n <span\n className=\"ods-flow-config-panel__save-dot\"\n style={{ background: SAVE_STATE_DOT[saveState] }}\n aria-hidden=\"true\"\n />\n {SAVE_STATE_LABEL[saveState]}\n </span>\n {footer ?? (\n <div className=\"ods-flow-config-panel__footer-actions\">\n {onCancel && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__btn ods-flow-config-panel__btn--ghost\"\n onClick={onCancel}\n >\n Cancel\n </button>\n )}\n {onSave && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__btn ods-flow-config-panel__btn--primary\"\n onClick={onSave}\n disabled={saveState === \"saving\" || saveState === \"clean\"}\n >\n {saveState === \"saving\" ? \"Saving…\" : \"Save\"}\n </button>\n )}\n </div>\n )}\n </footer>\n </aside>\n );\n}\n","\"use client\";\n\n// ════════════════════════════════════════════════════════════════════════\n// NodeResizer — drag-to-resize handles attached to a node.\n// ════════════════════════════════════════════════════════════════════════\n// Dropped inside a custom node renderer. Four corner handles each adjust\n// width/height (and, where appropriate, position so the OPPOSITE corner\n// stays pinned). Width/height changes flow back through the imperative\n// instance via `updateNode`.\n//\n// Visible by default only when the host node is selected. Min/max sizes\n// + aspect-ratio lock keep the resize sensible. We deliberately do NOT\n// ship a `shouldResize` veto (yet) — that's a follow-on.\n\nimport { type CSSProperties, type PointerEvent, useRef } from \"react\";\nimport { cn } from \"../../../utils/cn\";\nimport { useFlow } from \"../../hooks/useFlow\";\nimport { useViewport } from \"../../store/selectors\";\nimport { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from \"../../utils/geometry\";\nimport { useFlowNodeContext } from \"../FlowNode/FlowNodeContext\";\n\nexport interface NodeResizerProps {\n /** Force-visible. Default: visible iff the host node is selected. */\n isVisible?: boolean;\n /** Lower bound for the resize. Defaults to 80×60. */\n minWidth?: number;\n minHeight?: number;\n /** Upper bound for the resize. Defaults unbounded. */\n maxWidth?: number;\n maxHeight?: number;\n /** Lock width:height ratio at the value computed from the node's\n * current dimensions on resize-start. */\n keepAspectRatio?: boolean;\n /** Fired on every pointermove during resize. */\n onResize?: (size: { width: number; height: number }) => void;\n /** Fired on pointerup (the committed size). */\n onResizeEnd?: (size: { width: number; height: number }) => void;\n /** Override handle colour. */\n color?: string;\n}\n\ntype Corner = \"nw\" | \"ne\" | \"sw\" | \"se\";\n\ninterface DragState {\n pointerId: number;\n corner: Corner;\n startClientX: number;\n startClientY: number;\n startWidth: number;\n startHeight: number;\n startX: number;\n startY: number;\n aspect: number; // w / h\n}\n\nexport function NodeResizer({\n isVisible,\n minWidth = 80,\n minHeight = 60,\n maxWidth,\n maxHeight,\n keepAspectRatio = false,\n onResize,\n onResizeEnd,\n color,\n}: NodeResizerProps) {\n const { node, selected } = useFlowNodeContext();\n const viewport = useViewport();\n const flow = useFlow();\n const dragRef = useRef<DragState | null>(null);\n\n const show = isVisible ?? selected;\n if (!show) return null;\n\n const beginResize = (e: PointerEvent<HTMLDivElement>, corner: Corner) => {\n e.preventDefault();\n e.stopPropagation();\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n const w = node.width ?? DEFAULT_NODE_WIDTH;\n const h = node.height ?? DEFAULT_NODE_HEIGHT;\n dragRef.current = {\n pointerId: e.pointerId,\n corner,\n startClientX: e.clientX,\n startClientY: e.clientY,\n startWidth: w,\n startHeight: h,\n startX: node.position.x,\n startY: node.position.y,\n aspect: w / Math.max(1, h),\n };\n };\n\n const onMove = (e: PointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== e.pointerId) return;\n const dx = (e.clientX - drag.startClientX) / viewport.zoom;\n const dy = (e.clientY - drag.startClientY) / viewport.zoom;\n let nextW = drag.startWidth;\n let nextH = drag.startHeight;\n let nextX = drag.startX;\n let nextY = drag.startY;\n switch (drag.corner) {\n case \"se\":\n nextW = drag.startWidth + dx;\n nextH = drag.startHeight + dy;\n break;\n case \"sw\":\n nextW = drag.startWidth - dx;\n nextH = drag.startHeight + dy;\n nextX = drag.startX + dx;\n break;\n case \"ne\":\n nextW = drag.startWidth + dx;\n nextH = drag.startHeight - dy;\n nextY = drag.startY + dy;\n break;\n case \"nw\":\n nextW = drag.startWidth - dx;\n nextH = drag.startHeight - dy;\n nextX = drag.startX + dx;\n nextY = drag.startY + dy;\n break;\n }\n if (keepAspectRatio) {\n // Maintain the original ratio — fit height to width.\n nextH = nextW / drag.aspect;\n if (drag.corner === \"nw\" || drag.corner === \"ne\") {\n nextY = drag.startY + (drag.startHeight - nextH);\n }\n }\n nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);\n nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);\n flow.updateNode(node.id, {\n width: nextW,\n height: nextH,\n position: { x: nextX, y: nextY },\n });\n onResize?.({ width: nextW, height: nextH });\n };\n\n const onUp = (e: PointerEvent<HTMLDivElement>) => {\n if (dragRef.current?.pointerId === e.pointerId) {\n const cur = flow.getNode(node.id);\n if (cur) {\n onResizeEnd?.({\n width: cur.width ?? DEFAULT_NODE_WIDTH,\n height: cur.height ?? DEFAULT_NODE_HEIGHT,\n });\n }\n dragRef.current = null;\n }\n };\n\n const handleColor = color ?? \"var(--ods-accent)\";\n const handleStyle = (corner: Corner): CSSProperties => {\n const base: CSSProperties = {\n position: \"absolute\",\n width: 12,\n height: 12,\n background: \"var(--ods-surface-canvas)\",\n border: `2px solid ${handleColor}`,\n borderRadius: 2,\n cursor: cursorFor(corner),\n touchAction: \"none\",\n // Place each handle so its CENTRE sits on the corresponding corner.\n transform: \"translate(-50%, -50%)\",\n };\n switch (corner) {\n case \"nw\":\n return { ...base, top: 0, left: 0 };\n case \"ne\":\n return { ...base, top: 0, left: \"100%\" };\n case \"sw\":\n return { ...base, top: \"100%\", left: 0 };\n case \"se\":\n return { ...base, top: \"100%\", left: \"100%\" };\n }\n };\n\n return (\n <div className={cn(\"ods-node-resizer\")} data-flow-no-drag=\"true\">\n {([\"nw\", \"ne\", \"sw\", \"se\"] as Corner[]).map((corner) => (\n <div\n key={corner}\n style={handleStyle(corner)}\n onPointerDown={(e) => beginResize(e, corner)}\n onPointerMove={onMove}\n onPointerUp={onUp}\n onPointerCancel={onUp}\n aria-label={`Resize ${corner}`}\n />\n ))}\n </div>\n );\n}\n\nfunction cursorFor(corner: Corner): string {\n // Standard 8-direction resize cursors. We only ship corners for v1; the\n // edge handles (n/s/e/w) can come later.\n switch (corner) {\n case \"nw\":\n case \"se\":\n return \"nwse-resize\";\n case \"ne\":\n case \"sw\":\n return \"nesw-resize\";\n }\n}\n","\"use client\";\n\n// ════════════════════════════════════════════════════════════════════════\n// NodeToolbar — floating action bar attached to a node.\n// ════════════════════════════════════════════════════════════════════════\n// Dropped inside a custom node renderer (or anywhere with a\n// `FlowNodeContext`). Visible by default only when the host node is\n// selected; consumers can force it on with `isVisible`. Position-stable\n// across zoom — the bar applies `scale(1 / zoom)` to counter the viewport\n// transform so the buttons stay at constant pixel size.\n//\n// Pointer events on the toolbar are isolated from the node's drag /\n// connection gestures via `data-flow-no-drag=\"true\"` on the wrapper.\n\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../../../utils/cn\";\nimport type { HandlePosition } from \"../../editor\";\nimport { useViewport } from \"../../store/selectors\";\nimport { useFlowNodeContext } from \"../FlowNode/FlowNodeContext\";\n\nexport interface NodeToolbarProps {\n /** Force-visible state. Defaults to `undefined` → auto (visible iff\n * the host node is selected). Pass `true`/`false` to override. */\n isVisible?: boolean;\n /** Where the toolbar sits relative to the node. Defaults to `top`. */\n position?: HandlePosition;\n /** Pixel offset from the node's edge. Default 10. */\n offset?: number;\n /** Horizontal/vertical alignment along the chosen edge. Default center. */\n align?: \"start\" | \"center\" | \"end\";\n /** Toolbar contents (typically a row of `<button>` elements). */\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function NodeToolbar({\n isVisible,\n position = \"top\",\n offset = 10,\n align = \"center\",\n children,\n className,\n style,\n}: NodeToolbarProps) {\n const node = useFlowNodeContext();\n const viewport = useViewport();\n const show = isVisible ?? node.selected;\n if (!show) return null;\n\n // Counter the canvas's `scale(zoom)` so the toolbar reads at constant\n // pixel size at any zoom level. Translation is in flow-space units; the\n // inverse-scale wraps the visible content so children also stay crisp.\n const inverseScale = 1 / viewport.zoom;\n\n return (\n <div\n className={cn(\"ods-node-toolbar\", `ods-node-toolbar--${position}`, className)}\n data-flow-no-drag=\"true\"\n style={{\n ...positionStyle(position, offset, align),\n ...style,\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Inner element absorbs the inverse zoom so the toolbar's\n children stay screen-stable. */}\n <div\n className=\"ods-node-toolbar__inner\"\n style={{\n transform: `scale(${inverseScale})`,\n transformOrigin: transformOriginFor(position, align),\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n\nfunction positionStyle(\n side: HandlePosition,\n offset: number,\n align: NodeToolbarProps[\"align\"],\n): CSSProperties {\n const center = align === \"center\";\n const start = align === \"start\";\n switch (side) {\n case \"top\":\n return {\n position: \"absolute\",\n bottom: \"100%\",\n marginBottom: offset,\n left: start ? 0 : center ? \"50%\" : undefined,\n right: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateX(-50%)\" : undefined,\n };\n case \"bottom\":\n return {\n position: \"absolute\",\n top: \"100%\",\n marginTop: offset,\n left: start ? 0 : center ? \"50%\" : undefined,\n right: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateX(-50%)\" : undefined,\n };\n case \"left\":\n return {\n position: \"absolute\",\n right: \"100%\",\n marginRight: offset,\n top: start ? 0 : center ? \"50%\" : undefined,\n bottom: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateY(-50%)\" : undefined,\n };\n case \"right\":\n return {\n position: \"absolute\",\n left: \"100%\",\n marginLeft: offset,\n top: start ? 0 : center ? \"50%\" : undefined,\n bottom: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateY(-50%)\" : undefined,\n };\n }\n}\n\nfunction transformOriginFor(side: HandlePosition, align: NodeToolbarProps[\"align\"]): string {\n // Choose an origin that visually attaches the bar to the node edge,\n // even after inverse-scaling. Defaults match the `position*` styles.\n if (side === \"top\")\n return align === \"start\" ? \"left bottom\" : align === \"end\" ? \"right bottom\" : \"center bottom\";\n if (side === \"bottom\")\n return align === \"start\" ? \"left top\" : align === \"end\" ? \"right top\" : \"center top\";\n if (side === \"left\")\n return align === \"start\" ? \"right top\" : align === \"end\" ? \"right bottom\" : \"right center\";\n return align === \"start\" ? \"left top\" : align === \"end\" ? \"left bottom\" : \"left center\";\n}\n","// ════════════════════════════════════════════════════════════════════════\n// Collapse helpers — when a group's `data.collapsed === true`, hide its\n// descendants AND re-route any edges that crossed the collapse boundary\n// so they terminate at the group instead of dangling at a hidden child.\n// ════════════════════════════════════════════════════════════════════════\n//\n// Consumers feed `nodes` + `edges` (their canonical state) through\n// `collapseFor()` and pass the result to <FlowCanvas>. The helpers are\n// pure — no React state, no DOM. They run in O(N + E) so they're cheap\n// to call on every render.\n\nimport type { WorkflowEdge, WorkflowNode } from \"../editor\";\n\ninterface CollapseResult {\n nodes: WorkflowNode[];\n edges: WorkflowEdge[];\n /** Per-collapsed-group: how many descendants got hidden. The host UI\n * reads this to render a \"N steps\" badge. */\n hiddenCountByGroupId: Map<string, number>;\n}\n\n/**\n * Given the canonical graph, return a derived graph that respects the\n * `collapsed` flag on every group node:\n *\n * - Descendants of a collapsed group are marked `hidden`.\n * - Edges that crossed the collapse boundary are rewritten: their\n * source (or target) becomes the collapsed group's id, and the\n * handle is reset to a synthetic perimeter handle (`__group_out` /\n * `__group_in`). Internal-to-internal edges are dropped.\n */\nexport function collapseFor(nodes: WorkflowNode[], edges: WorkflowEdge[]): CollapseResult {\n // Map child → topmost collapsed ancestor (if any).\n // If a node has multiple collapsed ancestors, the OUTERMOST wins so the\n // edge terminates at the visible boundary.\n const collapsedAncestorOf = new Map<string, string>();\n const parents = new Map<string, string>();\n for (const n of nodes) if (n.parentId) parents.set(n.id, n.parentId);\n\n // ANY node with `data.collapsed === true` collapses its descendants —\n // not just `group`. This lets `forEach` (and consumer-defined container\n // kinds) opt into the same collapse model without each one being\n // hard-coded in this helper.\n const isCollapsed = (node: WorkflowNode | undefined): boolean => {\n const d = node?.data as { collapsed?: boolean } | undefined;\n return !!d?.collapsed;\n };\n\n for (const n of nodes) {\n let cursor: string | undefined = n.id;\n let outermostCollapsed: string | undefined;\n const seen = new Set<string>([cursor]);\n while (cursor) {\n const p = parents.get(cursor);\n if (!p || seen.has(p)) break;\n seen.add(p);\n const parentNode = nodes.find((x) => x.id === p);\n if (isCollapsed(parentNode)) outermostCollapsed = p;\n cursor = p;\n }\n if (outermostCollapsed && outermostCollapsed !== n.id) {\n collapsedAncestorOf.set(n.id, outermostCollapsed);\n }\n }\n\n // Hide every descendant of a collapsed group.\n const outNodes: WorkflowNode[] = nodes.map((n) => {\n const anc = collapsedAncestorOf.get(n.id);\n if (anc) return { ...n, hidden: true };\n return n;\n });\n\n // Rewrite edges: any endpoint that sits inside a collapsed group gets\n // hoisted to the group itself. Edges entirely within a collapsed group\n // are dropped. Edges that start at the group and end inside it (or\n // vice versa) become self-loops that we also drop.\n //\n // **Boundary marker** — edges that get rewritten to terminate at a\n // collapsed group's perimeter are stroked with a sparse dot pattern so\n // the user immediately sees they're a \"summary\" of one or more hidden\n // inner connections rather than a real handle-to-handle edge. The\n // original `edge.style` is preserved; only `strokeDasharray` is\n // overlaid. FlowEdge gives `edge.style.strokeDasharray` precedence\n // over the type's default dash.\n const BOUNDARY_DASH = \"2 4\";\n const outEdges: WorkflowEdge[] = [];\n const hiddenCount = new Map<string, number>();\n for (const e of edges) {\n const srcAnc = collapsedAncestorOf.get(e.source);\n const tgtAnc = collapsedAncestorOf.get(e.target);\n if (srcAnc && tgtAnc && srcAnc === tgtAnc) continue; // wholly inside one collapsed group — drop\n let src = e.source;\n let srcHandle = e.sourceHandle;\n let tgt = e.target;\n let tgtHandle = e.targetHandle;\n const rewritten = !!(srcAnc || tgtAnc);\n if (srcAnc) {\n src = srcAnc;\n // Synthetic perimeter handle — the GroupNode renderer registers\n // matching `__group_out` / `__group_in` handles when collapsed.\n srcHandle = \"__group_out\";\n }\n if (tgtAnc) {\n tgt = tgtAnc;\n tgtHandle = \"__group_in\";\n }\n if (src === tgt) continue; // collapsed self-loop\n const nextEdge: WorkflowEdge = {\n ...e,\n source: src,\n sourceHandle: srcHandle,\n target: tgt,\n targetHandle: tgtHandle,\n };\n if (rewritten) {\n nextEdge.style = { ...(e.style ?? {}), strokeDasharray: BOUNDARY_DASH };\n }\n outEdges.push(nextEdge);\n }\n\n // Count hidden descendants per collapsed group for the badge.\n for (const [_id, anc] of collapsedAncestorOf) {\n hiddenCount.set(anc, (hiddenCount.get(anc) ?? 0) + 1);\n }\n\n return { nodes: outNodes, edges: outEdges, hiddenCountByGroupId: hiddenCount };\n}\n\n/**\n * Toggle the `data.collapsed` flag on a group node. Returns the patched\n * nodes array (immutable). Useful in event handlers.\n */\nexport function toggleGroupCollapse(groupId: string, nodes: WorkflowNode[]): WorkflowNode[] {\n return nodes.map((n) => {\n if (n.id !== groupId) return n;\n const data = (n.data ?? {}) as { collapsed?: boolean } & Record<string, unknown>;\n return { ...n, data: { ...data, collapsed: !data.collapsed } };\n });\n}\n"],"mappings":"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","names":["useCallback","useRef","useState","useState","useRef","useCallback","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useRef","jsx","useRef","jsx"]}
1
+ {"version":3,"sources":["../src/workflow/hooks/useAutoLayout.ts","../src/workflow/hooks/useFlowState.ts","../src/workflow/layout/dagre.ts","../src/workflow/layout/elk.ts","../src/workflow/components/ConfigPanel/ConfigPanel.tsx","../src/workflow/components/NodeToolbar/NodeToolbar.tsx","../src/workflow/utils/collapse.ts"],"sourcesContent":["// ════════════════════════════════════════════════════════════════════════\n// useAutoLayout — run a layout engine and apply its output via the\n// canvas's `onNodesChange` callback.\n// ════════════════════════════════════════════════════════════════════════\n// Two trigger modes:\n//\n// 1. Imperative — call the returned `runLayout()` function (e.g. from a\n// toolbar button). This is the recommended default; users press\n// \"Auto-arrange\" and the engine runs once.\n//\n// 2. Reactive — pass `auto: true`. The hook debounces 250ms on every\n// node/edge add/remove and runs the engine. Useful for \"always\n// auto-layout\" surfaces; expensive in graphs where users drag nodes\n// around (their positions get clobbered).\n//\n// The hook does NOT subscribe to selection / drag / viewport — it watches\n// only the *shape* of the graph (node ids + edge connections). That\n// keeps reactive mode from re-running on every pointermove.\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type {\n LayoutEngine,\n LayoutOptions,\n NodeChange,\n WorkflowEdge,\n WorkflowNode,\n} from \"../editor\";\nimport { change } from \"../store/changes\";\n\nexport interface UseAutoLayoutOptions {\n /** The engine. Required. */\n engine: LayoutEngine | null | undefined;\n /** Current nodes (controlled). */\n nodes: WorkflowNode[];\n /** Current edges (controlled). */\n edges: WorkflowEdge[];\n /** Where to send position changes. Required. */\n onNodesChange?: (changes: NodeChange[]) => void;\n /** Layout options forwarded to the engine. */\n options?: LayoutOptions;\n /** Re-run on every graph-shape change (debounced 250ms). Default false. */\n auto?: boolean;\n /** Debounce delay for `auto` mode (ms). Default 250. */\n debounceMs?: number;\n /** Called after each successful run. */\n onLayout?: (positions: { id: string; position: { x: number; y: number } }[]) => void;\n /** Called when the engine throws (network / worker / library). */\n onError?: (err: unknown) => void;\n}\n\nexport interface UseAutoLayoutResult {\n /** Manually trigger a layout pass. Returns the promise of the engine. */\n runLayout(): Promise<void>;\n /** True while a layout pass is in flight. */\n isLaying: boolean;\n}\n\n/**\n * Build a stable graph-shape signature so reactive mode only re-runs when\n * the structure (node ids + edges) changes, not on position/select/drag.\n */\nfunction signature(nodes: WorkflowNode[], edges: WorkflowEdge[]): string {\n // Sorted to be invariant under array re-ordering.\n const nodeKeys = nodes\n .map((n) => `${n.id}:${n.parentId ?? \"\"}`)\n .sort()\n .join(\"|\");\n const edgeKeys = edges\n .map((e) => `${e.id}:${e.source}>${e.target}`)\n .sort()\n .join(\"|\");\n return `${nodeKeys}::${edgeKeys}`;\n}\n\nexport function useAutoLayout({\n engine,\n nodes,\n edges,\n onNodesChange,\n options,\n auto = false,\n debounceMs = 250,\n onLayout,\n onError,\n}: UseAutoLayoutOptions): UseAutoLayoutResult {\n const [isLaying, setIsLaying] = useState(false);\n const runningRef = useRef(false);\n const optionsRef = useRef(options);\n const onLayoutRef = useRef(onLayout);\n const onErrorRef = useRef(onError);\n const onNodesChangeRef = useRef(onNodesChange);\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n useEffect(() => {\n onLayoutRef.current = onLayout;\n }, [onLayout]);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n useEffect(() => {\n onNodesChangeRef.current = onNodesChange;\n }, [onNodesChange]);\n\n // Snapshot refs of nodes/edges so the runLayout callback can read fresh\n // data without re-creating itself on every render.\n const nodesRef = useRef(nodes);\n const edgesRef = useRef(edges);\n useEffect(() => {\n nodesRef.current = nodes;\n }, [nodes]);\n useEffect(() => {\n edgesRef.current = edges;\n }, [edges]);\n\n const runLayout = useCallback(async () => {\n if (!engine) return;\n if (runningRef.current) return;\n runningRef.current = true;\n setIsLaying(true);\n try {\n const positions = await engine.layout(nodesRef.current, edgesRef.current, optionsRef.current);\n\n // Index engine output by id + remember per-node deltas so we can\n // transitively apply them to descendants (children move with the\n // parent — otherwise a parent moves and its children get stranded\n // outside the new bbox, which is the v1 subflow auto-layout bug).\n const positionedIds = new Set(positions.map((p) => p.id));\n const deltas = new Map<string, { dx: number; dy: number }>();\n\n const cs: NodeChange[] = [];\n for (const p of positions) {\n const cur = nodesRef.current.find((n) => n.id === p.id);\n if (!cur) continue;\n if (cur.position.x === p.position.x && cur.position.y === p.position.y) continue;\n cs.push(change.node.position(p.id, p.position, false));\n deltas.set(p.id, {\n dx: p.position.x - cur.position.x,\n dy: p.position.y - cur.position.y,\n });\n }\n\n // For every node the engine did NOT reposition (typically because\n // it sat in a sub-flow that the engine treated as opaque), walk up\n // the parentId chain to find the nearest moved ancestor. Shift the\n // descendant by that ancestor's delta. The first moved ancestor\n // wins — we don't sum deltas because each child's position is\n // already relative to its ancestor's old position.\n const findParent = (id: string): WorkflowNode | undefined =>\n nodesRef.current.find((x) => x.id === id);\n\n for (const n of nodesRef.current) {\n if (positionedIds.has(n.id)) continue;\n if (!n.parentId) continue;\n let cursor: string | undefined = n.parentId;\n const seen = new Set<string>();\n let delta: { dx: number; dy: number } | undefined;\n while (cursor) {\n if (seen.has(cursor)) break; // cycle guard\n seen.add(cursor);\n const found = deltas.get(cursor);\n if (found) {\n delta = found;\n break;\n }\n cursor = findParent(cursor)?.parentId;\n }\n if (!delta) continue;\n cs.push(\n change.node.position(\n n.id,\n { x: n.position.x + delta.dx, y: n.position.y + delta.dy },\n false,\n ),\n );\n }\n\n if (cs.length) onNodesChangeRef.current?.(cs);\n onLayoutRef.current?.(positions);\n } catch (err) {\n onErrorRef.current?.(err);\n } finally {\n runningRef.current = false;\n setIsLaying(false);\n }\n }, [engine]);\n\n // Reactive auto-mode — debounce on graph-shape changes.\n const sig = signature(nodes, edges);\n const sigRef = useRef(sig);\n useEffect(() => {\n if (!auto) return;\n if (sigRef.current === sig) return; // first render after toggling auto\n sigRef.current = sig;\n const timer = setTimeout(() => void runLayout(), debounceMs);\n return () => clearTimeout(timer);\n }, [sig, auto, debounceMs, runLayout]);\n\n return { runLayout, isLaying };\n}\n","// ════════════════════════════════════════════════════════════════════════\n// useFlowState — the uncontrolled-mode convenience.\n// ════════════════════════════════════════════════════════════════════════\n// Consumers that don't want to thread state through their own reducer\n// can use this hook and spread the result into <FlowCanvas>:\n//\n// const flow = useFlowState({ initialNodes, initialEdges });\n// <FlowCanvas {...flow} />\n//\n// In addition to the canonical change-stream contract, `useFlowState`\n// exposes per-node helpers — `updateNodeData(id, patch)` and the\n// companion `useNodeData(state, id)` selector — so individual node\n// updates can be expressed without re-mapping the entire nodes array\n// in caller code. Each helper writes ONLY the targeted node's slice;\n// React.memo'd kind components for other nodes never re-render.\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { EdgeChange, NodeChange, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { applyEdgeChanges, applyNodeChanges } from \"../store/changes\";\n\nexport interface UseFlowStateOptions<TNData = unknown, TEData = unknown> {\n initialNodes?: WorkflowNode<TNData>[];\n initialEdges?: WorkflowEdge<TEData>[];\n}\n\n/**\n * Patch type for `updateNodeData` — accepts a partial object that gets\n * shallow-merged, or a producer function `(prev) => next`.\n */\nexport type NodeDataPatch<TNData> =\n | Partial<TNData>\n | ((prev: TNData | undefined) => TNData | undefined);\n\nexport interface FlowStateBag<TNData = unknown, TEData = unknown> {\n nodes: WorkflowNode<TNData>[];\n edges: WorkflowEdge<TEData>[];\n onNodesChange: (changes: NodeChange<TNData>[]) => void;\n onEdgesChange: (changes: EdgeChange<TEData>[]) => void;\n setNodes: (\n update: WorkflowNode<TNData>[] | ((prev: WorkflowNode<TNData>[]) => WorkflowNode<TNData>[]),\n ) => void;\n setEdges: (\n update: WorkflowEdge<TEData>[] | ((prev: WorkflowEdge<TEData>[]) => WorkflowEdge<TEData>[]),\n ) => void;\n /**\n * Update the `data` slice of a single node by id. Only that node's\n * reference changes — every other node keeps its previous reference,\n * so React.memo'd kind components for unrelated nodes do not re-render.\n * No-ops when `id` is not present in the current nodes array.\n */\n updateNodeData: (id: string, patch: NodeDataPatch<TNData>) => void;\n /**\n * Read a single node by id from the latest snapshot. Returns undefined\n * if the id is unknown. Useful inside imperative callbacks that need\n * the freshest value without subscribing to re-renders.\n */\n getNode: (id: string) => WorkflowNode<TNData> | undefined;\n}\n\nexport function useFlowState<TNData = unknown, TEData = unknown>(\n options: UseFlowStateOptions<TNData, TEData> = {},\n): FlowStateBag<TNData, TEData> {\n const [nodes, setNodes] = useState<WorkflowNode<TNData>[]>(options.initialNodes ?? []);\n const [edges, setEdges] = useState<WorkflowEdge<TEData>[]>(options.initialEdges ?? []);\n\n // Ref-mirror of the latest nodes array. `getNode` reads through it so\n // imperative callers see fresh values without depending on render scope.\n const nodesRef = useRef(nodes);\n nodesRef.current = nodes;\n\n const onNodesChange = useCallback((changes: NodeChange<TNData>[]) => {\n setNodes((prev) => applyNodeChanges(prev, changes));\n }, []);\n\n const onEdgesChange = useCallback((changes: EdgeChange<TEData>[]) => {\n setEdges((prev) => applyEdgeChanges(prev, changes));\n }, []);\n\n const updateNodeData = useCallback((id: string, patch: NodeDataPatch<TNData>) => {\n setNodes((prev) => {\n let touched = false;\n const next: WorkflowNode<TNData>[] = prev.map((n) => {\n if (n.id !== id) return n;\n const nextData =\n typeof patch === \"function\"\n ? (patch as (p: TNData | undefined) => TNData | undefined)(n.data)\n : ({ ...(n.data ?? ({} as TNData)), ...patch } as TNData);\n if (Object.is(nextData, n.data)) return n;\n touched = true;\n return { ...n, data: nextData as TNData };\n });\n return touched ? next : prev;\n });\n }, []);\n\n const getNode = useCallback((id: string) => nodesRef.current.find((n) => n.id === id), []);\n\n return useMemo(\n () => ({\n nodes,\n edges,\n onNodesChange,\n onEdgesChange,\n setNodes,\n setEdges,\n updateNodeData,\n getNode,\n }),\n [nodes, edges, onNodesChange, onEdgesChange, updateNodeData, getNode],\n );\n}\n","// ════════════════════════════════════════════════════════════════════════\n// Dagre adapter — wraps a consumer-supplied `dagre` module.\n// ════════════════════════════════════════════════════════════════════════\n// Usage:\n//\n// import dagre from \"dagre\";\n// import { createDagreEngine } from \"@octaviaflow/core/workflow\";\n//\n// const engine = createDagreEngine(dagre);\n// useAutoLayout({ engine, onLayout });\n//\n// Dagre is synchronous — `layout()` blocks the main thread. Good for\n// small graphs (≤ ~200 nodes). For larger graphs, see `createElkEngine`.\n\nimport type { LayoutEngine, LayoutOptions, Position, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from \"../utils/geometry\";\nimport type { DagreLike } from \"./types\";\n\nexport interface CreateDagreEngineOptions {\n /** Default spacing between nodes within a rank (default 60). */\n nodeSpacing?: number;\n /** Default spacing between ranks (default 80). */\n rankSpacing?: number;\n /** Default direction — TB, BT, LR, RL. Defaults to TB. */\n direction?: LayoutOptions[\"direction\"];\n}\n\n/**\n * Build a `LayoutEngine` backed by a consumer-supplied `dagre` module.\n *\n * `dagre` can be passed as either the default export or the namespace\n * import (`import * as dagre from \"dagre\"`). The adapter probes both\n * shapes so consumers don't have to think about it.\n */\nexport function createDagreEngine(\n dagre:\n | DagreLike\n | { default: DagreLike }\n | { graphlib: DagreLike[\"graphlib\"]; layout: DagreLike[\"layout\"] },\n defaults: CreateDagreEngineOptions = {},\n): LayoutEngine {\n const lib = resolveDagre(dagre);\n const defaultDir = defaults.direction ?? \"TB\";\n const defaultNodeSpacing = defaults.nodeSpacing ?? 60;\n const defaultRankSpacing = defaults.rankSpacing ?? 80;\n\n return {\n name: \"dagre\",\n async layout(nodes: WorkflowNode[], edges: WorkflowEdge[], options?: LayoutOptions) {\n const direction = options?.direction ?? defaultDir;\n const nodesep = options?.nodeSpacing ?? defaultNodeSpacing;\n const ranksep = options?.rankSpacing ?? defaultRankSpacing;\n\n // Dagre requires every node to have width/height. Use the measured\n // values from the canvas; fall back to our default footprint.\n const g = new lib.graphlib.Graph({ multigraph: true, compound: true });\n g.setGraph({\n rankdir: direction,\n nodesep,\n ranksep,\n ...(options?.engine ?? {}),\n });\n g.setDefaultEdgeLabel(() => ({}));\n\n for (const n of nodes) {\n if (n.hidden) continue;\n g.setNode(n.id, {\n width: n.width ?? DEFAULT_NODE_WIDTH,\n height: n.height ?? DEFAULT_NODE_HEIGHT,\n ...(n.parentId ? { parent: n.parentId } : {}),\n });\n // Compound graph: register the parent–child relationship.\n if (n.parentId) {\n // `setParent` is on the graph but not strongly typed in our\n // narrow surface — call via index access. dagre tolerates a\n // missing parent node (it auto-creates).\n (g as unknown as { setParent: (id: string, parent: string) => void }).setParent(\n n.id,\n n.parentId,\n );\n }\n }\n\n for (const e of edges) {\n if (e.hidden) continue;\n g.setEdge(e.source, e.target, { id: e.id });\n }\n\n lib.layout(g);\n\n // Dagre stores node centres; our model uses top-left. Translate.\n const result: { id: string; position: Position }[] = [];\n for (const id of g.nodes()) {\n const out = g.node(id);\n if (!out) continue;\n result.push({\n id,\n position: { x: out.x - out.width / 2, y: out.y - out.height / 2 },\n });\n }\n return result;\n },\n };\n}\n\nfunction resolveDagre(input: unknown): DagreLike {\n const asAny = input as Partial<DagreLike> & { default?: DagreLike };\n if (asAny && typeof asAny.layout === \"function\" && asAny.graphlib?.Graph) {\n return asAny as DagreLike;\n }\n if (asAny?.default && typeof asAny.default.layout === \"function\") {\n return asAny.default;\n }\n throw new Error(\n \"[@octaviaflow/core/workflow] createDagreEngine: input does not look like the dagre module. \" +\n \"Pass `dagre` (the default export) or `import * as dagre from 'dagre'`.\",\n );\n}\n","// ════════════════════════════════════════════════════════════════════════\n// ELK adapter — wraps a consumer-supplied `elkjs` instance.\n// ════════════════════════════════════════════════════════════════════════\n// Usage (Web Worker — recommended for large graphs):\n//\n// import ELK from \"elkjs/lib/elk-api\";\n// import { createElkEngine } from \"@octaviaflow/core/workflow\";\n//\n// const elk = new ELK({ workerUrl: \"/elk-worker.min.js\" });\n// const engine = createElkEngine(elk);\n//\n// Usage (in-process, no worker):\n//\n// import ELK from \"elkjs/lib/elk.bundled\";\n// const elk = new ELK();\n// const engine = createElkEngine(elk);\n//\n// ELK supports compound graphs natively — nodes with `parentId` get\n// nested into the right `children[]` so subflow layouts just work.\n\nimport type { LayoutEngine, LayoutOptions, Position, WorkflowEdge, WorkflowNode } from \"../editor\";\nimport { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from \"../utils/geometry\";\nimport type { ElkEdge, ElkGraph, ElkLike } from \"./types\";\n\nexport interface CreateElkEngineOptions {\n /** Default direction — TB / BT / LR / RL. Defaults to TB. */\n direction?: LayoutOptions[\"direction\"];\n /** Default node-to-node spacing within a layer (default 60). */\n nodeSpacing?: number;\n /** Default layer-to-layer spacing (default 80). */\n rankSpacing?: number;\n /** Override the algorithm. Defaults to `layered` (Sugiyama). */\n algorithm?:\n | \"layered\"\n | \"mrtree\"\n | \"force\"\n | \"stress\"\n | \"radial\"\n | \"disco\"\n | \"rectpacking\"\n | \"box\";\n}\n\nconst ELK_DIRECTION: Record<NonNullable<LayoutOptions[\"direction\"]>, string> = {\n TB: \"DOWN\",\n BT: \"UP\",\n LR: \"RIGHT\",\n RL: \"LEFT\",\n};\n\n/**\n * Build a `LayoutEngine` backed by a consumer-supplied `elkjs` instance.\n *\n * The consumer is responsible for constructing the ELK instance (which\n * may or may not run in a Web Worker — that's a `workerUrl` constructor\n * option on their end). Our adapter just calls `elk.layout(graph)` and\n * patches positions back.\n */\nexport function createElkEngine(elk: ElkLike, defaults: CreateElkEngineOptions = {}): LayoutEngine {\n const algorithm = defaults.algorithm ?? \"layered\";\n const defaultDir = defaults.direction ?? \"TB\";\n const defaultNodeSpacing = defaults.nodeSpacing ?? 60;\n const defaultRankSpacing = defaults.rankSpacing ?? 80;\n\n return {\n name: \"elk\",\n async layout(nodes: WorkflowNode[], edges: WorkflowEdge[], options?: LayoutOptions) {\n const direction = options?.direction ?? defaultDir;\n const nodeSpacing = options?.nodeSpacing ?? defaultNodeSpacing;\n const rankSpacing = options?.rankSpacing ?? defaultRankSpacing;\n\n // Build a nested ELK graph respecting `parentId` so compound layout\n // works. We walk the node list once into a parent → children map,\n // then recursively build ElkGraph.children[].\n const childrenByParent = new Map<string | undefined, WorkflowNode[]>();\n for (const n of nodes) {\n if (n.hidden) continue;\n const key = n.parentId ?? undefined;\n const list = childrenByParent.get(key);\n if (list) list.push(n);\n else childrenByParent.set(key, [n]);\n }\n\n const buildChildren = (parentId: string | undefined): ElkGraph[] => {\n const list = childrenByParent.get(parentId) ?? [];\n return list.map<ElkGraph>((n) => ({\n id: n.id,\n width: n.width ?? DEFAULT_NODE_WIDTH,\n height: n.height ?? DEFAULT_NODE_HEIGHT,\n children: childrenByParent.has(n.id) ? buildChildren(n.id) : undefined,\n layoutOptions: childrenByParent.has(n.id)\n ? {\n \"elk.algorithm\": algorithm,\n \"elk.direction\": ELK_DIRECTION[direction],\n \"elk.spacing.nodeNode\": String(nodeSpacing),\n \"elk.layered.spacing.nodeNodeBetweenLayers\": String(rankSpacing),\n }\n : undefined,\n }));\n };\n\n const elkEdges: ElkEdge[] = edges\n .filter((e) => !e.hidden)\n .map((e) => ({\n id: e.id,\n sources: [e.source],\n targets: [e.target],\n }));\n\n const graph: ElkGraph = {\n id: \"root\",\n layoutOptions: {\n \"elk.algorithm\": algorithm,\n \"elk.direction\": ELK_DIRECTION[direction],\n \"elk.spacing.nodeNode\": String(nodeSpacing),\n \"elk.layered.spacing.nodeNodeBetweenLayers\": String(rankSpacing),\n ...Object.fromEntries(\n Object.entries(options?.engine ?? {}).map(([k, v]) => [k, String(v)]),\n ),\n },\n children: buildChildren(undefined),\n edges: elkEdges,\n };\n\n const laidOut = await elk.layout(graph);\n\n // Walk the resulting graph and collect (id, position). ELK returns\n // positions relative to the parent — we walk parents to flatten\n // back to absolute flow-space coordinates (matches our ADR-003\n // \"positions are always absolute\" rule).\n const result: { id: string; position: Position }[] = [];\n const walk = (g: ElkGraph, ox: number, oy: number) => {\n const ax = ox + (g.x ?? 0);\n const ay = oy + (g.y ?? 0);\n if (g.id !== \"root\") {\n result.push({ id: g.id, position: { x: ax, y: ay } });\n }\n if (g.children) {\n for (const c of g.children) walk(c, ax, ay);\n }\n };\n walk(laidOut, 0, 0);\n return result;\n },\n };\n}\n","\"use client\";\n\n// ════════════════════════════════════════════════════════════════════════\n// ConfigPanel v2 — right-side editor for the selected node.\n// ════════════════════════════════════════════════════════════════════════\n// Improvements over v1:\n// - Tabbed body (config / input / output schema; consumer can override)\n// - Resizable width (drag the left edge)\n// - Pinned vs floating modes\n// - Breadcrumb header showing node kind → title\n// - Footer with save-state pill (dirty / saving / saved)\n// - Slot for inline status banner (validation errors, etc.)\n// - Inline-edit-the-title affordance (double-click)\n// - Sticky footer with save / cancel buttons\n\nimport {\n type CSSProperties,\n type PointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { cn } from \"../../../utils/cn\";\n\nexport type ConfigPanelSaveState = \"clean\" | \"dirty\" | \"saving\" | \"saved\" | \"error\";\n\nexport interface ConfigPanelTab {\n id: string;\n label: ReactNode;\n /** Optional badge (e.g. count of errors). */\n badge?: ReactNode;\n /** When true, the tab is rendered but disabled. */\n disabled?: boolean;\n content: ReactNode;\n}\n\nexport interface ConfigPanelProps {\n open?: boolean;\n /** Title shown in the header. Double-click opens the inline editor. */\n title?: string;\n /** Optional breadcrumb prefix (e.g. \"Action\"). */\n kindLabel?: string;\n /** Optional icon rendered before the title. */\n icon?: ReactNode;\n /** Inline status banner — error / warning / hint, shown at the top of the body. */\n banner?: ReactNode;\n /** Tab definitions. When omitted, `children` renders directly (no tabs). */\n tabs?: ConfigPanelTab[];\n /** Initial active tab. Controlled if `onTabChange` is provided. */\n activeTab?: string;\n defaultActiveTab?: string;\n onTabChange?: (id: string) => void;\n /** Save-state pill in the footer. */\n saveState?: ConfigPanelSaveState;\n /** Fires on the title editor's commit. Omit to disable title editing. */\n onTitleChange?: (next: string) => void;\n onClose?: () => void;\n onSave?: () => void;\n onCancel?: () => void;\n /** Resizable width — pass `resizable: false` to lock. */\n resizable?: boolean;\n /** Initial width (px). Defaults to 360. */\n defaultWidth?: number;\n /** Lower clamp during resize. Defaults to 280. */\n minWidth?: number;\n /**\n * Upper clamp during resize. Defaults to 720. The panel ALSO clamps\n * to its parent container's measured width minus\n * `maxWidthContainerInset` (default 80 px) so the panel can never\n * cover the whole canvas — whichever is smaller wins.\n */\n maxWidth?: number;\n /**\n * How much of the parent container to leave visible at the panel's\n * widest. Subtracted from the parent's clientWidth before clamping\n * `maxWidth`. Defaults to 80 px.\n */\n maxWidthContainerInset?: number;\n /** \"pinned\" sits flush against the canvas; \"floating\" raises with shadow. */\n variant?: \"pinned\" | \"floating\";\n /** Body slot used only when `tabs` is omitted. */\n children?: ReactNode;\n /** Custom footer slot — replaces the default save/cancel buttons. */\n footer?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SAVE_STATE_LABEL: Record<ConfigPanelSaveState, string> = {\n clean: \"Saved\",\n dirty: \"Unsaved changes\",\n saving: \"Saving…\",\n saved: \"Saved\",\n error: \"Save failed\",\n};\n\nconst SAVE_STATE_DOT: Record<ConfigPanelSaveState, string> = {\n clean: \"var(--ods-text-tertiary)\",\n dirty: \"var(--ods-status-warning, #d97706)\",\n saving: \"var(--ods-status-running, #2563eb)\",\n saved: \"var(--ods-status-success, #16a34a)\",\n error: \"var(--ods-status-failed, #dc2626)\",\n};\n\nexport function ConfigPanel({\n open = true,\n title,\n kindLabel,\n icon,\n banner,\n tabs,\n activeTab: controlledActive,\n defaultActiveTab,\n onTabChange,\n saveState = \"clean\",\n onTitleChange,\n onClose,\n onSave,\n onCancel,\n resizable = true,\n defaultWidth = 360,\n minWidth = 280,\n maxWidth = 720,\n maxWidthContainerInset = 80,\n variant = \"pinned\",\n children,\n footer,\n className,\n style,\n}: ConfigPanelProps) {\n const [width, setWidth] = useState(defaultWidth);\n // Effective upper bound — `min(maxWidth, parentClientWidth - inset)`.\n // Recomputed on every parent resize so the panel can never cover the\n // canvas. Starts at `maxWidth` so the first paint is sane; the\n // ResizeObserver below tightens it as soon as the layout settles.\n const [effectiveMax, setEffectiveMax] = useState(maxWidth);\n const panelRef = useRef<HTMLElement>(null);\n useEffect(() => {\n const el = panelRef.current;\n if (!el) return;\n const parent = el.parentElement;\n if (!parent || typeof ResizeObserver === \"undefined\") return;\n const update = () => {\n const room = parent.clientWidth - maxWidthContainerInset;\n const next = Math.max(minWidth, Math.min(maxWidth, room));\n setEffectiveMax(next);\n // If the current width exceeds the new ceiling (window shrank),\n // snap the panel down so it remains inside the canvas.\n setWidth((w) => (w > next ? next : w));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(parent);\n return () => ro.disconnect();\n }, [maxWidth, minWidth, maxWidthContainerInset]);\n const [internalActive, setInternalActive] = useState<string | undefined>(\n defaultActiveTab ?? tabs?.[0]?.id,\n );\n const activeTabId = controlledActive ?? internalActive;\n const selectTab = (id: string) => {\n if (controlledActive === undefined) setInternalActive(id);\n onTabChange?.(id);\n };\n\n // ── Title editor ────────────────────────────────────────────────────\n const [editingTitle, setEditingTitle] = useState(false);\n const [draftTitle, setDraftTitle] = useState(title ?? \"\");\n useEffect(() => {\n if (!editingTitle) setDraftTitle(title ?? \"\");\n }, [title, editingTitle]);\n const commitTitle = (next: string) => {\n setEditingTitle(false);\n if (next !== title) onTitleChange?.(next);\n };\n const titleKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n commitTitle(draftTitle);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setEditingTitle(false);\n setDraftTitle(title ?? \"\");\n }\n };\n\n // ── Resize handle ───────────────────────────────────────────────────\n // We capture pointer events on the drag handle; the panel width is\n // updated on every pointermove. Width is clamped to [min, max].\n const resizeRef = useRef<{ pointerId: number; startX: number; startWidth: number } | null>(null);\n const onResizeDown = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (!resizable) return;\n e.preventDefault();\n e.stopPropagation();\n resizeRef.current = { pointerId: e.pointerId, startX: e.clientX, startWidth: width };\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n },\n [resizable, width],\n );\n const onResizeMove = (e: PointerEvent<HTMLDivElement>) => {\n const r = resizeRef.current;\n if (!r || r.pointerId !== e.pointerId) return;\n // Dragging LEFT widens the panel (it sticks to the right edge of\n // the canvas). Upper bound is `effectiveMax` — the smaller of the\n // consumer's `maxWidth` and the parent container's available room,\n // so the panel can never grow past the canvas itself.\n const delta = r.startX - e.clientX;\n const next = Math.max(minWidth, Math.min(effectiveMax, r.startWidth + delta));\n setWidth(next);\n };\n const onResizeUp = (e: PointerEvent<HTMLDivElement>) => {\n if (resizeRef.current?.pointerId === e.pointerId) resizeRef.current = null;\n };\n\n if (!open) return null;\n\n const activeTab = tabs?.find((t) => t.id === activeTabId);\n\n return (\n <aside\n ref={panelRef}\n className={cn(\n \"ods-flow-config-panel\",\n `ods-flow-config-panel--${variant}`,\n editingTitle && \"ods-flow-config-panel--editing-title\",\n className,\n )}\n style={{ width, ...style }}\n aria-label={title ?? \"Configuration\"}\n >\n {resizable && (\n <div\n className=\"ods-flow-config-panel__resize\"\n onPointerDown={onResizeDown}\n onPointerMove={onResizeMove}\n onPointerUp={onResizeUp}\n onPointerCancel={onResizeUp}\n aria-hidden=\"true\"\n />\n )}\n\n <header className=\"ods-flow-config-panel__header\">\n {kindLabel && <span className=\"ods-flow-config-panel__breadcrumb\">{kindLabel}</span>}\n <div className=\"ods-flow-config-panel__title-row\">\n {icon && <span className=\"ods-flow-config-panel__icon\">{icon}</span>}\n {editingTitle ? (\n <input\n className=\"ods-flow-config-panel__title-input\"\n value={draftTitle}\n autoFocus\n onChange={(e) => setDraftTitle(e.target.value)}\n onBlur={() => commitTitle(draftTitle)}\n onKeyDown={titleKeyDown}\n aria-label=\"Edit title\"\n />\n ) : (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__title\"\n onDoubleClick={() => {\n if (!onTitleChange) return;\n setDraftTitle(title ?? \"\");\n setEditingTitle(true);\n }}\n title={onTitleChange ? \"Double-click to rename\" : undefined}\n >\n {title ?? \"Untitled\"}\n </button>\n )}\n {onClose && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__close\"\n onClick={onClose}\n aria-label=\"Close panel\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <path\n d=\"M3 3l8 8M11 3l-8 8\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n )}\n </div>\n </header>\n\n {tabs && tabs.length > 1 && (\n <nav className=\"ods-flow-config-panel__tabs\" role=\"tablist\">\n {tabs.map((t) => (\n <button\n key={t.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTabId === t.id}\n disabled={t.disabled}\n className={cn(\n \"ods-flow-config-panel__tab\",\n activeTabId === t.id && \"ods-flow-config-panel__tab--active\",\n )}\n onClick={() => selectTab(t.id)}\n >\n <span>{t.label}</span>\n {t.badge !== undefined && t.badge !== null && (\n <span className=\"ods-flow-config-panel__tab-badge\">{t.badge}</span>\n )}\n </button>\n ))}\n </nav>\n )}\n\n {banner && <div className=\"ods-flow-config-panel__banner\">{banner}</div>}\n\n <div className=\"ods-flow-config-panel__body\" role=\"tabpanel\">\n {tabs ? activeTab?.content : children}\n </div>\n\n <footer className=\"ods-flow-config-panel__footer\">\n <span\n className={cn(\n \"ods-flow-config-panel__save-state\",\n `ods-flow-config-panel__save-state--${saveState}`,\n )}\n >\n <span\n className=\"ods-flow-config-panel__save-dot\"\n style={{ background: SAVE_STATE_DOT[saveState] }}\n aria-hidden=\"true\"\n />\n {SAVE_STATE_LABEL[saveState]}\n </span>\n {footer ?? (\n <div className=\"ods-flow-config-panel__footer-actions\">\n {onCancel && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__btn ods-flow-config-panel__btn--ghost\"\n onClick={onCancel}\n >\n Cancel\n </button>\n )}\n {onSave && (\n <button\n type=\"button\"\n className=\"ods-flow-config-panel__btn ods-flow-config-panel__btn--primary\"\n onClick={onSave}\n disabled={saveState === \"saving\" || saveState === \"clean\"}\n >\n {saveState === \"saving\" ? \"Saving…\" : \"Save\"}\n </button>\n )}\n </div>\n )}\n </footer>\n </aside>\n );\n}\n","\"use client\";\n\n// ════════════════════════════════════════════════════════════════════════\n// NodeToolbar — floating action bar attached to a node.\n// ════════════════════════════════════════════════════════════════════════\n// Dropped inside a custom node renderer (or anywhere with a\n// `FlowNodeContext`). Visible by default only when the host node is\n// selected; consumers can force it on with `isVisible`. Position-stable\n// across zoom — the bar applies `scale(1 / zoom)` to counter the viewport\n// transform so the buttons stay at constant pixel size.\n//\n// Pointer events on the toolbar are isolated from the node's drag /\n// connection gestures via `data-flow-no-drag=\"true\"` on the wrapper.\n\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../../../utils/cn\";\nimport type { HandlePosition } from \"../../editor\";\nimport { useViewport } from \"../../store/selectors\";\nimport { useFlowNodeContext } from \"../FlowNode/FlowNodeContext\";\n\nexport interface NodeToolbarProps {\n /** Force-visible state. Defaults to `undefined` → auto (visible iff\n * the host node is selected). Pass `true`/`false` to override. */\n isVisible?: boolean;\n /** Where the toolbar sits relative to the node. Defaults to `top`. */\n position?: HandlePosition;\n /** Pixel offset from the node's edge. Default 10. */\n offset?: number;\n /** Horizontal/vertical alignment along the chosen edge. Default center. */\n align?: \"start\" | \"center\" | \"end\";\n /** Toolbar contents (typically a row of `<button>` elements). */\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function NodeToolbar({\n isVisible,\n position = \"top\",\n offset = 10,\n align = \"center\",\n children,\n className,\n style,\n}: NodeToolbarProps) {\n const node = useFlowNodeContext();\n const viewport = useViewport();\n const show = isVisible ?? node.selected;\n if (!show) return null;\n\n // Counter the canvas's `scale(zoom)` so the toolbar reads at constant\n // pixel size at any zoom level. Translation is in flow-space units; the\n // inverse-scale wraps the visible content so children also stay crisp.\n const inverseScale = 1 / viewport.zoom;\n\n return (\n <div\n className={cn(\"ods-node-toolbar\", `ods-node-toolbar--${position}`, className)}\n data-flow-no-drag=\"true\"\n style={{\n ...positionStyle(position, offset, align),\n ...style,\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Inner element absorbs the inverse zoom so the toolbar's\n children stay screen-stable. */}\n <div\n className=\"ods-node-toolbar__inner\"\n style={{\n transform: `scale(${inverseScale})`,\n transformOrigin: transformOriginFor(position, align),\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n\nfunction positionStyle(\n side: HandlePosition,\n offset: number,\n align: NodeToolbarProps[\"align\"],\n): CSSProperties {\n const center = align === \"center\";\n const start = align === \"start\";\n switch (side) {\n case \"top\":\n return {\n position: \"absolute\",\n bottom: \"100%\",\n marginBottom: offset,\n left: start ? 0 : center ? \"50%\" : undefined,\n right: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateX(-50%)\" : undefined,\n };\n case \"bottom\":\n return {\n position: \"absolute\",\n top: \"100%\",\n marginTop: offset,\n left: start ? 0 : center ? \"50%\" : undefined,\n right: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateX(-50%)\" : undefined,\n };\n case \"left\":\n return {\n position: \"absolute\",\n right: \"100%\",\n marginRight: offset,\n top: start ? 0 : center ? \"50%\" : undefined,\n bottom: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateY(-50%)\" : undefined,\n };\n case \"right\":\n return {\n position: \"absolute\",\n left: \"100%\",\n marginLeft: offset,\n top: start ? 0 : center ? \"50%\" : undefined,\n bottom: align === \"end\" ? 0 : undefined,\n transform: center ? \"translateY(-50%)\" : undefined,\n };\n }\n}\n\nfunction transformOriginFor(side: HandlePosition, align: NodeToolbarProps[\"align\"]): string {\n // Choose an origin that visually attaches the bar to the node edge,\n // even after inverse-scaling. Defaults match the `position*` styles.\n if (side === \"top\")\n return align === \"start\" ? \"left bottom\" : align === \"end\" ? \"right bottom\" : \"center bottom\";\n if (side === \"bottom\")\n return align === \"start\" ? \"left top\" : align === \"end\" ? \"right top\" : \"center top\";\n if (side === \"left\")\n return align === \"start\" ? \"right top\" : align === \"end\" ? \"right bottom\" : \"right center\";\n return align === \"start\" ? \"left top\" : align === \"end\" ? \"left bottom\" : \"left center\";\n}\n","// ════════════════════════════════════════════════════════════════════════\n// Collapse helpers — when a group's `data.collapsed === true`, hide its\n// descendants AND re-route any edges that crossed the collapse boundary\n// so they terminate at the group instead of dangling at a hidden child.\n// ════════════════════════════════════════════════════════════════════════\n//\n// Consumers feed `nodes` + `edges` (their canonical state) through\n// `collapseFor()` and pass the result to <FlowCanvas>. The helpers are\n// pure — no React state, no DOM. They run in O(N + E) so they're cheap\n// to call on every render.\n\nimport type { WorkflowEdge, WorkflowNode } from \"../editor\";\n\ninterface CollapseResult {\n nodes: WorkflowNode[];\n edges: WorkflowEdge[];\n /** Per-collapsed-group: how many descendants got hidden. The host UI\n * reads this to render a \"N steps\" badge. */\n hiddenCountByGroupId: Map<string, number>;\n}\n\n/**\n * Given the canonical graph, return a derived graph that respects the\n * `collapsed` flag on every group node:\n *\n * - Descendants of a collapsed group are marked `hidden`.\n * - Edges that crossed the collapse boundary are rewritten: their\n * source (or target) becomes the collapsed group's id, and the\n * handle is reset to a synthetic perimeter handle (`__group_out` /\n * `__group_in`). Internal-to-internal edges are dropped.\n */\nexport function collapseFor(nodes: WorkflowNode[], edges: WorkflowEdge[]): CollapseResult {\n // Map child → topmost collapsed ancestor (if any).\n // If a node has multiple collapsed ancestors, the OUTERMOST wins so the\n // edge terminates at the visible boundary.\n const collapsedAncestorOf = new Map<string, string>();\n const parents = new Map<string, string>();\n for (const n of nodes) if (n.parentId) parents.set(n.id, n.parentId);\n\n // ANY node with `data.collapsed === true` collapses its descendants —\n // not just `group`. This lets `forEach` (and consumer-defined container\n // kinds) opt into the same collapse model without each one being\n // hard-coded in this helper.\n const isCollapsed = (node: WorkflowNode | undefined): boolean => {\n const d = node?.data as { collapsed?: boolean } | undefined;\n return !!d?.collapsed;\n };\n\n for (const n of nodes) {\n let cursor: string | undefined = n.id;\n let outermostCollapsed: string | undefined;\n const seen = new Set<string>([cursor]);\n while (cursor) {\n const p = parents.get(cursor);\n if (!p || seen.has(p)) break;\n seen.add(p);\n const parentNode = nodes.find((x) => x.id === p);\n if (isCollapsed(parentNode)) outermostCollapsed = p;\n cursor = p;\n }\n if (outermostCollapsed && outermostCollapsed !== n.id) {\n collapsedAncestorOf.set(n.id, outermostCollapsed);\n }\n }\n\n // Hide every descendant of a collapsed group.\n const outNodes: WorkflowNode[] = nodes.map((n) => {\n const anc = collapsedAncestorOf.get(n.id);\n if (anc) return { ...n, hidden: true };\n return n;\n });\n\n // Rewrite edges: any endpoint that sits inside a collapsed group gets\n // hoisted to the group itself. Edges entirely within a collapsed group\n // are dropped. Edges that start at the group and end inside it (or\n // vice versa) become self-loops that we also drop.\n //\n // **Boundary marker** — edges that get rewritten to terminate at a\n // collapsed group's perimeter are stroked with a sparse dot pattern so\n // the user immediately sees they're a \"summary\" of one or more hidden\n // inner connections rather than a real handle-to-handle edge. The\n // original `edge.style` is preserved; only `strokeDasharray` is\n // overlaid. FlowEdge gives `edge.style.strokeDasharray` precedence\n // over the type's default dash.\n const BOUNDARY_DASH = \"2 4\";\n const outEdges: WorkflowEdge[] = [];\n const hiddenCount = new Map<string, number>();\n for (const e of edges) {\n const srcAnc = collapsedAncestorOf.get(e.source);\n const tgtAnc = collapsedAncestorOf.get(e.target);\n if (srcAnc && tgtAnc && srcAnc === tgtAnc) continue; // wholly inside one collapsed group — drop\n let src = e.source;\n let srcHandle = e.sourceHandle;\n let tgt = e.target;\n let tgtHandle = e.targetHandle;\n const rewritten = !!(srcAnc || tgtAnc);\n if (srcAnc) {\n src = srcAnc;\n // Synthetic perimeter handle — the GroupNode renderer registers\n // matching `__group_out` / `__group_in` handles when collapsed.\n srcHandle = \"__group_out\";\n }\n if (tgtAnc) {\n tgt = tgtAnc;\n tgtHandle = \"__group_in\";\n }\n if (src === tgt) continue; // collapsed self-loop\n const nextEdge: WorkflowEdge = {\n ...e,\n source: src,\n sourceHandle: srcHandle,\n target: tgt,\n targetHandle: tgtHandle,\n };\n if (rewritten) {\n nextEdge.style = { ...(e.style ?? {}), strokeDasharray: BOUNDARY_DASH };\n }\n outEdges.push(nextEdge);\n }\n\n // Count hidden descendants per collapsed group for the badge.\n for (const [_id, anc] of collapsedAncestorOf) {\n hiddenCount.set(anc, (hiddenCount.get(anc) ?? 0) + 1);\n }\n\n return { nodes: outNodes, edges: outEdges, hiddenCountByGroupId: hiddenCount };\n}\n\n/**\n * Toggle the `data.collapsed` flag on a group node. Returns the patched\n * nodes array (immutable). Useful in event handlers.\n */\nexport function toggleGroupCollapse(groupId: string, nodes: WorkflowNode[]): WorkflowNode[] {\n return nodes.map((n) => {\n if (n.id !== groupId) return n;\n const data = (n.data ?? {}) as { collapsed?: boolean } & Record<string, unknown>;\n return { ...n, data: { ...data, collapsed: !data.collapsed } };\n });\n}\n"],"mappings":"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","names":["useCallback","useRef","useState","useState","useRef","useCallback","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/core",
3
- "version": "3.0.18-beta.8",
3
+ "version": "3.0.18-beta.9",
4
4
  "description": "OctaviaFlow Design System React components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",