import { r as __toESM } from "./chunk-C8JhGJ3N.js";
import { For, onUnmount } from "@pyreon/core";
import { batch, computed, signal } from "@pyreon/reactivity";

//#region ../../core/core/lib/jsx-runtime.js
/** Marker for fragment nodes — renders children without a wrapper element */
const Fragment = Symbol("Pyreon.Fragment");
/**
* Hyperscript function — the compiled output of JSX.
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
*
* Generic on P so TypeScript validates props match the component's signature
* at the call site, then stores the result in the loosely-typed VNode.
*/
/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
const EMPTY_PROPS = {};
function h(type, props, ...children) {
	return {
		type,
		props: props ?? EMPTY_PROPS,
		children: normalizeChildren(children),
		key: props?.key ?? null
	};
}
function normalizeChildren(children) {
	for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
	return children;
}
function flattenChildren(children) {
	const result = [];
	for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
	else result.push(child);
	return result;
}
/**
* JSX automatic runtime.
*
* When tsconfig has `"jsxImportSource": "@pyreon/core"`, the TS/bundler compiler
* rewrites JSX to imports from this file automatically:
*   <div class="x" />  →  jsx("div", { class: "x" })
*/
function jsx(type, props, key) {
	const { children, ...rest } = props;
	const propsWithKey = key != null ? {
		...rest,
		key
	} : rest;
	if (typeof type === "function") return h(type, children !== void 0 ? {
		...propsWithKey,
		children
	} : propsWithKey);
	return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
}
const jsxs = jsx;

//#endregion
//#region src/components/background.tsx
/**
* Background pattern for the flow canvas.
* Renders dots, lines, or cross patterns that move with the viewport.
*
* @example
* ```tsx
* <Flow instance={flow}>
*   <Background variant="dots" gap={20} />
* </Flow>
* ```
*/
function Background(props) {
	const { variant = "dots", gap = 20, size = 1, color = "#ddd" } = props;
	const patternId = `flow-bg-${variant}`;
	if (variant === "dots") return /* @__PURE__ */ jsxs("svg", {
		role: "img",
		"aria-label": "background pattern",
		class: "pyreon-flow-background",
		style: "position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none;",
		children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("pattern", {
			id: patternId,
			x: "0",
			y: "0",
			width: String(gap),
			height: String(gap),
			patternUnits: "userSpaceOnUse",
			children: /* @__PURE__ */ jsx("circle", {
				cx: String(size),
				cy: String(size),
				r: String(size),
				fill: color
			})
		}) }), /* @__PURE__ */ jsx("rect", {
			width: "100%",
			height: "100%",
			fill: `url(#${patternId})`
		})]
	});
	if (variant === "lines") return /* @__PURE__ */ jsxs("svg", {
		role: "img",
		"aria-label": "background pattern",
		class: "pyreon-flow-background",
		style: "position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none;",
		children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("pattern", {
			id: patternId,
			x: "0",
			y: "0",
			width: String(gap),
			height: String(gap),
			patternUnits: "userSpaceOnUse",
			children: [/* @__PURE__ */ jsx("line", {
				x1: "0",
				y1: String(gap),
				x2: String(gap),
				y2: String(gap),
				stroke: color,
				"stroke-width": String(size)
			}), /* @__PURE__ */ jsx("line", {
				x1: String(gap),
				y1: "0",
				x2: String(gap),
				y2: String(gap),
				stroke: color,
				"stroke-width": String(size)
			})]
		}) }), /* @__PURE__ */ jsx("rect", {
			width: "100%",
			height: "100%",
			fill: `url(#${patternId})`
		})]
	});
	return /* @__PURE__ */ jsxs("svg", {
		role: "img",
		"aria-label": "background pattern",
		class: "pyreon-flow-background",
		style: "position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none;",
		children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("pattern", {
			id: patternId,
			x: "0",
			y: "0",
			width: String(gap),
			height: String(gap),
			patternUnits: "userSpaceOnUse",
			children: [/* @__PURE__ */ jsx("line", {
				x1: String(gap / 2 - size * 2),
				y1: String(gap / 2),
				x2: String(gap / 2 + size * 2),
				y2: String(gap / 2),
				stroke: color,
				"stroke-width": String(size)
			}), /* @__PURE__ */ jsx("line", {
				x1: String(gap / 2),
				y1: String(gap / 2 - size * 2),
				x2: String(gap / 2),
				y2: String(gap / 2 + size * 2),
				stroke: color,
				"stroke-width": String(size)
			})]
		}) }), /* @__PURE__ */ jsx("rect", {
			width: "100%",
			height: "100%",
			fill: `url(#${patternId})`
		})]
	});
}

//#endregion
//#region src/components/controls.tsx
const positionStyles$2 = {
	"top-left": "top: 10px; left: 10px;",
	"top-right": "top: 10px; right: 10px;",
	"bottom-left": "bottom: 10px; left: 10px;",
	"bottom-right": "bottom: 10px; right: 10px;"
};
const ZoomInIcon = () => /* @__PURE__ */ jsxs("svg", {
	width: "16",
	height: "16",
	viewBox: "0 0 16 16",
	fill: "none",
	stroke: "currentColor",
	"stroke-width": "1.5",
	children: [
		/* @__PURE__ */ jsx("circle", {
			cx: "7",
			cy: "7",
			r: "5"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "7",
			y1: "5",
			x2: "7",
			y2: "9"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "5",
			y1: "7",
			x2: "9",
			y2: "7"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "11",
			y1: "11",
			x2: "14",
			y2: "14"
		})
	]
});
const ZoomOutIcon = () => /* @__PURE__ */ jsxs("svg", {
	width: "16",
	height: "16",
	viewBox: "0 0 16 16",
	fill: "none",
	stroke: "currentColor",
	"stroke-width": "1.5",
	children: [
		/* @__PURE__ */ jsx("circle", {
			cx: "7",
			cy: "7",
			r: "5"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "5",
			y1: "7",
			x2: "9",
			y2: "7"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "11",
			y1: "11",
			x2: "14",
			y2: "14"
		})
	]
});
const FitViewIcon = () => /* @__PURE__ */ jsxs("svg", {
	width: "16",
	height: "16",
	viewBox: "0 0 16 16",
	fill: "none",
	stroke: "currentColor",
	"stroke-width": "1.5",
	children: [
		/* @__PURE__ */ jsx("rect", {
			x: "2",
			y: "2",
			width: "12",
			height: "12",
			rx: "2"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "2",
			y1: "6",
			x2: "14",
			y2: "6"
		}),
		/* @__PURE__ */ jsx("line", {
			x1: "6",
			y1: "2",
			x2: "6",
			y2: "14"
		})
	]
});
const LockIcon = () => /* @__PURE__ */ jsxs("svg", {
	width: "16",
	height: "16",
	viewBox: "0 0 16 16",
	fill: "none",
	stroke: "currentColor",
	"stroke-width": "1.5",
	children: [/* @__PURE__ */ jsx("rect", {
		x: "3",
		y: "7",
		width: "10",
		height: "7",
		rx: "1"
	}), /* @__PURE__ */ jsx("path", { d: "M5 7V5a3 3 0 0 1 6 0v2" })]
});
/**
* Zoom and viewport controls for the flow canvas.
* Shows zoom in, zoom out, fit view, and optional lock button.
*
* @example
* ```tsx
* <Flow instance={flow}>
*   <Controls />
* </Flow>
* ```
*/
function Controls(props) {
	const { showZoomIn = true, showZoomOut = true, showFitView = true, showLock = false, position = "bottom-left", instance } = props;
	if (!instance) return null;
	const baseStyle = `position: absolute; ${positionStyles$2[position] ?? positionStyles$2["bottom-left"]} display: flex; flex-direction: column; gap: 2px; z-index: 5; background: white; border: 1px solid #ddd; border-radius: 6px; padding: 2px; box-shadow: 0 1px 4px rgba(0,0,0,0.08);`;
	const btnStyle = "width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; border-radius: 4px; cursor: pointer; color: #555; padding: 0;";
	return () => {
		const zoomPercent = Math.round(instance.zoom() * 100);
		return /* @__PURE__ */ jsxs("div", {
			class: "pyreon-flow-controls",
			style: baseStyle,
			children: [
				showZoomIn && /* @__PURE__ */ jsx("button", {
					type: "button",
					style: btnStyle,
					title: "Zoom in",
					onClick: () => instance.zoomIn(),
					children: /* @__PURE__ */ jsx(ZoomInIcon, {})
				}),
				showZoomOut && /* @__PURE__ */ jsx("button", {
					type: "button",
					style: btnStyle,
					title: "Zoom out",
					onClick: () => instance.zoomOut(),
					children: /* @__PURE__ */ jsx(ZoomOutIcon, {})
				}),
				showFitView && /* @__PURE__ */ jsx("button", {
					type: "button",
					style: btnStyle,
					title: "Fit view",
					onClick: () => instance.fitView(),
					children: /* @__PURE__ */ jsx(FitViewIcon, {})
				}),
				showLock && /* @__PURE__ */ jsx("button", {
					type: "button",
					style: btnStyle,
					title: "Lock/unlock",
					onClick: () => {},
					children: /* @__PURE__ */ jsx(LockIcon, {})
				}),
				/* @__PURE__ */ jsxs("div", {
					style: "font-size: 10px; text-align: center; color: #999; padding: 2px 0; user-select: none;",
					title: "Current zoom level",
					children: [zoomPercent, "%"]
				})
			]
		});
	};
}

//#endregion
//#region src/types.ts
let Position = /* @__PURE__ */ function(Position) {
	Position["Top"] = "top";
	Position["Right"] = "right";
	Position["Bottom"] = "bottom";
	Position["Left"] = "left";
	return Position;
}({});

//#endregion
//#region src/edges.ts
/**
* Auto-detect the best handle position based on relative node positions.
* If the node has configured handles, uses those. Otherwise picks the
* closest edge (top/right/bottom/left) based on direction to the other node.
*/
function getSmartHandlePositions(sourceNode, targetNode) {
	const sw = sourceNode.width ?? 150;
	const sh = sourceNode.height ?? 40;
	const tw = targetNode.width ?? 150;
	const th = targetNode.height ?? 40;
	const dx = targetNode.position.x + tw / 2 - (sourceNode.position.x + sw / 2);
	const dy = targetNode.position.y + th / 2 - (sourceNode.position.y + sh / 2);
	const sourceHandle = sourceNode.sourceHandles?.[0];
	const targetHandle = targetNode.targetHandles?.[0];
	return {
		sourcePosition: sourceHandle ? sourceHandle.position : Math.abs(dx) > Math.abs(dy) ? dx > 0 ? Position.Right : Position.Left : dy > 0 ? Position.Bottom : Position.Top,
		targetPosition: targetHandle ? targetHandle.position : Math.abs(dx) > Math.abs(dy) ? dx > 0 ? Position.Left : Position.Right : dy > 0 ? Position.Top : Position.Bottom
	};
}
/**
* Get the center point between source and target positions.
*/
function getCenter(source, target) {
	return {
		x: (source.x + target.x) / 2,
		y: (source.y + target.y) / 2
	};
}
/**
* Get the handle position offset for a given position (top/right/bottom/left).
*/
function getHandlePosition(position, nodeX, nodeY, nodeWidth, nodeHeight, _handleId) {
	switch (position) {
		case Position.Top: return {
			x: nodeX + nodeWidth / 2,
			y: nodeY
		};
		case Position.Right: return {
			x: nodeX + nodeWidth,
			y: nodeY + nodeHeight / 2
		};
		case Position.Bottom: return {
			x: nodeX + nodeWidth / 2,
			y: nodeY + nodeHeight
		};
		case Position.Left: return {
			x: nodeX,
			y: nodeY + nodeHeight / 2
		};
	}
}
/**
* Calculate a cubic bezier edge path between two points.
*
* @example
* ```ts
* const { path, labelX, labelY } = getBezierPath({
*   sourceX: 0, sourceY: 0, sourcePosition: Position.Right,
*   targetX: 200, targetY: 100, targetPosition: Position.Left,
* })
* // path = "M0,0 C100,0 100,100 200,100"
* ```
*/
function getBezierPath(params) {
	const { sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, curvature = .25 } = params;
	const distX = Math.abs(targetX - sourceX);
	const distY = Math.abs(targetY - sourceY);
	const offset = Math.sqrt(distX * distX + distY * distY) * curvature;
	let sourceControlX = sourceX;
	let sourceControlY = sourceY;
	let targetControlX = targetX;
	let targetControlY = targetY;
	switch (sourcePosition) {
		case Position.Top:
			sourceControlY = sourceY - offset;
			break;
		case Position.Bottom:
			sourceControlY = sourceY + offset;
			break;
		case Position.Left:
			sourceControlX = sourceX - offset;
			break;
		case Position.Right:
			sourceControlX = sourceX + offset;
			break;
	}
	switch (targetPosition) {
		case Position.Top:
			targetControlY = targetY - offset;
			break;
		case Position.Bottom:
			targetControlY = targetY + offset;
			break;
		case Position.Left:
			targetControlX = targetX - offset;
			break;
		case Position.Right:
			targetControlX = targetX + offset;
			break;
	}
	const center = getCenter({
		x: sourceX,
		y: sourceY
	}, {
		x: targetX,
		y: targetY
	});
	return {
		path: `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
		labelX: center.x,
		labelY: center.y
	};
}
/**
* Calculate a smoothstep edge path — horizontal/vertical segments with rounded corners.
*/
function getSmoothStepPath(params) {
	const { sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, borderRadius = 5, offset = 20 } = params;
	const isHorizontalSource = sourcePosition === Position.Left || sourcePosition === Position.Right;
	const isHorizontalTarget = targetPosition === Position.Left || targetPosition === Position.Right;
	const sourceOffsetX = sourcePosition === Position.Right ? offset : sourcePosition === Position.Left ? -offset : 0;
	const sourceOffsetY = sourcePosition === Position.Bottom ? offset : sourcePosition === Position.Top ? -offset : 0;
	const targetOffsetX = targetPosition === Position.Right ? offset : targetPosition === Position.Left ? -offset : 0;
	const targetOffsetY = targetPosition === Position.Bottom ? offset : targetPosition === Position.Top ? -offset : 0;
	const sX = sourceX + sourceOffsetX;
	const sY = sourceY + sourceOffsetY;
	const tX = targetX + targetOffsetX;
	const tY = targetY + targetOffsetY;
	const center = getCenter({
		x: sourceX,
		y: sourceY
	}, {
		x: targetX,
		y: targetY
	});
	const midX = (sX + tX) / 2;
	const midY = (sY + tY) / 2;
	const r = borderRadius;
	let path;
	if (isHorizontalSource && !isHorizontalTarget) {
		const cornerY = tY;
		path = `M${sourceX},${sourceY} L${sX},${sY} L${sX},${cornerY > sY ? cornerY - r : cornerY + r} Q${sX},${cornerY} ${sX + (tX > sX ? r : -r)},${cornerY} L${tX},${cornerY} L${targetX},${targetY}`;
	} else if (!isHorizontalSource && isHorizontalTarget) {
		const cornerX = tX;
		path = `M${sourceX},${sourceY} L${sX},${sY} L${cornerX > sX ? cornerX - r : cornerX + r},${sY} Q${cornerX},${sY} ${cornerX},${sY + (tY > sY ? r : -r)} L${cornerX},${tY} L${targetX},${targetY}`;
	} else if (isHorizontalSource && isHorizontalTarget) path = `M${sourceX},${sourceY} L${sX},${sourceY} L${midX},${sourceY} Q${midX},${sourceY} ${midX},${midY} L${midX},${targetY} L${tX},${targetY} L${targetX},${targetY}`;
	else path = `M${sourceX},${sourceY} L${sourceX},${sY} L${sourceX},${midY} Q${sourceX},${midY} ${midX},${midY} L${targetX},${midY} L${targetX},${tY} L${targetX},${targetY}`;
	return {
		path,
		labelX: center.x,
		labelY: center.y
	};
}
/**
* Calculate a straight edge path — direct line between two points.
*/
function getStraightPath(params) {
	const { sourceX, sourceY, targetX, targetY } = params;
	const center = getCenter({
		x: sourceX,
		y: sourceY
	}, {
		x: targetX,
		y: targetY
	});
	return {
		path: `M${sourceX},${sourceY} L${targetX},${targetY}`,
		labelX: center.x,
		labelY: center.y
	};
}
/**
* Calculate a step edge path — right-angle segments with no rounding.
*/
function getStepPath(params) {
	return getSmoothStepPath({
		...params,
		borderRadius: 0
	});
}
/**
* Calculate an edge path that passes through waypoints.
* Uses line segments with optional smoothing.
*/
function getWaypointPath(params) {
	const { sourceX, sourceY, targetX, targetY, waypoints } = params;
	if (waypoints.length === 0) return getStraightPath({
		sourceX,
		sourceY,
		targetX,
		targetY
	});
	const path = `M${[
		{
			x: sourceX,
			y: sourceY
		},
		...waypoints,
		{
			x: targetX,
			y: targetY
		}
	].map((p) => `${p.x},${p.y}`).join(" L")}`;
	const midPoint = waypoints[Math.floor(waypoints.length / 2)] ?? {
		x: (sourceX + targetX) / 2,
		y: (sourceY + targetY) / 2
	};
	return {
		path,
		labelX: midPoint.x,
		labelY: midPoint.y
	};
}
/**
* Get the edge path for a given edge type.
*/
function getEdgePath(type, sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition) {
	switch (type) {
		case "smoothstep": return getSmoothStepPath({
			sourceX,
			sourceY,
			sourcePosition,
			targetX,
			targetY,
			targetPosition
		});
		case "straight": return getStraightPath({
			sourceX,
			sourceY,
			targetX,
			targetY
		});
		case "step": return getStepPath({
			sourceX,
			sourceY,
			sourcePosition,
			targetX,
			targetY,
			targetPosition
		});
		default: return getBezierPath({
			sourceX,
			sourceY,
			sourcePosition,
			targetX,
			targetY,
			targetPosition
		});
	}
}

//#endregion
//#region src/components/flow-component.tsx
/**
* Default node renderer — simple labeled box.
*
* Every prop except `id` is an accessor function. `data()`, `selected()`,
* and `dragging()` are read inside reactive scopes (the `style` and
* children thunks) so the node patches in place when any underlying
* state changes — including drags, selection clicks, and data
* updates — without re-mounting the component.
*/
function DefaultNode(props) {
	return /* @__PURE__ */ jsx("div", {
		style: () => {
			return `padding: 8px 16px; background: white; border: 2px solid ${props.selected() ? "#3b82f6" : "#ddd"}; border-radius: 6px; font-size: 13px; min-width: 80px; text-align: center; cursor: ${props.dragging() ? "grabbing" : "grab"}; user-select: none;`;
		},
		children: () => props.data()?.label ?? props.id
	});
}
const emptyConnection = {
	active: false,
	sourceNodeId: "",
	sourceHandleId: "",
	sourcePosition: Position.Right,
	sourceX: 0,
	sourceY: 0,
	currentX: 0,
	currentY: 0
};
const emptySelectionBox = {
	active: false,
	startX: 0,
	startY: 0,
	currentX: 0,
	currentY: 0
};
const emptyDrag = {
	active: false,
	nodeId: "",
	startX: 0,
	startY: 0,
	startPositions: /* @__PURE__ */ new Map()
};
/**
* Compute a path geometry packet from live source/target nodes. The
* EdgeLayer's per-edge accessor calls this inside a reactive scope
* so position updates flow through. Pulled out as a top-level
* helper so the EdgeLayer body stays readable.
*/
function computeEdgeGeometry(edge, sourceNode, targetNode) {
	const sourceW = sourceNode.width ?? 150;
	const sourceH = sourceNode.height ?? 40;
	const targetW = targetNode.width ?? 150;
	const targetH = targetNode.height ?? 40;
	const { sourcePosition, targetPosition } = getSmartHandlePositions(sourceNode, targetNode);
	const sourcePos = getHandlePosition(sourcePosition, sourceNode.position.x, sourceNode.position.y, sourceW, sourceH);
	const targetPos = getHandlePosition(targetPosition, targetNode.position.x, targetNode.position.y, targetW, targetH);
	const { path, labelX, labelY } = edge.waypoints?.length ? getWaypointPath({
		sourceX: sourcePos.x,
		sourceY: sourcePos.y,
		targetX: targetPos.x,
		targetY: targetPos.y,
		waypoints: edge.waypoints
	}) : getEdgePath(edge.type ?? "bezier", sourcePos.x, sourcePos.y, sourcePosition, targetPos.x, targetPos.y, targetPosition);
	return {
		sourceX: sourcePos.x,
		sourceY: sourcePos.y,
		targetX: targetPos.x,
		targetY: targetPos.y,
		path,
		labelX,
		labelY
	};
}
function EdgeLayer(props) {
	const { instance, connectionState, edgeTypes } = props;
	return () => /* @__PURE__ */ jsxs("svg", {
		role: "img",
		"aria-label": "flow edges",
		class: "pyreon-flow-edges",
		style: "position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible;",
		children: [
			/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("marker", {
				id: "flow-arrowhead",
				markerWidth: "10",
				markerHeight: "7",
				refX: "10",
				refY: "3.5",
				orient: "auto",
				children: /* @__PURE__ */ jsx("polygon", {
					points: "0 0, 10 3.5, 0 7",
					fill: "#999"
				})
			}) }),
			/* @__PURE__ */ jsx(For, {
				each: () => instance.edges(),
				by: (e) => e.id ?? "",
				children: (initialEdge) => {
					const edgeId = initialEdge.id ?? "";
					let liveEdge = () => {
						return instance.edges().find((e) => e.id === edgeId) ?? initialEdge;
					};
					let geometry = () => {
						const e = liveEdge();
						const all = instance.nodes();
						const sourceNode = all.find((n) => n.id === e.source);
						const targetNode = all.find((n) => n.id === e.target);
						if (!sourceNode || !targetNode) return null;
						return computeEdgeGeometry(e, sourceNode, targetNode);
					};
					let isSelected = () => edgeId ? instance.selectedEdges().includes(edgeId) : false;
					const CustomEdge = initialEdge.type && edgeTypes?.[initialEdge.type];
					if (CustomEdge) return /* @__PURE__ */ jsx("g", {
						onClick: () => edgeId && instance.selectEdge(edgeId),
						children: /* @__PURE__ */ jsx(CustomEdge, {
							edge: initialEdge,
							sourceX: () => geometry()?.sourceX ?? 0,
							sourceY: () => geometry()?.sourceY ?? 0,
							targetX: () => geometry()?.targetX ?? 0,
							targetY: () => geometry()?.targetY ?? 0,
							selected: isSelected
						})
					});
					return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("path", {
						d: () => geometry()?.path ?? "",
						fill: "none",
						stroke: () => isSelected() ? "#3b82f6" : "#999",
						"stroke-width": () => isSelected() ? "2" : "1.5",
						"marker-end": "url(#flow-arrowhead)",
						class: () => liveEdge().animated ? "pyreon-flow-edge-animated" : "",
						style: () => `pointer-events: stroke; cursor: pointer; ${liveEdge().style ?? ""}`,
						onClick: () => {
							if (edgeId) instance.selectEdge(edgeId);
							instance._emit.edgeClick(liveEdge());
						}
					}), () => {
						const e = liveEdge();
						const g = geometry();
						if (!e.label || !g) return null;
						return /* @__PURE__ */ jsx("text", {
							x: String(g.labelX),
							y: String(g.labelY),
							"text-anchor": "middle",
							"dominant-baseline": "central",
							style: "font-size: 11px; fill: #666; pointer-events: none;",
							children: e.label
						});
					}] });
				}
			}),
			() => {
				const conn = connectionState();
				if (!conn.active) return null;
				return /* @__PURE__ */ jsx("path", {
					d: getEdgePath("bezier", conn.sourceX, conn.sourceY, conn.sourcePosition, conn.currentX, conn.currentY, Position.Left).path,
					fill: "none",
					stroke: "#3b82f6",
					"stroke-width": "2",
					"stroke-dasharray": "5,5"
				});
			}
		]
	});
}
function NodeLayer(props) {
	const { instance, nodeTypes, draggingNodeId, onNodePointerDown, onHandlePointerDown } = props;
	return () => /* @__PURE__ */ jsx(For, {
		each: () => instance.nodes(),
		by: (n) => n.id,
		children: (initialNode) => {
			const id = initialNode.id;
			const node = () => {
				return instance.nodes().find((n) => n.id === id) ?? initialNode;
			};
			const isSelected = () => instance.selectedNodes().includes(id);
			const isDragging = () => draggingNodeId() === id;
			const NodeComponent = initialNode.type && nodeTypes[initialNode.type] || nodeTypes.default;
			return /* @__PURE__ */ jsx("div", {
				class: () => {
					return `pyreon-flow-node ${node().class ?? ""} ${isSelected() ? "selected" : ""} ${isDragging() ? "dragging" : ""}`;
				},
				style: () => {
					const n = node();
					return `position: absolute; transform: translate(${n.position.x}px, ${n.position.y}px); z-index: ${isDragging() ? 1e3 : isSelected() ? 100 : 0}; ${n.style ?? ""}`;
				},
				"data-nodeid": id,
				onClick: (e) => {
					e.stopPropagation();
					instance.selectNode(id, e.shiftKey);
					instance._emit.nodeClick(node());
				},
				onDblClick: (e) => {
					e.stopPropagation();
					instance._emit.nodeDoubleClick(node());
				},
				onPointerDown: (e) => {
					const handle = e.target.closest(".pyreon-flow-handle");
					if (handle) {
						onHandlePointerDown(e, id, handle.getAttribute("data-handletype") ?? "source", handle.getAttribute("data-handleid") ?? "source", handle.getAttribute("data-handleposition") ?? Position.Right);
						return;
					}
					const n = node();
					if (n.draggable !== false && instance.config.nodesDraggable !== false) onNodePointerDown(e, n);
				},
				children: /* @__PURE__ */ jsx(NodeComponent, {
					id,
					data: () => node().data,
					selected: isSelected,
					dragging: isDragging
				})
			});
		}
	});
}
/**
* The main Flow component — renders the interactive flow diagram.
*
* Supports node dragging, connection drawing, custom node types,
* pan/zoom, and all standard flow interactions.
*
* @example
* ```tsx
* const flow = createFlow({
*   nodes: [...],
*   edges: [...],
* })
*
* <Flow instance={flow} nodeTypes={{ custom: CustomNode }}>
*   <Background />
*   <MiniMap />
*   <Controls />
* </Flow>
* ```
*/
function Flow(props) {
	const { instance, children, edgeTypes } = props;
	const nodeTypes = {
		default: DefaultNode,
		input: DefaultNode,
		output: DefaultNode,
		...props.nodeTypes
	};
	const dragState = signal({ ...emptyDrag });
	const connectionState = signal({ ...emptyConnection });
	const selectionBox = signal({ ...emptySelectionBox });
	const helperLines = signal({
		x: null,
		y: null
	});
	const draggingNodeId = () => dragState().active ? dragState().nodeId : "";
	const handleNodePointerDown = (e, node) => {
		e.stopPropagation();
		const selected = instance.selectedNodes();
		const startPositions = /* @__PURE__ */ new Map();
		startPositions.set(node.id, { ...node.position });
		if (selected.includes(node.id)) for (const nid of selected) {
			if (nid === node.id) continue;
			const n = instance.getNode(nid);
			if (n) startPositions.set(nid, { ...n.position });
		}
		instance.pushHistory();
		dragState.set({
			active: true,
			nodeId: node.id,
			startX: e.clientX,
			startY: e.clientY,
			startPositions
		});
		instance.selectNode(node.id, e.shiftKey);
		instance._emit.nodeDragStart(node);
		const container = e.currentTarget.closest(".pyreon-flow");
		if (container) container.setPointerCapture(e.pointerId);
	};
	const handleHandlePointerDown = (e, nodeId, _handleType, handleId, position) => {
		e.stopPropagation();
		e.preventDefault();
		const node = instance.getNode(nodeId);
		if (!node) return;
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		const handlePos = getHandlePosition(position, node.position.x, node.position.y, w, h);
		connectionState.set({
			active: true,
			sourceNodeId: nodeId,
			sourceHandleId: handleId,
			sourcePosition: position,
			sourceX: handlePos.x,
			sourceY: handlePos.y,
			currentX: handlePos.x,
			currentY: handlePos.y
		});
		const container = e.target.closest(".pyreon-flow");
		if (container) container.setPointerCapture(e.pointerId);
	};
	const handleWheel = (e) => {
		if (instance.config.zoomable === false) return;
		e.preventDefault();
		const delta = -e.deltaY * .001;
		const newZoom = Math.min(Math.max(instance.viewport.peek().zoom * (1 + delta), instance.config.minZoom ?? .1), instance.config.maxZoom ?? 4);
		const rect = e.currentTarget.getBoundingClientRect();
		const mouseX = e.clientX - rect.left;
		const mouseY = e.clientY - rect.top;
		const vp = instance.viewport.peek();
		const scale = newZoom / vp.zoom;
		instance.viewport.set({
			x: mouseX - (mouseX - vp.x) * scale,
			y: mouseY - (mouseY - vp.y) * scale,
			zoom: newZoom
		});
	};
	let isPanning = false;
	let panStartX = 0;
	let panStartY = 0;
	let panStartVpX = 0;
	let panStartVpY = 0;
	const handlePointerDown = (e) => {
		if (instance.config.pannable === false) return;
		const target = e.target;
		if (target.closest(".pyreon-flow-node")) return;
		if (target.closest(".pyreon-flow-handle")) return;
		if (e.shiftKey && instance.config.multiSelect !== false) {
			const container = e.currentTarget;
			const rect = container.getBoundingClientRect();
			const vp = instance.viewport.peek();
			const flowX = (e.clientX - rect.left - vp.x) / vp.zoom;
			const flowY = (e.clientY - rect.top - vp.y) / vp.zoom;
			selectionBox.set({
				active: true,
				startX: flowX,
				startY: flowY,
				currentX: flowX,
				currentY: flowY
			});
			container.setPointerCapture(e.pointerId);
			return;
		}
		isPanning = true;
		panStartX = e.clientX;
		panStartY = e.clientY;
		const vp = instance.viewport.peek();
		panStartVpX = vp.x;
		panStartVpY = vp.y;
		e.currentTarget.setPointerCapture(e.pointerId);
		instance.clearSelection();
	};
	const handlePointerMove = (e) => {
		const drag = dragState.peek();
		const conn = connectionState.peek();
		const sel = selectionBox.peek();
		if (sel.active) {
			const rect = e.currentTarget.getBoundingClientRect();
			const vp = instance.viewport.peek();
			const flowX = (e.clientX - rect.left - vp.x) / vp.zoom;
			const flowY = (e.clientY - rect.top - vp.y) / vp.zoom;
			selectionBox.set({
				...sel,
				currentX: flowX,
				currentY: flowY
			});
			return;
		}
		if (drag.active) {
			const vp = instance.viewport.peek();
			const dx = (e.clientX - drag.startX) / vp.zoom;
			const dy = (e.clientY - drag.startY) / vp.zoom;
			const primaryStart = drag.startPositions.get(drag.nodeId);
			if (!primaryStart) return;
			const rawPos = {
				x: primaryStart.x + dx,
				y: primaryStart.y + dy
			};
			const snap = instance.getSnapLines(drag.nodeId, rawPos);
			helperLines.set({
				x: snap.x,
				y: snap.y
			});
			const actualDx = snap.snappedPosition.x - primaryStart.x;
			const actualDy = snap.snappedPosition.y - primaryStart.y;
			instance.nodes.update((nds) => nds.map((n) => {
				const start = drag.startPositions.get(n.id);
				if (!start) return n;
				return {
					...n,
					position: {
						x: start.x + actualDx,
						y: start.y + actualDy
					}
				};
			}));
			return;
		}
		if (conn.active) {
			const rect = e.currentTarget.getBoundingClientRect();
			const vp = instance.viewport.peek();
			const flowX = (e.clientX - rect.left - vp.x) / vp.zoom;
			const flowY = (e.clientY - rect.top - vp.y) / vp.zoom;
			connectionState.set({
				...conn,
				currentX: flowX,
				currentY: flowY
			});
			return;
		}
		if (isPanning) {
			const dx = e.clientX - panStartX;
			const dy = e.clientY - panStartY;
			instance.viewport.set({
				...instance.viewport.peek(),
				x: panStartVpX + dx,
				y: panStartVpY + dy
			});
		}
	};
	const handlePointerUp = (e) => {
		const drag = dragState.peek();
		const conn = connectionState.peek();
		const sel = selectionBox.peek();
		if (sel.active) {
			const minX = Math.min(sel.startX, sel.currentX);
			const minY = Math.min(sel.startY, sel.currentY);
			const maxX = Math.max(sel.startX, sel.currentX);
			const maxY = Math.max(sel.startY, sel.currentY);
			instance.clearSelection();
			for (const node of instance.nodes.peek()) {
				const w = node.width ?? 150;
				const h = node.height ?? 40;
				const nx = node.position.x;
				const ny = node.position.y;
				if (nx + w > minX && nx < maxX && ny + h > minY && ny < maxY) instance.selectNode(node.id, true);
			}
			selectionBox.set({ ...emptySelectionBox });
			return;
		}
		if (drag.active) {
			const node = instance.getNode(drag.nodeId);
			if (node) instance._emit.nodeDragEnd(node);
			dragState.set({ ...emptyDrag });
			helperLines.set({
				x: null,
				y: null
			});
		}
		if (conn.active) {
			const handle = e.target.closest(".pyreon-flow-handle");
			if (handle) {
				const targetNodeId = handle.closest(".pyreon-flow-node")?.getAttribute("data-nodeid") ?? "";
				const targetHandleId = handle.getAttribute("data-handleid") ?? "target";
				if (targetNodeId && targetNodeId !== conn.sourceNodeId) {
					const connection = {
						source: conn.sourceNodeId,
						target: targetNodeId,
						sourceHandle: conn.sourceHandleId,
						targetHandle: targetHandleId
					};
					if (instance.isValidConnection(connection)) instance.addEdge({
						source: connection.source,
						target: connection.target,
						...connection.sourceHandle != null ? { sourceHandle: connection.sourceHandle } : {},
						...connection.targetHandle != null ? { targetHandle: connection.targetHandle } : {}
					});
				}
			}
			connectionState.set({ ...emptyConnection });
		}
		isPanning = false;
	};
	const handleKeyDown = (e) => {
		if (e.key === "Delete" || e.key === "Backspace") {
			const target = e.target;
			if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return;
			instance.pushHistory();
			instance.deleteSelected();
		}
		if (e.key === "Escape") {
			instance.clearSelection();
			connectionState.set({ ...emptyConnection });
		}
		if (e.key === "a" && (e.metaKey || e.ctrlKey)) {
			e.preventDefault();
			instance.selectAll();
		}
		if (e.key === "c" && (e.metaKey || e.ctrlKey)) instance.copySelected();
		if (e.key === "v" && (e.metaKey || e.ctrlKey)) instance.paste();
		if (e.key === "z" && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
			e.preventDefault();
			instance.undo();
		}
		if (e.key === "z" && (e.metaKey || e.ctrlKey) && e.shiftKey) {
			e.preventDefault();
			instance.redo();
		}
	};
	let lastTouchDist = 0;
	let lastTouchCenter = {
		x: 0,
		y: 0
	};
	const handleTouchStart = (e) => {
		if (e.touches.length === 2) {
			e.preventDefault();
			const t1 = e.touches[0];
			const t2 = e.touches[1];
			lastTouchDist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);
			lastTouchCenter = {
				x: (t1.clientX + t2.clientX) / 2,
				y: (t1.clientY + t2.clientY) / 2
			};
		}
	};
	const handleTouchMove = (e) => {
		if (e.touches.length === 2 && instance.config.zoomable !== false) {
			e.preventDefault();
			const t1 = e.touches[0];
			const t2 = e.touches[1];
			const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);
			const center = {
				x: (t1.clientX + t2.clientX) / 2,
				y: (t1.clientY + t2.clientY) / 2
			};
			const vp = instance.viewport.peek();
			const scaleFactor = dist / lastTouchDist;
			const newZoom = Math.min(Math.max(vp.zoom * scaleFactor, instance.config.minZoom ?? .1), instance.config.maxZoom ?? 4);
			const rect = e.currentTarget.getBoundingClientRect();
			const mouseX = center.x - rect.left;
			const mouseY = center.y - rect.top;
			const scale = newZoom / vp.zoom;
			const panDx = center.x - lastTouchCenter.x;
			const panDy = center.y - lastTouchCenter.y;
			instance.viewport.set({
				x: mouseX - (mouseX - vp.x) * scale + panDx,
				y: mouseY - (mouseY - vp.y) * scale + panDy,
				zoom: newZoom
			});
			lastTouchDist = dist;
			lastTouchCenter = center;
		}
	};
	let resizeObserver = null;
	const containerRef = (el) => {
		if (resizeObserver) {
			resizeObserver.disconnect();
			resizeObserver = null;
		}
		if (!el) return;
		const updateSize = () => {
			const rect = el.getBoundingClientRect();
			instance.containerSize.set({
				width: rect.width,
				height: rect.height
			});
		};
		updateSize();
		resizeObserver = new ResizeObserver(updateSize);
		resizeObserver.observe(el);
	};
	const containerStyle = `position: relative; width: 100%; height: 100%; overflow: hidden; outline: none; touch-action: none; ${props.style ?? ""}`;
	return /* @__PURE__ */ jsxs("div", {
		ref: containerRef,
		class: `pyreon-flow ${props.class ?? ""}`,
		style: containerStyle,
		tabIndex: 0,
		onWheel: handleWheel,
		onPointerDown: handlePointerDown,
		onPointerMove: handlePointerMove,
		onPointerUp: handlePointerUp,
		onTouchStart: handleTouchStart,
		onTouchMove: handleTouchMove,
		onKeyDown: handleKeyDown,
		children: [children, () => {
			const vp = instance.viewport();
			return /* @__PURE__ */ jsxs("div", {
				class: "pyreon-flow-viewport",
				style: `position: absolute; transform-origin: 0 0; transform: translate(${vp.x}px, ${vp.y}px) scale(${vp.zoom});`,
				children: [
					/* @__PURE__ */ jsx(EdgeLayer, {
						instance,
						connectionState: () => connectionState(),
						...edgeTypes != null ? { edgeTypes } : {}
					}),
					() => {
						const sel = selectionBox();
						if (!sel.active) return null;
						return /* @__PURE__ */ jsx("div", {
							class: "pyreon-flow-selection-box",
							style: `position: absolute; left: ${Math.min(sel.startX, sel.currentX)}px; top: ${Math.min(sel.startY, sel.currentY)}px; width: ${Math.abs(sel.currentX - sel.startX)}px; height: ${Math.abs(sel.currentY - sel.startY)}px; border: 1px dashed #3b82f6; background: rgba(59, 130, 246, 0.08); pointer-events: none; z-index: 10;`
						});
					},
					() => {
						const lines = helperLines();
						if (!lines.x && !lines.y) return null;
						return /* @__PURE__ */ jsxs("svg", {
							role: "img",
							"aria-label": "helper lines",
							style: "position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; z-index: 5;",
							children: [lines.x !== null && /* @__PURE__ */ jsx("line", {
								x1: String(lines.x),
								y1: "-10000",
								x2: String(lines.x),
								y2: "10000",
								stroke: "#3b82f6",
								"stroke-width": "0.5",
								"stroke-dasharray": "4,4"
							}), lines.y !== null && /* @__PURE__ */ jsx("line", {
								x1: "-10000",
								y1: String(lines.y),
								x2: "10000",
								y2: String(lines.y),
								stroke: "#3b82f6",
								"stroke-width": "0.5",
								"stroke-dasharray": "4,4"
							})]
						});
					},
					/* @__PURE__ */ jsx(NodeLayer, {
						instance,
						nodeTypes,
						draggingNodeId,
						onNodePointerDown: handleNodePointerDown,
						onHandlePointerDown: handleHandlePointerDown
					})
				]
			});
		}]
	});
}

//#endregion
//#region src/components/handle.tsx
const positionOffset = {
	top: "top: -4px; left: 50%; transform: translateX(-50%);",
	right: "right: -4px; top: 50%; transform: translateY(-50%);",
	bottom: "bottom: -4px; left: 50%; transform: translateX(-50%);",
	left: "left: -4px; top: 50%; transform: translateY(-50%);"
};
/**
* Connection handle — attachment point on a node where edges connect.
* Place inside custom node components.
*
* @example
* ```tsx
* function CustomNode({ data }: NodeComponentProps) {
*   return (
*     <div class="custom-node">
*       <Handle type="target" position={Position.Left} />
*       <span>{data.label}</span>
*       <Handle type="source" position={Position.Right} />
*     </div>
*   )
* }
* ```
*/
function Handle(props) {
	const baseStyle = `position: absolute; ${positionOffset[props.position] ?? positionOffset.bottom} width: 8px; height: 8px; background: #555; border: 2px solid white; border-radius: 50%; cursor: crosshair; z-index: 1; ${props.style ?? ""}`;
	return /* @__PURE__ */ jsx("div", {
		class: `pyreon-flow-handle pyreon-flow-handle-${props.type} ${props.class ?? ""}`,
		style: baseStyle,
		"data-handletype": props.type,
		"data-handleid": props.id ?? props.type,
		"data-handleposition": props.position
	});
}

//#endregion
//#region src/components/minimap.tsx
/**
* Miniature overview of the flow diagram showing all nodes
* and the current viewport position. Click to navigate.
*
* @example
* ```tsx
* <Flow instance={flow}>
*   <MiniMap nodeColor={(n) => n.type === 'error' ? 'red' : '#ddd'} />
* </Flow>
* ```
*/
function MiniMap(props) {
	const { width = 200, height = 150, nodeColor = "#e2e8f0", maskColor = "rgba(0, 0, 0, 0.08)", instance } = props;
	if (!instance) return null;
	const containerStyle = `position: absolute; bottom: 10px; right: 10px; width: ${width}px; height: ${height}px; border: 1px solid #ddd; background: white; border-radius: 4px; overflow: hidden; z-index: 5; cursor: pointer;`;
	return () => {
		const nodes = instance.nodes();
		if (nodes.length === 0) return /* @__PURE__ */ jsx("div", {
			class: "pyreon-flow-minimap",
			style: containerStyle
		});
		let minX = Number.POSITIVE_INFINITY;
		let minY = Number.POSITIVE_INFINITY;
		let maxX = Number.NEGATIVE_INFINITY;
		let maxY = Number.NEGATIVE_INFINITY;
		for (const node of nodes) {
			const w = node.width ?? 150;
			const h = node.height ?? 40;
			minX = Math.min(minX, node.position.x);
			minY = Math.min(minY, node.position.y);
			maxX = Math.max(maxX, node.position.x + w);
			maxY = Math.max(maxY, node.position.y + h);
		}
		const padding = 40;
		const graphW = maxX - minX + padding * 2;
		const graphH = maxY - minY + padding * 2;
		const scale = Math.min(width / graphW, height / graphH);
		const vp = instance.viewport();
		const cs = instance.containerSize();
		const vpLeft = (-vp.x / vp.zoom - minX + padding) * scale;
		const vpTop = (-vp.y / vp.zoom - minY + padding) * scale;
		const vpWidth = cs.width / vp.zoom * scale;
		const vpHeight = cs.height / vp.zoom * scale;
		const handleClick = (e) => {
			const rect = e.currentTarget.getBoundingClientRect();
			const clickX = e.clientX - rect.left;
			const clickY = e.clientY - rect.top;
			const flowX = clickX / scale + minX - padding;
			const flowY = clickY / scale + minY - padding;
			instance.viewport.set({
				...vp,
				x: -(flowX * vp.zoom) + cs.width / 2,
				y: -(flowY * vp.zoom) + cs.height / 2
			});
		};
		return /* @__PURE__ */ jsx("div", {
			class: "pyreon-flow-minimap",
			style: containerStyle,
			onClick: handleClick,
			children: /* @__PURE__ */ jsxs("svg", {
				role: "img",
				"aria-label": "minimap",
				width: String(width),
				height: String(height),
				children: [
					/* @__PURE__ */ jsx("rect", {
						width: String(width),
						height: String(height),
						fill: maskColor
					}),
					nodes.map((node) => {
						const w = (node.width ?? 150) * scale;
						const h = (node.height ?? 40) * scale;
						const x = (node.position.x - minX + padding) * scale;
						const y = (node.position.y - minY + padding) * scale;
						const color = typeof nodeColor === "function" ? nodeColor(node) : nodeColor;
						return /* @__PURE__ */ jsx("rect", {
							x: String(x),
							y: String(y),
							width: String(w),
							height: String(h),
							fill: color,
							rx: "2"
						}, node.id);
					}),
					/* @__PURE__ */ jsx("rect", {
						x: String(Math.max(0, vpLeft)),
						y: String(Math.max(0, vpTop)),
						width: String(Math.min(vpWidth, width)),
						height: String(Math.min(vpHeight, height)),
						fill: "none",
						stroke: "#3b82f6",
						"stroke-width": "1.5",
						rx: "2"
					})
				]
			})
		});
	};
}

//#endregion
//#region src/components/node-resizer.tsx
const directionCursors = {
	nw: "nw-resize",
	ne: "ne-resize",
	sw: "sw-resize",
	se: "se-resize",
	n: "n-resize",
	s: "s-resize",
	e: "e-resize",
	w: "w-resize"
};
const directionPositions = {
	nw: "top: -4px; left: -4px;",
	ne: "top: -4px; right: -4px;",
	sw: "bottom: -4px; left: -4px;",
	se: "bottom: -4px; right: -4px;",
	n: "top: -4px; left: 50%; transform: translateX(-50%);",
	s: "bottom: -4px; left: 50%; transform: translateX(-50%);",
	e: "right: -4px; top: 50%; transform: translateY(-50%);",
	w: "left: -4px; top: 50%; transform: translateY(-50%);"
};
/**
* Node resize handles. Place inside a custom node component
* to allow users to resize the node by dragging corners or edges.
*
* Uses pointer capture for clean event handling — no document listener leaks.
*
* @example
* ```tsx
* function ResizableNode({ id, data, selected }: NodeComponentProps) {
*   return (
*     <div style="min-width: 100px; min-height: 50px; position: relative;">
*       {data.label}
*       <NodeResizer nodeId={id} instance={flow} />
*     </div>
*   )
* }
* ```
*/
function NodeResizer(props) {
	const minWidth = props.minWidth ?? 50;
	const minHeight = props.minHeight ?? 30;
	const handleSize = props.handleSize ?? 8;
	const directions = props.showEdgeHandles ?? false ? [
		"nw",
		"ne",
		"sw",
		"se",
		"n",
		"s",
		"e",
		"w"
	] : [
		"nw",
		"ne",
		"sw",
		"se"
	];
	const createHandler = (dir) => {
		let startX = 0;
		let startY = 0;
		let startWidth = 0;
		let startHeight = 0;
		let startNodeX = 0;
		let startNodeY = 0;
		let zoomAtStart = 1;
		const onPointerDown = (e) => {
			e.stopPropagation();
			e.preventDefault();
			const node = props.instance.getNode(props.nodeId);
			if (!node) return;
			startX = e.clientX;
			startY = e.clientY;
			startWidth = node.width ?? 150;
			startHeight = node.height ?? 40;
			startNodeX = node.position.x;
			startNodeY = node.position.y;
			zoomAtStart = props.instance.viewport.peek().zoom;
			e.currentTarget.setPointerCapture(e.pointerId);
		};
		const onPointerMove = (e) => {
			if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;
			const dx = (e.clientX - startX) / zoomAtStart;
			const dy = (e.clientY - startY) / zoomAtStart;
			let newW = startWidth;
			let newH = startHeight;
			let newX = startNodeX;
			let newY = startNodeY;
			if (dir === "e" || dir === "se" || dir === "ne") newW = Math.max(minWidth, startWidth + dx);
			if (dir === "w" || dir === "sw" || dir === "nw") {
				newW = Math.max(minWidth, startWidth - dx);
				newX = startNodeX + startWidth - newW;
			}
			if (dir === "s" || dir === "se" || dir === "sw") newH = Math.max(minHeight, startHeight + dy);
			if (dir === "n" || dir === "ne" || dir === "nw") {
				newH = Math.max(minHeight, startHeight - dy);
				newY = startNodeY + startHeight - newH;
			}
			props.instance.updateNode(props.nodeId, {
				width: newW,
				height: newH,
				position: {
					x: newX,
					y: newY
				}
			});
		};
		const onPointerUp = (e) => {
			const el = e.currentTarget;
			if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
		};
		return {
			onPointerDown,
			onPointerMove,
			onPointerUp
		};
	};
	const size = `${handleSize}px`;
	const baseStyle = `position: absolute; width: ${size}; height: ${size}; background: white; border: 1.5px solid #3b82f6; border-radius: 2px; z-index: 2;`;
	return /* @__PURE__ */ jsx(Fragment, { children: directions.map((dir) => {
		const handler = createHandler(dir);
		return /* @__PURE__ */ jsx("div", {
			class: `pyreon-flow-resizer pyreon-flow-resizer-${dir}`,
			style: `${baseStyle} ${directionPositions[dir]} cursor: ${directionCursors[dir]};`,
			onPointerDown: handler.onPointerDown,
			onPointerMove: handler.onPointerMove,
			onPointerUp: handler.onPointerUp
		}, dir);
	}) });
}

//#endregion
//#region src/components/node-toolbar.tsx
const positionStyles$1 = {
	top: "bottom: 100%; left: 50%; transform: translateX(-50%);",
	bottom: "top: 100%; left: 50%; transform: translateX(-50%);",
	left: "right: 100%; top: 50%; transform: translateY(-50%);",
	right: "left: 100%; top: 50%; transform: translateY(-50%);"
};
/**
* Floating toolbar that appears near a node, typically when selected.
* Place inside a custom node component.
*
* @example
* ```tsx
* function EditableNode({ id, data, selected }: NodeComponentProps) {
*   return (
*     <div class="node">
*       {data.label}
*       <NodeToolbar selected={selected}>
*         <button onClick={() => duplicate(id)}>Duplicate</button>
*         <button onClick={() => remove(id)}>Delete</button>
*       </NodeToolbar>
*     </div>
*   )
* }
* ```
*/
function NodeToolbar(props) {
	const position = props.position ?? "top";
	const offset = props.offset ?? 8;
	const showOnSelect = props.showOnSelect ?? true;
	const selected = props.selected ?? false;
	if (showOnSelect && !selected) return null;
	const baseStyle = `position: absolute; ${positionStyles$1[position] ?? positionStyles$1.top} ${position === "top" ? `margin-bottom: ${offset}px;` : position === "bottom" ? `margin-top: ${offset}px;` : position === "left" ? `margin-right: ${offset}px;` : `margin-left: ${offset}px;`} z-index: 10; display: flex; gap: 4px; background: white; border: 1px solid #ddd; border-radius: 6px; padding: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); ${props.style ?? ""}`;
	return /* @__PURE__ */ jsx("div", {
		class: `pyreon-flow-node-toolbar ${props.class ?? ""}`,
		style: baseStyle,
		children: props.children
	});
}

//#endregion
//#region src/components/panel.tsx
const positionStyles = {
	"top-left": "top: 10px; left: 10px;",
	"top-right": "top: 10px; right: 10px;",
	"bottom-left": "bottom: 10px; left: 10px;",
	"bottom-right": "bottom: 10px; right: 10px;"
};
/**
* Positioned overlay panel for custom content inside the flow canvas.
*
* @example
* ```tsx
* <Flow instance={flow}>
*   <Panel position="top-right">
*     <SearchBar />
*   </Panel>
* </Flow>
* ```
*/
function Panel(props) {
	const { position = "top-left", style = "", children } = props;
	const baseStyle = `position: absolute; ${positionStyles[position] ?? positionStyles["top-left"]} z-index: 5; ${style}`;
	return /* @__PURE__ */ jsx("div", {
		class: `pyreon-flow-panel ${props.class ?? ""}`,
		style: baseStyle,
		children
	});
}

//#endregion
//#region src/layout.ts
const ELK_ALGORITHMS = {
	layered: "org.eclipse.elk.layered",
	force: "org.eclipse.elk.force",
	stress: "org.eclipse.elk.stress",
	tree: "org.eclipse.elk.mrtree",
	radial: "org.eclipse.elk.radial",
	box: "org.eclipse.elk.box",
	rectpacking: "org.eclipse.elk.rectpacking"
};
const ELK_DIRECTIONS = {
	UP: "UP",
	DOWN: "DOWN",
	LEFT: "LEFT",
	RIGHT: "RIGHT"
};
const DIRECTION_ALGORITHMS = new Set(["layered", "tree"]);
const LAYER_SPACING_ALGORITHMS = new Set(["layered"]);
const EDGE_ROUTING_ALGORITHMS = new Set(["layered"]);
function warnIgnoredOptions(algorithm, options) {
	if (!import.meta.env?.DEV) return;
	if (options.direction !== void 0 && !DIRECTION_ALGORITHMS.has(algorithm)) console.warn(`[Pyreon] flow.layout: \`direction\` is silently ignored by the \`${algorithm}\` algorithm. It applies to \`layered\` and \`tree\` only — switch the algorithm or remove the option to silence this warning.`);
	if (options.layerSpacing !== void 0 && !LAYER_SPACING_ALGORITHMS.has(algorithm)) console.warn(`[Pyreon] flow.layout: \`layerSpacing\` is silently ignored by the \`${algorithm}\` algorithm. It applies to \`layered\` only — use \`nodeSpacing\` for general spacing or switch to \`layered\`.`);
	if (options.edgeRouting !== void 0 && !EDGE_ROUTING_ALGORITHMS.has(algorithm)) console.warn(`[Pyreon] flow.layout: \`edgeRouting\` is silently ignored by the \`${algorithm}\` algorithm. It applies to \`layered\` only — switch to \`layered\` or remove the option to silence this warning.`);
}
let elkInstance = null;
let elkPromise = null;
async function getELK() {
	if (elkInstance) return elkInstance;
	if (elkPromise) return elkPromise;
	elkPromise = import("./elk.bundled-5yXqqSm1.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)).then((mod) => {
		elkInstance = new (mod.default || mod)();
		return elkInstance;
	});
	return elkPromise;
}
function toElkGraph(nodes, edges, algorithm, options) {
	const layoutOptions = { "elk.algorithm": ELK_ALGORITHMS[algorithm] ?? ELK_ALGORITHMS.layered };
	if (options.direction) layoutOptions["elk.direction"] = ELK_DIRECTIONS[options.direction] ?? "DOWN";
	if (options.nodeSpacing !== void 0) layoutOptions["elk.spacing.nodeNode"] = String(options.nodeSpacing);
	if (options.layerSpacing !== void 0) layoutOptions["elk.layered.spacing.nodeNodeBetweenLayers"] = String(options.layerSpacing);
	if (options.edgeRouting) layoutOptions["elk.edgeRouting"] = {
		orthogonal: "ORTHOGONAL",
		splines: "SPLINES",
		polyline: "POLYLINE"
	}[options.edgeRouting] ?? "ORTHOGONAL";
	return {
		id: "root",
		layoutOptions,
		children: nodes.map((node) => ({
			id: node.id,
			width: node.width ?? 150,
			height: node.height ?? 40
		})),
		edges: edges.map((edge, i) => ({
			id: edge.id ?? `e-${i}`,
			sources: [edge.source],
			targets: [edge.target]
		}))
	};
}
/**
* Compute a layout for the given nodes and edges using elkjs.
* Returns an array of { id, position } for each node.
*
* elkjs is lazy-loaded — zero bundle cost until this function is called.
*
* **Algorithm-specific options**: not every option in `LayoutOptions`
* applies to every algorithm. `direction` and `layerSpacing` are
* namespaced under ELK's layered/tree pipelines and are silently
* ignored by `force`, `stress`, `radial`, `box`, and `rectpacking`.
* See the JSDoc on each `LayoutOptions` field for the exact
* applicability rules.
*
* @example
* ```ts
* const positions = await computeLayout(nodes, edges, 'layered', {
*   direction: 'RIGHT',
*   nodeSpacing: 50,
*   layerSpacing: 100,
* })
* // positions: [{ id: '1', position: { x: 0, y: 0 } }, ...]
* ```
*/
async function computeLayout(nodes, edges, algorithm = "layered", options = {}) {
	warnIgnoredOptions(algorithm, options);
	const elk = await getELK();
	const graph = toElkGraph(nodes, edges, algorithm, options);
	return ((await elk.layout(graph)).children ?? []).map((child) => ({
		id: child.id,
		position: {
			x: child.x ?? 0,
			y: child.y ?? 0
		}
	}));
}

//#endregion
//#region src/flow.ts
/**
* Generate a unique edge id from source/target.
*/
function edgeId(edge) {
	if (edge.id) return edge.id;
	const sh = edge.sourceHandle ? `-${edge.sourceHandle}` : "";
	const th = edge.targetHandle ? `-${edge.targetHandle}` : "";
	return `e-${edge.source}${sh}-${edge.target}${th}`;
}
/**
* Create a reactive flow instance — the core state manager for flow diagrams.
*
* All state is signal-based. Nodes, edges, viewport, and selection are
* reactive and update the UI automatically when modified.
*
* @param config - Initial configuration with nodes, edges, and options
* @returns A FlowInstance with signals and methods for managing the diagram
*
* @example
* ```tsx
* const flow = createFlow({
*   nodes: [
*     { id: '1', position: { x: 0, y: 0 }, data: { label: 'Start' } },
*     { id: '2', position: { x: 200, y: 100 }, data: { label: 'End' } },
*   ],
*   edges: [{ source: '1', target: '2' }],
* })
*
* flow.nodes()          // reactive node list
* flow.viewport()       // { x: 0, y: 0, zoom: 1 }
* flow.addNode({ id: '3', position: { x: 400, y: 0 }, data: { label: 'New' } })
* flow.layout('layered', { direction: 'RIGHT' })
* ```
*/
function createFlow(config = {}) {
	const { nodes: initialNodes = [], edges: initialEdges = [], defaultEdgeType = "bezier", minZoom = .1, maxZoom = 4, snapToGrid = false, snapGrid = 15, connectionRules } = config;
	const edgesWithIds = initialEdges.map((e) => ({
		...e,
		id: edgeId(e),
		type: e.type ?? defaultEdgeType
	}));
	const nodes = signal([...initialNodes]);
	const edges = signal(edgesWithIds);
	const viewport = signal({
		x: 0,
		y: 0,
		zoom: 1
	});
	const containerSize = signal({
		width: 800,
		height: 600
	});
	const selectedNodeIds = signal(/* @__PURE__ */ new Set());
	const selectedEdgeIds = signal(/* @__PURE__ */ new Set());
	const zoom = computed(() => viewport().zoom);
	const selectedNodes = computed(() => [...selectedNodeIds()]);
	const selectedEdges = computed(() => [...selectedEdgeIds()]);
	const connectListeners = /* @__PURE__ */ new Set();
	const nodesChangeListeners = /* @__PURE__ */ new Set();
	const nodeClickListeners = /* @__PURE__ */ new Set();
	const edgeClickListeners = /* @__PURE__ */ new Set();
	const nodeDragStartListeners = /* @__PURE__ */ new Set();
	const nodeDragEndListeners = /* @__PURE__ */ new Set();
	const nodeDoubleClickListeners = /* @__PURE__ */ new Set();
	function emitNodeChanges(changes) {
		for (const cb of nodesChangeListeners) cb(changes);
	}
	function getNode(id) {
		return nodes.peek().find((n) => n.id === id);
	}
	function addNode(node) {
		nodes.update((nds) => [...nds, node]);
	}
	function removeNode(id) {
		batch(() => {
			nodes.update((nds) => nds.filter((n) => n.id !== id));
			edges.update((eds) => eds.filter((e) => e.source !== id && e.target !== id));
			selectedNodeIds.update((set) => {
				const next = new Set(set);
				next.delete(id);
				return next;
			});
		});
		emitNodeChanges([{
			type: "remove",
			id
		}]);
	}
	function updateNode(id, update) {
		nodes.update((nds) => nds.map((n) => n.id === id ? {
			...n,
			...update
		} : n));
	}
	function updateNodePosition(id, position) {
		let pos = snapToGrid ? {
			x: Math.round(position.x / snapGrid) * snapGrid,
			y: Math.round(position.y / snapGrid) * snapGrid
		} : position;
		const node = getNode(id);
		pos = clampToExtent(pos, node?.width, node?.height);
		nodes.update((nds) => nds.map((n) => n.id === id ? {
			...n,
			position: pos
		} : n));
		emitNodeChanges([{
			type: "position",
			id,
			position: pos
		}]);
	}
	function getEdge(id) {
		return edges.peek().find((e) => e.id === id);
	}
	function addEdge(edge) {
		const newEdge = {
			...edge,
			id: edgeId(edge),
			type: edge.type ?? defaultEdgeType
		};
		if (edges.peek().some((e) => e.id === newEdge.id)) return;
		edges.update((eds) => [...eds, newEdge]);
		const connection = {
			source: edge.source,
			target: edge.target,
			...edge.sourceHandle != null ? { sourceHandle: edge.sourceHandle } : {},
			...edge.targetHandle != null ? { targetHandle: edge.targetHandle } : {}
		};
		for (const cb of connectListeners) cb(connection);
	}
	function removeEdge(id) {
		edges.update((eds) => eds.filter((e) => e.id !== id));
		selectedEdgeIds.update((set) => {
			const next = new Set(set);
			next.delete(id);
			return next;
		});
	}
	function isValidConnection(connection) {
		if (!connectionRules) return true;
		const sourceNode = getNode(connection.source);
		if (!sourceNode) return false;
		const rule = connectionRules[sourceNode.type ?? "default"];
		if (!rule) return true;
		const targetNode = getNode(connection.target);
		if (!targetNode) return false;
		const targetType = targetNode.type ?? "default";
		return rule.outputs.includes(targetType);
	}
	function selectNode(id, additive = false) {
		selectedNodeIds.update((set) => {
			const next = additive ? new Set(set) : /* @__PURE__ */ new Set();
			next.add(id);
			return next;
		});
		if (!additive) selectedEdgeIds.set(/* @__PURE__ */ new Set());
	}
	function deselectNode(id) {
		selectedNodeIds.update((set) => {
			const next = new Set(set);
			next.delete(id);
			return next;
		});
	}
	function selectEdge(id, additive = false) {
		selectedEdgeIds.update((set) => {
			const next = additive ? new Set(set) : /* @__PURE__ */ new Set();
			next.add(id);
			return next;
		});
		if (!additive) selectedNodeIds.set(/* @__PURE__ */ new Set());
	}
	function clearSelection() {
		batch(() => {
			selectedNodeIds.set(/* @__PURE__ */ new Set());
			selectedEdgeIds.set(/* @__PURE__ */ new Set());
		});
	}
	function selectAll() {
		selectedNodeIds.set(new Set(nodes.peek().map((n) => n.id)));
	}
	function deleteSelected() {
		batch(() => {
			const nodeIdsToRemove = selectedNodeIds.peek();
			const edgeIdsToRemove = selectedEdgeIds.peek();
			if (nodeIdsToRemove.size > 0) {
				nodes.update((nds) => nds.filter((n) => !nodeIdsToRemove.has(n.id)));
				edges.update((eds) => eds.filter((e) => !nodeIdsToRemove.has(e.source) && !nodeIdsToRemove.has(e.target) && !edgeIdsToRemove.has(e.id)));
			} else if (edgeIdsToRemove.size > 0) edges.update((eds) => eds.filter((e) => !edgeIdsToRemove.has(e.id)));
			selectedNodeIds.set(/* @__PURE__ */ new Set());
			selectedEdgeIds.set(/* @__PURE__ */ new Set());
		});
	}
	function fitView(nodeIds, padding = config.fitViewPadding ?? .1) {
		const targetNodes = nodeIds ? nodes.peek().filter((n) => nodeIds.includes(n.id)) : nodes.peek();
		if (targetNodes.length === 0) return;
		let minX = Number.POSITIVE_INFINITY;
		let minY = Number.POSITIVE_INFINITY;
		let maxX = Number.NEGATIVE_INFINITY;
		let maxY = Number.NEGATIVE_INFINITY;
		for (const node of targetNodes) {
			const w = node.width ?? 150;
			const h = node.height ?? 40;
			minX = Math.min(minX, node.position.x);
			minY = Math.min(minY, node.position.y);
			maxX = Math.max(maxX, node.position.x + w);
			maxY = Math.max(maxY, node.position.y + h);
		}
		const graphWidth = maxX - minX;
		const graphHeight = maxY - minY;
		const { width: containerWidth, height: containerHeight } = containerSize.peek();
		const zoomX = containerWidth / (graphWidth * (1 + padding * 2));
		const zoomY = containerHeight / (graphHeight * (1 + padding * 2));
		const newZoom = Math.min(Math.max(Math.min(zoomX, zoomY), minZoom), maxZoom);
		const centerX = (minX + maxX) / 2;
		const centerY = (minY + maxY) / 2;
		viewport.set({
			x: containerWidth / 2 - centerX * newZoom,
			y: containerHeight / 2 - centerY * newZoom,
			zoom: newZoom
		});
	}
	function zoomTo(z) {
		viewport.update((v) => ({
			...v,
			zoom: Math.min(Math.max(z, minZoom), maxZoom)
		}));
	}
	function zoomIn() {
		viewport.update((v) => ({
			...v,
			zoom: Math.min(v.zoom * 1.2, maxZoom)
		}));
	}
	function zoomOut() {
		viewport.update((v) => ({
			...v,
			zoom: Math.max(v.zoom / 1.2, minZoom)
		}));
	}
	function panTo(position) {
		viewport.update((v) => ({
			...v,
			x: -position.x * v.zoom,
			y: -position.y * v.zoom
		}));
	}
	function isNodeVisible(id) {
		const node = getNode(id);
		if (!node) return false;
		const v = viewport.peek();
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		const screenX = node.position.x * v.zoom + v.x;
		const screenY = node.position.y * v.zoom + v.y;
		const screenW = w * v.zoom;
		const screenH = h * v.zoom;
		const { width: cw, height: ch } = containerSize.peek();
		return screenX + screenW > 0 && screenX < cw && screenY + screenH > 0 && screenY < ch;
	}
	async function layout(algorithm = "layered", options = {}) {
		const currentNodes = nodes.peek();
		const positions = await computeLayout(currentNodes, edges.peek(), algorithm, options);
		const animate = options.animate !== false;
		const duration = options.animationDuration ?? 300;
		if (!animate) {
			batch(() => {
				nodes.update((nds) => nds.map((node) => {
					const pos = positions.find((p) => p.id === node.id);
					return pos ? {
						...node,
						position: pos.position
					} : node;
				}));
			});
			return;
		}
		const startPositions = new Map(currentNodes.map((n) => [n.id, { ...n.position }]));
		const targetPositions = new Map(positions.map((p) => [p.id, p.position]));
		const startTime = performance.now();
		const animateFrame = () => {
			const elapsed = performance.now() - startTime;
			const t = Math.min(elapsed / duration, 1);
			const eased = 1 - (1 - t) ** 3;
			batch(() => {
				nodes.update((nds) => nds.map((node) => {
					const start = startPositions.get(node.id);
					const end = targetPositions.get(node.id);
					if (!start || !end) return node;
					return {
						...node,
						position: {
							x: start.x + (end.x - start.x) * eased,
							y: start.y + (end.y - start.y) * eased
						}
					};
				}));
			});
			if (t < 1) requestAnimationFrame(animateFrame);
		};
		requestAnimationFrame(animateFrame);
	}
	function batchOp(fn) {
		batch(fn);
	}
	function getConnectedEdges(nodeId) {
		return edges.peek().filter((e) => e.source === nodeId || e.target === nodeId);
	}
	function getIncomers(nodeId) {
		const incomingEdges = edges.peek().filter((e) => e.target === nodeId);
		const sourceIds = new Set(incomingEdges.map((e) => e.source));
		return nodes.peek().filter((n) => sourceIds.has(n.id));
	}
	function getOutgoers(nodeId) {
		const outgoingEdges = edges.peek().filter((e) => e.source === nodeId);
		const targetIds = new Set(outgoingEdges.map((e) => e.target));
		return nodes.peek().filter((n) => targetIds.has(n.id));
	}
	function onConnect(callback) {
		connectListeners.add(callback);
		return () => connectListeners.delete(callback);
	}
	function onNodesChange(callback) {
		nodesChangeListeners.add(callback);
		return () => nodesChangeListeners.delete(callback);
	}
	function onNodeClick(callback) {
		nodeClickListeners.add(callback);
		return () => nodeClickListeners.delete(callback);
	}
	function onEdgeClick(callback) {
		edgeClickListeners.add(callback);
		return () => edgeClickListeners.delete(callback);
	}
	function onNodeDragStart(callback) {
		nodeDragStartListeners.add(callback);
		return () => nodeDragStartListeners.delete(callback);
	}
	function onNodeDragEnd(callback) {
		nodeDragEndListeners.add(callback);
		return () => nodeDragEndListeners.delete(callback);
	}
	function onNodeDoubleClick(callback) {
		nodeDoubleClickListeners.add(callback);
		return () => nodeDoubleClickListeners.delete(callback);
	}
	let clipboard = null;
	function copySelected() {
		const selectedNodeSet = selectedNodeIds.peek();
		if (selectedNodeSet.size === 0) return;
		const copiedNodes = nodes.peek().filter((n) => selectedNodeSet.has(n.id));
		const nodeIdSet = new Set(copiedNodes.map((n) => n.id));
		clipboard = {
			nodes: copiedNodes,
			edges: edges.peek().filter((e) => nodeIdSet.has(e.source) && nodeIdSet.has(e.target))
		};
	}
	function paste(offset = {
		x: 50,
		y: 50
	}) {
		if (!clipboard) return;
		const idMap = /* @__PURE__ */ new Map();
		const newNodes = [];
		for (const node of clipboard.nodes) {
			const newId = `${node.id}-copy-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
			idMap.set(node.id, newId);
			newNodes.push({
				...node,
				id: newId,
				position: {
					x: node.position.x + offset.x,
					y: node.position.y + offset.y
				}
			});
		}
		const newEdges = clipboard.edges.map((e) => {
			const { id: _id, ...rest } = e;
			return {
				...rest,
				source: idMap.get(e.source) ?? e.source,
				target: idMap.get(e.target) ?? e.target
			};
		});
		batch(() => {
			for (const node of newNodes) addNode(node);
			for (const edge of newEdges) addEdge(edge);
			selectedNodeIds.set(new Set(newNodes.map((n) => n.id)));
			selectedEdgeIds.set(/* @__PURE__ */ new Set());
		});
	}
	const undoStack = [];
	const redoStack = [];
	const maxHistory = 50;
	function pushHistory() {
		undoStack.push({
			nodes: structuredClone(nodes.peek()),
			edges: structuredClone(edges.peek())
		});
		if (undoStack.length > maxHistory) undoStack.shift();
		redoStack.length = 0;
	}
	function undo() {
		const prev = undoStack.pop();
		if (!prev) return;
		redoStack.push({
			nodes: structuredClone(nodes.peek()),
			edges: structuredClone(edges.peek())
		});
		batch(() => {
			nodes.set(prev.nodes);
			edges.set(prev.edges);
			clearSelection();
		});
	}
	function redo() {
		const next = redoStack.pop();
		if (!next) return;
		undoStack.push({
			nodes: structuredClone(nodes.peek()),
			edges: structuredClone(edges.peek())
		});
		batch(() => {
			nodes.set(next.nodes);
			edges.set(next.edges);
			clearSelection();
		});
	}
	function moveSelectedNodes(dx, dy) {
		const selected = selectedNodeIds.peek();
		if (selected.size === 0) return;
		nodes.update((nds) => nds.map((n) => {
			if (!selected.has(n.id)) return n;
			return {
				...n,
				position: {
					x: n.position.x + dx,
					y: n.position.y + dy
				}
			};
		}));
	}
	function getSnapLines(dragNodeId, position, threshold = 5) {
		const dragNode = getNode(dragNodeId);
		if (!dragNode) return {
			x: null,
			y: null,
			snappedPosition: position
		};
		const w = dragNode.width ?? 150;
		const h = dragNode.height ?? 40;
		const dragCenterX = position.x + w / 2;
		const dragCenterY = position.y + h / 2;
		let snapX = null;
		let snapY = null;
		let snappedX = position.x;
		let snappedY = position.y;
		for (const node of nodes.peek()) {
			if (node.id === dragNodeId) continue;
			const nw = node.width ?? 150;
			const nh = node.height ?? 40;
			const nodeCenterX = node.position.x + nw / 2;
			const nodeCenterY = node.position.y + nh / 2;
			if (Math.abs(dragCenterX - nodeCenterX) < threshold) {
				snapX = nodeCenterX;
				snappedX = nodeCenterX - w / 2;
			}
			if (Math.abs(position.x - node.position.x) < threshold) {
				snapX = node.position.x;
				snappedX = node.position.x;
			}
			if (Math.abs(position.x + w - (node.position.x + nw)) < threshold) {
				snapX = node.position.x + nw;
				snappedX = node.position.x + nw - w;
			}
			if (Math.abs(dragCenterY - nodeCenterY) < threshold) {
				snapY = nodeCenterY;
				snappedY = nodeCenterY - h / 2;
			}
			if (Math.abs(position.y - node.position.y) < threshold) {
				snapY = node.position.y;
				snappedY = node.position.y;
			}
			if (Math.abs(position.y + h - (node.position.y + nh)) < threshold) {
				snapY = node.position.y + nh;
				snappedY = node.position.y + nh - h;
			}
		}
		return {
			x: snapX,
			y: snapY,
			snappedPosition: {
				x: snappedX,
				y: snappedY
			}
		};
	}
	function getChildNodes(parentId) {
		return nodes.peek().filter((n) => n.parentId === parentId);
	}
	function getAbsolutePosition(nodeId) {
		const node = getNode(nodeId);
		if (!node) return {
			x: 0,
			y: 0
		};
		if (node.parentId) {
			const parentPos = getAbsolutePosition(node.parentId);
			return {
				x: parentPos.x + node.position.x,
				y: parentPos.y + node.position.y
			};
		}
		return node.position;
	}
	function reconnectEdge(targetEdgeId, newConnection) {
		edges.update((eds) => eds.map((e) => {
			if (e.id !== targetEdgeId) return e;
			const updated = {
				...e,
				source: newConnection.source ?? e.source,
				target: newConnection.target ?? e.target
			};
			const sh = newConnection.sourceHandle ?? e.sourceHandle;
			const th = newConnection.targetHandle ?? e.targetHandle;
			if (sh != null) updated.sourceHandle = sh;
			if (th != null) updated.targetHandle = th;
			return updated;
		}));
	}
	function addEdgeWaypoint(edgeIdentifier, point, index) {
		edges.update((eds) => eds.map((e) => {
			if (e.id !== edgeIdentifier) return e;
			const waypoints = [...e.waypoints ?? []];
			if (index !== void 0) waypoints.splice(index, 0, point);
			else waypoints.push(point);
			return {
				...e,
				waypoints
			};
		}));
	}
	function removeEdgeWaypoint(edgeIdentifier, index) {
		edges.update((eds) => eds.map((e) => {
			if (e.id !== edgeIdentifier) return e;
			const waypoints = [...e.waypoints ?? []];
			waypoints.splice(index, 1);
			const { waypoints: _wp, ...rest } = e;
			return waypoints.length > 0 ? {
				...rest,
				waypoints
			} : rest;
		}));
	}
	function updateEdgeWaypoint(edgeIdentifier, index, point) {
		edges.update((eds) => eds.map((e) => {
			if (e.id !== edgeIdentifier) return e;
			const waypoints = [...e.waypoints ?? []];
			if (index >= 0 && index < waypoints.length) waypoints[index] = point;
			return {
				...e,
				waypoints
			};
		}));
	}
	function getProximityConnection(nodeId, threshold = 50) {
		const node = getNode(nodeId);
		if (!node) return null;
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		const centerX = node.position.x + w / 2;
		const centerY = node.position.y + h / 2;
		let closest = null;
		for (const other of nodes.peek()) {
			if (other.id === nodeId) continue;
			if (edges.peek().some((e) => e.source === nodeId && e.target === other.id || e.source === other.id && e.target === nodeId)) continue;
			const ow = other.width ?? 150;
			const oh = other.height ?? 40;
			const ocx = other.position.x + ow / 2;
			const ocy = other.position.y + oh / 2;
			const dist = Math.hypot(centerX - ocx, centerY - ocy);
			if (dist < threshold && (!closest || dist < closest.dist)) closest = {
				nodeId: other.id,
				dist
			};
		}
		if (!closest) return null;
		const connection = {
			source: nodeId,
			target: closest.nodeId
		};
		return isValidConnection(connection) ? connection : null;
	}
	function getOverlappingNodes(nodeId) {
		const node = getNode(nodeId);
		if (!node) return [];
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		const ax1 = node.position.x;
		const ay1 = node.position.y;
		const ax2 = ax1 + w;
		const ay2 = ay1 + h;
		return nodes.peek().filter((other) => {
			if (other.id === nodeId) return false;
			const ow = other.width ?? 150;
			const oh = other.height ?? 40;
			const bx1 = other.position.x;
			const by1 = other.position.y;
			const bx2 = bx1 + ow;
			const by2 = by1 + oh;
			return ax1 < bx2 && ax2 > bx1 && ay1 < by2 && ay2 > by1;
		});
	}
	function resolveCollisions(nodeId, spacing = 10) {
		const overlapping = getOverlappingNodes(nodeId);
		if (overlapping.length === 0) return;
		const node = getNode(nodeId);
		if (!node) return;
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		for (const other of overlapping) {
			const ow = other.width ?? 150;
			const oh = other.height ?? 40;
			const overlapX = Math.min(node.position.x + w - other.position.x, other.position.x + ow - node.position.x);
			const overlapY = Math.min(node.position.y + h - other.position.y, other.position.y + oh - node.position.y);
			if (overlapX < overlapY) {
				const dx = node.position.x < other.position.x ? -(overlapX + spacing) / 2 : (overlapX + spacing) / 2;
				updateNodePosition(other.id, {
					x: other.position.x - dx,
					y: other.position.y
				});
			} else {
				const dy = node.position.y < other.position.y ? -(overlapY + spacing) / 2 : (overlapY + spacing) / 2;
				updateNodePosition(other.id, {
					x: other.position.x,
					y: other.position.y - dy
				});
			}
		}
	}
	function setNodeExtent(extent) {
		nodeExtent = extent;
	}
	let nodeExtent = config.nodeExtent ?? null;
	function clampToExtent(position, nodeWidth = 150, nodeHeight = 40) {
		if (!nodeExtent) return position;
		return {
			x: Math.min(Math.max(position.x, nodeExtent[0][0]), nodeExtent[1][0] - nodeWidth),
			y: Math.min(Math.max(position.y, nodeExtent[0][1]), nodeExtent[1][1] - nodeHeight)
		};
	}
	function findNodes(predicate) {
		return nodes.peek().filter(predicate);
	}
	function searchNodes(query) {
		const q = query.toLowerCase();
		return nodes.peek().filter((n) => {
			return (n.data?.label ?? n.id).toLowerCase().includes(q);
		});
	}
	function focusNode(nodeId, focusZoom) {
		const node = getNode(nodeId);
		if (!node) return;
		const w = node.width ?? 150;
		const h = node.height ?? 40;
		const centerX = node.position.x + w / 2;
		const centerY = node.position.y + h / 2;
		const z = focusZoom ?? viewport.peek().zoom;
		const { width: cw, height: ch } = containerSize.peek();
		animateViewport({
			x: -centerX * z + cw / 2,
			y: -centerY * z + ch / 2,
			zoom: z
		});
		selectNode(nodeId);
	}
	function toJSON() {
		return {
			nodes: structuredClone(nodes.peek()),
			edges: structuredClone(edges.peek()),
			viewport: { ...viewport.peek() }
		};
	}
	function fromJSON(data) {
		batch(() => {
			nodes.set(data.nodes);
			edges.set(data.edges.map((e) => ({
				...e,
				id: e.id ?? edgeId(e),
				type: e.type ?? defaultEdgeType
			})));
			if (data.viewport) viewport.set(data.viewport);
			clearSelection();
		});
	}
	function animateViewport(target, duration = 300) {
		const start = { ...viewport.peek() };
		const end = {
			x: target.x ?? start.x,
			y: target.y ?? start.y,
			zoom: target.zoom ?? start.zoom
		};
		const startTime = performance.now();
		const frame = () => {
			const elapsed = performance.now() - startTime;
			const t = Math.min(elapsed / duration, 1);
			const eased = 1 - (1 - t) ** 3;
			viewport.set({
				x: start.x + (end.x - start.x) * eased,
				y: start.y + (end.y - start.y) * eased,
				zoom: start.zoom + (end.zoom - start.zoom) * eased
			});
			if (t < 1) requestAnimationFrame(frame);
		};
		requestAnimationFrame(frame);
	}
	function dispose() {
		connectListeners.clear();
		nodesChangeListeners.clear();
		nodeClickListeners.clear();
		edgeClickListeners.clear();
		nodeDragStartListeners.clear();
		nodeDragEndListeners.clear();
		nodeDoubleClickListeners.clear();
	}
	if (config.fitView) fitView();
	return {
		nodes,
		edges,
		viewport,
		zoom,
		containerSize,
		selectedNodes,
		selectedEdges,
		getNode,
		addNode,
		removeNode,
		updateNode,
		updateNodePosition,
		getEdge,
		addEdge,
		removeEdge,
		isValidConnection,
		selectNode,
		deselectNode,
		selectEdge,
		clearSelection,
		selectAll,
		deleteSelected,
		fitView,
		zoomTo,
		zoomIn,
		zoomOut,
		panTo,
		isNodeVisible,
		layout,
		batch: batchOp,
		getConnectedEdges,
		getIncomers,
		getOutgoers,
		onConnect,
		onNodesChange,
		onNodeClick,
		onEdgeClick,
		onNodeDragStart,
		onNodeDragEnd,
		onNodeDoubleClick,
		_emit: {
			nodeDragStart: (node) => {
				for (const cb of nodeDragStartListeners) cb(node);
			},
			nodeDragEnd: (node) => {
				for (const cb of nodeDragEndListeners) cb(node);
			},
			nodeDoubleClick: (node) => {
				for (const cb of nodeDoubleClickListeners) cb(node);
			},
			nodeClick: (node) => {
				for (const cb of nodeClickListeners) cb(node);
			},
			edgeClick: (edge) => {
				for (const cb of edgeClickListeners) cb(edge);
			}
		},
		copySelected,
		paste,
		pushHistory,
		undo,
		redo,
		moveSelectedNodes,
		getSnapLines,
		getChildNodes,
		getAbsolutePosition,
		addEdgeWaypoint,
		removeEdgeWaypoint,
		updateEdgeWaypoint,
		reconnectEdge,
		getProximityConnection,
		getOverlappingNodes,
		resolveCollisions,
		setNodeExtent,
		clampToExtent,
		findNodes,
		searchNodes,
		focusNode,
		toJSON,
		fromJSON,
		animateViewport,
		config,
		dispose
	};
}

//#endregion
//#region src/use-flow.ts
/**
* Component-scoped wrapper around `createFlow` that auto-disposes the
* instance when the calling component unmounts. Use this inside a
* component body to avoid the manual `onUnmount(() => flow.dispose())`
* boilerplate.
*
* @example
* ```tsx
* const MyDiagram = () => {
*   const flow = useFlow<MyData>({
*     nodes: [{ id: '1', position: { x: 0, y: 0 }, data: { kind: 'start' } }],
*     edges: [],
*   })
*   return <Flow instance={flow}><Background /></Flow>
* }
* ```
*
* For flows owned outside the component tree (singleton, app store, etc.)
* keep using `createFlow` directly and dispose manually.
*/
function useFlow(config) {
	const instance = createFlow(config);
	onUnmount(() => {
		instance.dispose();
	});
	return instance;
}

//#endregion
//#region src/styles.ts
/**
* Default CSS styles for the flow diagram.
* Inject via `<style>` tag or import in your CSS.
*
* @example
* ```tsx
* import { flowStyles } from '@pyreon/flow'
*
* // Inject once at app root
* const style = document.createElement('style')
* style.textContent = flowStyles
* document.head.appendChild(style)
* ```
*/
const flowStyles = `
/* ── Animated edges ────────────────────────────────────────────────────────── */

.pyreon-flow-edge-animated {
  stroke-dasharray: 5;
  animation: pyreon-flow-edge-dash 0.5s linear infinite;
}

@keyframes pyreon-flow-edge-dash {
  to {
    stroke-dashoffset: -10;
  }
}

/* ── Node states ──────────────────────────────────────────────────────────── */

.pyreon-flow-node {
  transition: box-shadow 0.15s ease;
}

.pyreon-flow-node.dragging {
  opacity: 0.9;
  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
  cursor: grabbing;
}

.pyreon-flow-node.selected {
  filter: drop-shadow(0 0 0 2px rgba(59, 130, 246, 0.3));
}

/* ── Handles ──────────────────────────────────────────────────────────────── */

.pyreon-flow-handle {
  transition: transform 0.1s ease, background 0.1s ease;
}

.pyreon-flow-handle:hover {
  transform: scale(1.4);
  background: #3b82f6 !important;
}

.pyreon-flow-handle-target:hover {
  background: #22c55e !important;
  border-color: #22c55e !important;
}

/* ── Resizer ──────────────────────────────────────────────────────────────── */

.pyreon-flow-resizer {
  transition: background 0.1s ease, transform 0.1s ease;
}

.pyreon-flow-resizer:hover {
  background: #3b82f6 !important;
  transform: scale(1.2);
}

/* ── Selection box ────────────────────────────────────────────────────────── */

.pyreon-flow-selection-box {
  pointer-events: none;
  border-radius: 2px;
}

/* ── MiniMap ──────────────────────────────────────────────────────────────── */

.pyreon-flow-minimap {
  transition: opacity 0.2s ease;
}

.pyreon-flow-minimap:hover {
  opacity: 1 !important;
}

/* ── Node toolbar ─────────────────────────────────────────────────────────── */

.pyreon-flow-node-toolbar {
  animation: pyreon-flow-toolbar-enter 0.15s ease;
}

@keyframes pyreon-flow-toolbar-enter {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

/* ── Controls ─────────────────────────────────────────────────────────────── */

.pyreon-flow-controls button:hover {
  background: #f3f4f6 !important;
}

.pyreon-flow-controls button:active {
  background: #e5e7eb !important;
}
`;

//#endregion
export { Background, Controls, Flow, Handle, MiniMap, NodeResizer, NodeToolbar, Panel, Position, computeLayout, createFlow, flowStyles, getBezierPath, getEdgePath, getHandlePosition, getSmartHandlePositions, getSmoothStepPath, getStepPath, getStraightPath, getWaypointPath, useFlow };
//# sourceMappingURL=index.js.map