var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/core/types.ts var init_types = __esm({ "src/core/types.ts"() { "use strict"; } }); // src/core/graph-store.ts var graph_store_exports = {}; __export(graph_store_exports, { currentGraphIdAtom: () => currentGraphIdAtom, draggingNodeIdAtom: () => draggingNodeIdAtom, edgeCreationAtom: () => edgeCreationAtom, graphAtom: () => graphAtom, graphOptions: () => graphOptions, graphUpdateVersionAtom: () => graphUpdateVersionAtom, preDragNodeAttributesAtom: () => preDragNodeAttributesAtom }); import { atom } from "jotai"; import Graph from "graphology"; var graphOptions, currentGraphIdAtom, graphAtom, graphUpdateVersionAtom, edgeCreationAtom, draggingNodeIdAtom, preDragNodeAttributesAtom; var init_graph_store = __esm({ "src/core/graph-store.ts"() { "use strict"; graphOptions = { type: "directed", multi: true, allowSelfLoops: true }; currentGraphIdAtom = atom(null); graphAtom = atom(new Graph(graphOptions)); graphUpdateVersionAtom = atom(0); edgeCreationAtom = atom({ isCreating: false, sourceNodeId: null, sourceNodePosition: null, targetPosition: null, hoveredTargetNodeId: null, sourceHandle: null, targetHandle: null, sourcePort: null, targetPort: null, snappedTargetPosition: null }); draggingNodeIdAtom = atom(null); preDragNodeAttributesAtom = atom(null); } }); // src/utils/debug.ts import debugFactory from "debug"; function createDebug(module) { const base = debugFactory(`${NAMESPACE}:${module}`); const warn = debugFactory(`${NAMESPACE}:${module}:warn`); const error = debugFactory(`${NAMESPACE}:${module}:error`); warn.enabled = true; error.enabled = true; warn.log = console.warn.bind(console); error.log = console.error.bind(console); const debugFn = Object.assign(base, { warn, error }); return debugFn; } var NAMESPACE, debug; var init_debug = __esm({ "src/utils/debug.ts"() { "use strict"; NAMESPACE = "canvas"; debug = { graph: { node: createDebug("graph:node"), edge: createDebug("graph:edge"), sync: createDebug("graph:sync") }, ui: { selection: createDebug("ui:selection"), drag: createDebug("ui:drag"), resize: createDebug("ui:resize") }, sync: { status: createDebug("sync:status"), mutations: createDebug("sync:mutations"), queue: createDebug("sync:queue") }, viewport: createDebug("viewport") }; } }); // src/utils/mutation-queue.ts function getPendingState(nodeId) { let state = pendingNodeMutations.get(nodeId); if (!state) { state = { inFlight: false, queuedPosition: null, queuedUiProperties: null, graphId: null }; pendingNodeMutations.set(nodeId, state); } return state; } function clearPendingState(nodeId) { pendingNodeMutations.delete(nodeId); } function clearAllPendingMutations() { pendingNodeMutations.clear(); } function hasPendingMutations() { for (const state of pendingNodeMutations.values()) { if (state.inFlight || state.queuedPosition !== null || state.queuedUiProperties !== null) { return true; } } return false; } var pendingNodeMutations; var init_mutation_queue = __esm({ "src/utils/mutation-queue.ts"() { "use strict"; pendingNodeMutations = /* @__PURE__ */ new Map(); } }); // src/core/perf.ts import { atom as atom2 } from "jotai"; function setPerfEnabled(enabled) { _enabled = enabled; } function canvasMark(name) { if (!_enabled) return _noop; const markName = `canvas:${name}`; try { performance.mark(markName); } catch { return _noop; } return () => { try { performance.measure(`canvas:${name}`, markName); } catch { } }; } function _noop() { } function canvasWrap(name, fn) { const end = canvasMark(name); try { return fn(); } finally { end(); } } var perfEnabledAtom, _enabled; var init_perf = __esm({ "src/core/perf.ts"() { "use strict"; perfEnabledAtom = atom2(false); _enabled = false; if (typeof window !== "undefined") { window.__canvasPerf = setPerfEnabled; } } }); // src/core/graph-position.ts import { atom as atom3 } from "jotai"; import { atomFamily } from "jotai-family"; import Graph2 from "graphology"; function getPositionCache(graph) { let cache = _positionCacheByGraph.get(graph); if (!cache) { cache = /* @__PURE__ */ new Map(); _positionCacheByGraph.set(graph, cache); } return cache; } var debug2, _positionCacheByGraph, nodePositionUpdateCounterAtom, nodePositionAtomFamily, updateNodePositionAtom, cleanupNodePositionAtom, cleanupAllNodePositionsAtom, clearGraphOnSwitchAtom; var init_graph_position = __esm({ "src/core/graph-position.ts"() { "use strict"; init_graph_store(); init_debug(); init_mutation_queue(); init_perf(); debug2 = createDebug("graph:position"); _positionCacheByGraph = /* @__PURE__ */ new WeakMap(); nodePositionUpdateCounterAtom = atom3(0); nodePositionAtomFamily = atomFamily((nodeId) => atom3((get) => { get(nodePositionUpdateCounterAtom); const graph = get(graphAtom); if (!graph.hasNode(nodeId)) { return { x: 0, y: 0 }; } const x = graph.getNodeAttribute(nodeId, "x"); const y = graph.getNodeAttribute(nodeId, "y"); const cache = getPositionCache(graph); const prev = cache.get(nodeId); if (prev && prev.x === x && prev.y === y) { return prev; } const pos = { x, y }; cache.set(nodeId, pos); return pos; })); updateNodePositionAtom = atom3(null, (get, set, { nodeId, position }) => { const end = canvasMark("drag-frame"); const graph = get(graphAtom); if (graph.hasNode(nodeId)) { debug2("Updating node %s position to %o", nodeId, position); graph.setNodeAttribute(nodeId, "x", position.x); graph.setNodeAttribute(nodeId, "y", position.y); set(nodePositionUpdateCounterAtom, (c) => c + 1); } end(); }); cleanupNodePositionAtom = atom3(null, (get, _set, nodeId) => { nodePositionAtomFamily.remove(nodeId); const graph = get(graphAtom); getPositionCache(graph).delete(nodeId); debug2("Removed position atom for node: %s", nodeId); }); cleanupAllNodePositionsAtom = atom3(null, (get, _set) => { const graph = get(graphAtom); const nodeIds = graph.nodes(); nodeIds.forEach((nodeId) => { nodePositionAtomFamily.remove(nodeId); }); _positionCacheByGraph.delete(graph); debug2("Removed %d position atoms", nodeIds.length); }); clearGraphOnSwitchAtom = atom3(null, (get, set) => { debug2("Clearing graph for switch"); set(cleanupAllNodePositionsAtom); clearAllPendingMutations(); const emptyGraph = new Graph2(graphOptions); set(graphAtom, emptyGraph); set(graphUpdateVersionAtom, (v) => v + 1); }); } }); // src/core/selection-store.ts var selection_store_exports = {}; __export(selection_store_exports, { addNodesToSelectionAtom: () => addNodesToSelectionAtom, clearEdgeSelectionAtom: () => clearEdgeSelectionAtom, clearSelectionAtom: () => clearSelectionAtom, focusedNodeIdAtom: () => focusedNodeIdAtom, handleNodePointerDownSelectionAtom: () => handleNodePointerDownSelectionAtom, hasFocusedNodeAtom: () => hasFocusedNodeAtom, hasSelectionAtom: () => hasSelectionAtom, removeNodesFromSelectionAtom: () => removeNodesFromSelectionAtom, selectEdgeAtom: () => selectEdgeAtom, selectSingleNodeAtom: () => selectSingleNodeAtom, selectedEdgeIdAtom: () => selectedEdgeIdAtom, selectedNodeIdsAtom: () => selectedNodeIdsAtom, selectedNodesCountAtom: () => selectedNodesCountAtom, setFocusedNodeAtom: () => setFocusedNodeAtom, toggleNodeInSelectionAtom: () => toggleNodeInSelectionAtom }); import { atom as atom4 } from "jotai"; var debug3, selectedNodeIdsAtom, selectedEdgeIdAtom, handleNodePointerDownSelectionAtom, selectSingleNodeAtom, toggleNodeInSelectionAtom, clearSelectionAtom, addNodesToSelectionAtom, removeNodesFromSelectionAtom, selectEdgeAtom, clearEdgeSelectionAtom, focusedNodeIdAtom, setFocusedNodeAtom, hasFocusedNodeAtom, selectedNodesCountAtom, hasSelectionAtom; var init_selection_store = __esm({ "src/core/selection-store.ts"() { "use strict"; init_debug(); debug3 = createDebug("selection"); selectedNodeIdsAtom = atom4(/* @__PURE__ */ new Set()); selectedEdgeIdAtom = atom4(null); handleNodePointerDownSelectionAtom = atom4(null, (get, set, { nodeId, isShiftPressed }) => { const currentSelection = get(selectedNodeIdsAtom); debug3("handleNodePointerDownSelection: nodeId=%s, shift=%s, current=%o", nodeId, isShiftPressed, Array.from(currentSelection)); set(selectedEdgeIdAtom, null); if (isShiftPressed) { const newSelection = new Set(currentSelection); if (newSelection.has(nodeId)) { newSelection.delete(nodeId); } else { newSelection.add(nodeId); } debug3("Shift-click, setting selection to: %o", Array.from(newSelection)); set(selectedNodeIdsAtom, newSelection); } else { if (!currentSelection.has(nodeId)) { debug3("Node not in selection, selecting: %s", nodeId); set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId])); } else { debug3("Node already selected, preserving multi-select"); } } }); selectSingleNodeAtom = atom4(null, (get, set, nodeId) => { debug3("selectSingleNode: %s", nodeId); set(selectedEdgeIdAtom, null); if (nodeId === null || nodeId === void 0) { debug3("Clearing selection"); set(selectedNodeIdsAtom, /* @__PURE__ */ new Set()); } else { const currentSelection = get(selectedNodeIdsAtom); if (currentSelection.has(nodeId) && currentSelection.size === 1) { return; } set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId])); } }); toggleNodeInSelectionAtom = atom4(null, (get, set, nodeId) => { const currentSelection = get(selectedNodeIdsAtom); const newSelection = new Set(currentSelection); if (newSelection.has(nodeId)) { newSelection.delete(nodeId); } else { newSelection.add(nodeId); } set(selectedNodeIdsAtom, newSelection); }); clearSelectionAtom = atom4(null, (_get, set) => { debug3("clearSelection"); set(selectedNodeIdsAtom, /* @__PURE__ */ new Set()); }); addNodesToSelectionAtom = atom4(null, (get, set, nodeIds) => { const currentSelection = get(selectedNodeIdsAtom); const newSelection = new Set(currentSelection); for (const nodeId of nodeIds) { newSelection.add(nodeId); } set(selectedNodeIdsAtom, newSelection); }); removeNodesFromSelectionAtom = atom4(null, (get, set, nodeIds) => { const currentSelection = get(selectedNodeIdsAtom); const newSelection = new Set(currentSelection); for (const nodeId of nodeIds) { newSelection.delete(nodeId); } set(selectedNodeIdsAtom, newSelection); }); selectEdgeAtom = atom4(null, (get, set, edgeId) => { set(selectedEdgeIdAtom, edgeId); if (edgeId !== null) { set(selectedNodeIdsAtom, /* @__PURE__ */ new Set()); } }); clearEdgeSelectionAtom = atom4(null, (_get, set) => { set(selectedEdgeIdAtom, null); }); focusedNodeIdAtom = atom4(null); setFocusedNodeAtom = atom4(null, (_get, set, nodeId) => { set(focusedNodeIdAtom, nodeId); }); hasFocusedNodeAtom = atom4((get) => get(focusedNodeIdAtom) !== null); selectedNodesCountAtom = atom4((get) => get(selectedNodeIdsAtom).size); hasSelectionAtom = atom4((get) => get(selectedNodeIdsAtom).size > 0); } }); // src/utils/layout.ts function getNodeCenter(node) { return { x: node.x + node.width / 2, y: node.y + node.height / 2 }; } function setNodeCenter(node, centerX, centerY) { return { ...node, x: centerX - node.width / 2, y: centerY - node.height / 2 }; } function checkNodesOverlap(node1, node2) { const center1 = getNodeCenter(node1); const center2 = getNodeCenter(node2); const dx = Math.abs(center1.x - center2.x); const dy = Math.abs(center1.y - center2.y); const minDistanceX = (node1.width + node2.width) / 2; const minDistanceY = (node1.height + node2.height) / 2; return dx < minDistanceX && dy < minDistanceY; } function getNodeBounds(node) { return { x: node.x, y: node.y, width: node.width, height: node.height, left: node.x, right: node.x + node.width, top: node.y, bottom: node.y + node.height }; } function areNodesClose(node1, node2, threshold = 200) { const center1 = getNodeCenter(node1); const center2 = getNodeCenter(node2); const distance = Math.sqrt(Math.pow(center1.x - center2.x, 2) + Math.pow(center1.y - center2.y, 2)); return distance <= threshold; } var FitToBoundsMode, calculateBounds; var init_layout = __esm({ "src/utils/layout.ts"() { "use strict"; FitToBoundsMode = /* @__PURE__ */ (function(FitToBoundsMode2) { FitToBoundsMode2["Graph"] = "graph"; FitToBoundsMode2["Selection"] = "selection"; return FitToBoundsMode2; })({}); calculateBounds = (nodes) => { if (nodes.length === 0) { return { x: 0, y: 0, width: 0, height: 0 }; } const minX = Math.min(...nodes.map((node) => node.x)); const minY = Math.min(...nodes.map((node) => node.y)); const maxX = Math.max(...nodes.map((node) => node.x + node.width)); const maxY = Math.max(...nodes.map((node) => node.y + node.height)); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; }; } }); // src/core/viewport-store.ts var viewport_store_exports = {}; __export(viewport_store_exports, { ZOOM_EXIT_THRESHOLD: () => ZOOM_EXIT_THRESHOLD, ZOOM_TRANSITION_THRESHOLD: () => ZOOM_TRANSITION_THRESHOLD, animateFitToBoundsAtom: () => animateFitToBoundsAtom, animateZoomToNodeAtom: () => animateZoomToNodeAtom, centerOnNodeAtom: () => centerOnNodeAtom, fitToBoundsAtom: () => fitToBoundsAtom, isZoomTransitioningAtom: () => isZoomTransitioningAtom, panAtom: () => panAtom, resetViewportAtom: () => resetViewportAtom, screenToWorldAtom: () => screenToWorldAtom, setZoomAtom: () => setZoomAtom, viewportRectAtom: () => viewportRectAtom, worldToScreenAtom: () => worldToScreenAtom, zoomAnimationTargetAtom: () => zoomAnimationTargetAtom, zoomAtom: () => zoomAtom, zoomFocusNodeIdAtom: () => zoomFocusNodeIdAtom, zoomTransitionProgressAtom: () => zoomTransitionProgressAtom }); import { atom as atom5 } from "jotai"; var zoomAtom, panAtom, viewportRectAtom, screenToWorldAtom, worldToScreenAtom, setZoomAtom, resetViewportAtom, fitToBoundsAtom, centerOnNodeAtom, ZOOM_TRANSITION_THRESHOLD, ZOOM_EXIT_THRESHOLD, zoomFocusNodeIdAtom, zoomTransitionProgressAtom, isZoomTransitioningAtom, zoomAnimationTargetAtom, animateZoomToNodeAtom, animateFitToBoundsAtom; var init_viewport_store = __esm({ "src/core/viewport-store.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_graph_derived(); init_selection_store(); init_layout(); zoomAtom = atom5(1); panAtom = atom5({ x: 0, y: 0 }); viewportRectAtom = atom5(null); screenToWorldAtom = atom5((get) => { return (screenX, screenY) => { const pan = get(panAtom); const zoom = get(zoomAtom); const rect = get(viewportRectAtom); if (!rect) { return { x: screenX, y: screenY }; } const relativeX = screenX - rect.left; const relativeY = screenY - rect.top; return { x: (relativeX - pan.x) / zoom, y: (relativeY - pan.y) / zoom }; }; }); worldToScreenAtom = atom5((get) => { return (worldX, worldY) => { const pan = get(panAtom); const zoom = get(zoomAtom); const rect = get(viewportRectAtom); if (!rect) { return { x: worldX, y: worldY }; } return { x: worldX * zoom + pan.x + rect.left, y: worldY * zoom + pan.y + rect.top }; }; }); setZoomAtom = atom5(null, (get, set, { zoom, centerX, centerY }) => { const currentZoom = get(zoomAtom); const pan = get(panAtom); const rect = get(viewportRectAtom); const newZoom = Math.max(0.1, Math.min(5, zoom)); if (centerX !== void 0 && centerY !== void 0 && rect) { const relativeX = centerX - rect.left; const relativeY = centerY - rect.top; const worldX = (relativeX - pan.x) / currentZoom; const worldY = (relativeY - pan.y) / currentZoom; const newPanX = relativeX - worldX * newZoom; const newPanY = relativeY - worldY * newZoom; set(panAtom, { x: newPanX, y: newPanY }); } set(zoomAtom, newZoom); }); resetViewportAtom = atom5(null, (_get, set) => { set(zoomAtom, 1); set(panAtom, { x: 0, y: 0 }); }); fitToBoundsAtom = atom5(null, (get, set, { mode, padding = 20 }) => { const normalizedMode = typeof mode === "string" ? mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection : mode; const viewportSize = get(viewportRectAtom); if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return; get(nodePositionUpdateCounterAtom); let bounds; if (normalizedMode === FitToBoundsMode.Graph) { const graph = get(graphAtom); const nodes = graph.nodes().map((node) => { const attrs = graph.getNodeAttributes(node); return { x: attrs.x, y: attrs.y, width: attrs.width || 500, height: attrs.height || 500 }; }); bounds = calculateBounds(nodes); } else { const selectedIds = get(selectedNodeIdsAtom); const allNodes = get(uiNodesAtom); const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({ x: n.position.x, y: n.position.y, width: n.width ?? 500, height: n.height ?? 500 })); bounds = calculateBounds(selectedNodes); } if (bounds.width <= 0 || bounds.height <= 0) return; const maxHPad = Math.max(0, viewportSize.width / 2 - 1); const maxVPad = Math.max(0, viewportSize.height / 2 - 1); const safePadding = Math.max(0, Math.min(padding, maxHPad, maxVPad)); const effW = Math.max(1, viewportSize.width - 2 * safePadding); const effH = Math.max(1, viewportSize.height - 2 * safePadding); const scale = Math.min(effW / bounds.width, effH / bounds.height); if (scale <= 0 || !isFinite(scale)) return; set(zoomAtom, scale); const scaledW = bounds.width * scale; const scaledH = bounds.height * scale; const startX = safePadding + (effW - scaledW) / 2; const startY = safePadding + (effH - scaledH) / 2; set(panAtom, { x: startX - bounds.x * scale, y: startY - bounds.y * scale }); }); centerOnNodeAtom = atom5(null, (get, set, nodeId) => { const nodes = get(uiNodesAtom); const node = nodes.find((n) => n.id === nodeId); if (!node) return; const { x, y, width = 200, height = 100 } = node; const zoom = get(zoomAtom); const centerX = x + width / 2; const centerY = y + height / 2; const rect = get(viewportRectAtom); const halfWidth = rect ? rect.width / 2 : 400; const halfHeight = rect ? rect.height / 2 : 300; set(panAtom, { x: halfWidth - centerX * zoom, y: halfHeight - centerY * zoom }); }); ZOOM_TRANSITION_THRESHOLD = 3.5; ZOOM_EXIT_THRESHOLD = 2; zoomFocusNodeIdAtom = atom5(null); zoomTransitionProgressAtom = atom5(0); isZoomTransitioningAtom = atom5((get) => { const progress = get(zoomTransitionProgressAtom); return progress > 0 && progress < 1; }); zoomAnimationTargetAtom = atom5(null); animateZoomToNodeAtom = atom5(null, (get, set, { nodeId, targetZoom, duration = 300 }) => { const nodes = get(uiNodesAtom); const node = nodes.find((n) => n.id === nodeId); if (!node) return; const { x, y, width = 200, height = 100 } = node; const centerX = x + width / 2; const centerY = y + height / 2; const rect = get(viewportRectAtom); const halfWidth = rect ? rect.width / 2 : 400; const halfHeight = rect ? rect.height / 2 : 300; const finalZoom = targetZoom ?? get(zoomAtom); const targetPan = { x: halfWidth - centerX * finalZoom, y: halfHeight - centerY * finalZoom }; set(zoomFocusNodeIdAtom, nodeId); set(zoomAnimationTargetAtom, { targetZoom: finalZoom, targetPan, startZoom: get(zoomAtom), startPan: { ...get(panAtom) }, duration, startTime: performance.now() }); }); animateFitToBoundsAtom = atom5(null, (get, set, { mode, padding = 20, duration = 300 }) => { const viewportSize = get(viewportRectAtom); if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return; get(nodePositionUpdateCounterAtom); let bounds; if (mode === "graph") { const graph = get(graphAtom); const nodes = graph.nodes().map((node) => { const attrs = graph.getNodeAttributes(node); return { x: attrs.x, y: attrs.y, width: attrs.width || 500, height: attrs.height || 500 }; }); bounds = calculateBounds(nodes); } else { const selectedIds = get(selectedNodeIdsAtom); const allNodes = get(uiNodesAtom); const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({ x: n.position.x, y: n.position.y, width: n.width ?? 500, height: n.height ?? 500 })); bounds = calculateBounds(selectedNodes); } if (bounds.width <= 0 || bounds.height <= 0) return; const safePadding = Math.max(0, Math.min(padding, viewportSize.width / 2 - 1, viewportSize.height / 2 - 1)); const effW = Math.max(1, viewportSize.width - 2 * safePadding); const effH = Math.max(1, viewportSize.height - 2 * safePadding); const scale = Math.min(effW / bounds.width, effH / bounds.height); if (scale <= 0 || !isFinite(scale)) return; const scaledW = bounds.width * scale; const scaledH = bounds.height * scale; const startX = safePadding + (effW - scaledW) / 2; const startY = safePadding + (effH - scaledH) / 2; const targetPan = { x: startX - bounds.x * scale, y: startY - bounds.y * scale }; set(zoomAnimationTargetAtom, { targetZoom: scale, targetPan, startZoom: get(zoomAtom), startPan: { ...get(panAtom) }, duration, startTime: performance.now() }); }); } }); // src/core/history-actions.ts function applyDelta(graph, delta) { switch (delta.type) { case "move-node": { if (!graph.hasNode(delta.nodeId)) return false; graph.setNodeAttribute(delta.nodeId, "x", delta.to.x); graph.setNodeAttribute(delta.nodeId, "y", delta.to.y); return false; } case "resize-node": { if (!graph.hasNode(delta.nodeId)) return false; graph.setNodeAttribute(delta.nodeId, "width", delta.to.width); graph.setNodeAttribute(delta.nodeId, "height", delta.to.height); return false; } case "add-node": { if (graph.hasNode(delta.nodeId)) return false; graph.addNode(delta.nodeId, delta.attributes); return true; } case "remove-node": { if (!graph.hasNode(delta.nodeId)) return false; graph.dropNode(delta.nodeId); return true; } case "add-edge": { if (graph.hasEdge(delta.edgeId)) return false; if (!graph.hasNode(delta.source) || !graph.hasNode(delta.target)) return false; graph.addEdgeWithKey(delta.edgeId, delta.source, delta.target, delta.attributes); return true; } case "remove-edge": { if (!graph.hasEdge(delta.edgeId)) return false; graph.dropEdge(delta.edgeId); return true; } case "update-node-attr": { if (!graph.hasNode(delta.nodeId)) return false; graph.setNodeAttribute(delta.nodeId, delta.key, delta.to); return false; } case "batch": { let structuralChange = false; for (const d of delta.deltas) { if (applyDelta(graph, d)) structuralChange = true; } return structuralChange; } case "full-snapshot": { graph.clear(); for (const node of delta.nodes) { graph.addNode(node.id, node.attributes); } for (const edge of delta.edges) { if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) { graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge.attributes); } } return true; } } } function invertDelta(delta) { switch (delta.type) { case "move-node": return { ...delta, from: delta.to, to: delta.from }; case "resize-node": return { ...delta, from: delta.to, to: delta.from }; case "add-node": return { type: "remove-node", nodeId: delta.nodeId, attributes: delta.attributes, connectedEdges: [] }; case "remove-node": { const batch = [{ type: "add-node", nodeId: delta.nodeId, attributes: delta.attributes }, ...delta.connectedEdges.map((e) => ({ type: "add-edge", edgeId: e.id, source: e.source, target: e.target, attributes: e.attributes }))]; return batch.length === 1 ? batch[0] : { type: "batch", deltas: batch }; } case "add-edge": return { type: "remove-edge", edgeId: delta.edgeId, source: delta.source, target: delta.target, attributes: delta.attributes }; case "remove-edge": return { type: "add-edge", edgeId: delta.edgeId, source: delta.source, target: delta.target, attributes: delta.attributes }; case "update-node-attr": return { ...delta, from: delta.to, to: delta.from }; case "batch": return { type: "batch", deltas: delta.deltas.map(invertDelta).reverse() }; case "full-snapshot": return delta; } } function createSnapshot(graph, label) { const nodes = []; const edges = []; graph.forEachNode((nodeId, attributes) => { nodes.push({ id: nodeId, attributes: { ...attributes } }); }); graph.forEachEdge((edgeId, attributes, source, target) => { edges.push({ id: edgeId, source, target, attributes: { ...attributes } }); }); return { timestamp: Date.now(), label, nodes, edges }; } var init_history_actions = __esm({ "src/core/history-actions.ts"() { "use strict"; } }); // src/core/history-store.ts var history_store_exports = {}; __export(history_store_exports, { applyDelta: () => applyDelta, canRedoAtom: () => canRedoAtom, canUndoAtom: () => canUndoAtom, clearHistoryAtom: () => clearHistoryAtom, createSnapshot: () => createSnapshot, historyLabelsAtom: () => historyLabelsAtom, historyStateAtom: () => historyStateAtom, invertDelta: () => invertDelta, pushDeltaAtom: () => pushDeltaAtom, pushHistoryAtom: () => pushHistoryAtom, redoAtom: () => redoAtom, redoCountAtom: () => redoCountAtom, undoAtom: () => undoAtom, undoCountAtom: () => undoCountAtom }); import { atom as atom6 } from "jotai"; var debug4, MAX_HISTORY_SIZE, historyStateAtom, canUndoAtom, canRedoAtom, undoCountAtom, redoCountAtom, pushDeltaAtom, pushHistoryAtom, undoAtom, redoAtom, clearHistoryAtom, historyLabelsAtom; var init_history_store = __esm({ "src/core/history-store.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_debug(); init_history_actions(); init_history_actions(); debug4 = createDebug("history"); MAX_HISTORY_SIZE = 50; historyStateAtom = atom6({ past: [], future: [], isApplying: false }); canUndoAtom = atom6((get) => { const history = get(historyStateAtom); return history.past.length > 0 && !history.isApplying; }); canRedoAtom = atom6((get) => { const history = get(historyStateAtom); return history.future.length > 0 && !history.isApplying; }); undoCountAtom = atom6((get) => get(historyStateAtom).past.length); redoCountAtom = atom6((get) => get(historyStateAtom).future.length); pushDeltaAtom = atom6(null, (get, set, delta) => { const history = get(historyStateAtom); if (history.isApplying) return; const { label, ...cleanDelta } = delta; const entry = { forward: cleanDelta, reverse: invertDelta(cleanDelta), timestamp: Date.now(), label }; const newPast = [...history.past, entry]; if (newPast.length > MAX_HISTORY_SIZE) newPast.shift(); set(historyStateAtom, { past: newPast, future: [], // Clear redo stack isApplying: false }); debug4("Pushed delta: %s (past: %d)", label || delta.type, newPast.length); }); pushHistoryAtom = atom6(null, (get, set, label) => { const history = get(historyStateAtom); if (history.isApplying) return; const graph = get(graphAtom); const snapshot = createSnapshot(graph, label); const forward = { type: "full-snapshot", nodes: snapshot.nodes, edges: snapshot.edges }; const entry = { forward, reverse: forward, // For full snapshots, reverse IS the current state timestamp: Date.now(), label }; const newPast = [...history.past, entry]; if (newPast.length > MAX_HISTORY_SIZE) newPast.shift(); set(historyStateAtom, { past: newPast, future: [], isApplying: false }); debug4("Pushed snapshot: %s (past: %d)", label || "unnamed", newPast.length); }); undoAtom = atom6(null, (get, set) => { const history = get(historyStateAtom); if (history.past.length === 0 || history.isApplying) return false; set(historyStateAtom, { ...history, isApplying: true }); try { const graph = get(graphAtom); const newPast = [...history.past]; const entry = newPast.pop(); let forwardForRedo = entry.forward; if (entry.reverse.type === "full-snapshot") { const currentSnapshot = createSnapshot(graph, "current"); forwardForRedo = { type: "full-snapshot", nodes: currentSnapshot.nodes, edges: currentSnapshot.edges }; } const structuralChange = applyDelta(graph, entry.reverse); if (structuralChange) { set(graphAtom, graph); set(graphUpdateVersionAtom, (v) => v + 1); } set(nodePositionUpdateCounterAtom, (c) => c + 1); const redoEntry = { forward: forwardForRedo, reverse: entry.reverse, timestamp: entry.timestamp, label: entry.label }; set(historyStateAtom, { past: newPast, future: [redoEntry, ...history.future], isApplying: false }); debug4("Undo: %s (past: %d, future: %d)", entry.label, newPast.length, history.future.length + 1); return true; } catch (error) { debug4.error("Undo failed: %O", error); set(historyStateAtom, { ...history, isApplying: false }); return false; } }); redoAtom = atom6(null, (get, set) => { const history = get(historyStateAtom); if (history.future.length === 0 || history.isApplying) return false; set(historyStateAtom, { ...history, isApplying: true }); try { const graph = get(graphAtom); const newFuture = [...history.future]; const entry = newFuture.shift(); let reverseForUndo = entry.reverse; if (entry.forward.type === "full-snapshot") { const currentSnapshot = createSnapshot(graph, "current"); reverseForUndo = { type: "full-snapshot", nodes: currentSnapshot.nodes, edges: currentSnapshot.edges }; } const structuralChange = applyDelta(graph, entry.forward); if (structuralChange) { set(graphAtom, graph); set(graphUpdateVersionAtom, (v) => v + 1); } set(nodePositionUpdateCounterAtom, (c) => c + 1); const undoEntry = { forward: entry.forward, reverse: reverseForUndo, timestamp: entry.timestamp, label: entry.label }; set(historyStateAtom, { past: [...history.past, undoEntry], future: newFuture, isApplying: false }); debug4("Redo: %s (past: %d, future: %d)", entry.label, history.past.length + 1, newFuture.length); return true; } catch (error) { debug4.error("Redo failed: %O", error); set(historyStateAtom, { ...history, isApplying: false }); return false; } }); clearHistoryAtom = atom6(null, (_get, set) => { set(historyStateAtom, { past: [], future: [], isApplying: false }); debug4("History cleared"); }); historyLabelsAtom = atom6((get) => { const history = get(historyStateAtom); return { past: history.past.map((e) => e.label || "Unnamed"), future: history.future.map((e) => e.label || "Unnamed") }; }); } }); // src/core/group-store.ts var group_store_exports = {}; __export(group_store_exports, { autoResizeGroupAtom: () => autoResizeGroupAtom, collapseGroupAtom: () => collapseGroupAtom, collapsedEdgeRemapAtom: () => collapsedEdgeRemapAtom, collapsedGroupsAtom: () => collapsedGroupsAtom, expandGroupAtom: () => expandGroupAtom, getNodeDescendants: () => getNodeDescendants, groupChildCountAtom: () => groupChildCountAtom, groupSelectedNodesAtom: () => groupSelectedNodesAtom, isGroupNodeAtom: () => isGroupNodeAtom, isNodeCollapsed: () => isNodeCollapsed, moveNodesToGroupAtom: () => moveNodesToGroupAtom, nestNodesOnDropAtom: () => nestNodesOnDropAtom, nodeChildrenAtom: () => nodeChildrenAtom, nodeParentAtom: () => nodeParentAtom, removeFromGroupAtom: () => removeFromGroupAtom, setNodeParentAtom: () => setNodeParentAtom, toggleGroupCollapseAtom: () => toggleGroupCollapseAtom, ungroupNodesAtom: () => ungroupNodesAtom }); import { atom as atom7 } from "jotai"; function getNodeDescendants(graph, groupId) { const descendants = []; const stack = [groupId]; while (stack.length > 0) { const current = stack.pop(); graph.forEachNode((nodeId, attrs) => { if (attrs.parentId === current) { descendants.push(nodeId); stack.push(nodeId); } }); } return descendants; } function isNodeCollapsed(nodeId, getParentId, collapsed) { let current = nodeId; while (true) { const parentId = getParentId(current); if (!parentId) return false; if (collapsed.has(parentId)) return true; current = parentId; } } var collapsedGroupsAtom, toggleGroupCollapseAtom, collapseGroupAtom, expandGroupAtom, nodeChildrenAtom, nodeParentAtom, isGroupNodeAtom, groupChildCountAtom, setNodeParentAtom, moveNodesToGroupAtom, removeFromGroupAtom, groupSelectedNodesAtom, ungroupNodesAtom, nestNodesOnDropAtom, collapsedEdgeRemapAtom, autoResizeGroupAtom; var init_group_store = __esm({ "src/core/group-store.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_history_store(); collapsedGroupsAtom = atom7(/* @__PURE__ */ new Set()); toggleGroupCollapseAtom = atom7(null, (get, set, groupId) => { const current = get(collapsedGroupsAtom); const next = new Set(current); if (next.has(groupId)) { next.delete(groupId); } else { next.add(groupId); } set(collapsedGroupsAtom, next); }); collapseGroupAtom = atom7(null, (get, set, groupId) => { const current = get(collapsedGroupsAtom); if (!current.has(groupId)) { const next = new Set(current); next.add(groupId); set(collapsedGroupsAtom, next); } }); expandGroupAtom = atom7(null, (get, set, groupId) => { const current = get(collapsedGroupsAtom); if (current.has(groupId)) { const next = new Set(current); next.delete(groupId); set(collapsedGroupsAtom, next); } }); nodeChildrenAtom = atom7((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); return (parentId) => { const children = []; graph.forEachNode((nodeId, attrs) => { if (attrs.parentId === parentId) { children.push(nodeId); } }); return children; }; }); nodeParentAtom = atom7((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); return (nodeId) => { if (!graph.hasNode(nodeId)) return void 0; return graph.getNodeAttribute(nodeId, "parentId"); }; }); isGroupNodeAtom = atom7((get) => { const getChildren = get(nodeChildrenAtom); return (nodeId) => getChildren(nodeId).length > 0; }); groupChildCountAtom = atom7((get) => { const getChildren = get(nodeChildrenAtom); return (groupId) => getChildren(groupId).length; }); setNodeParentAtom = atom7(null, (get, set, { nodeId, parentId }) => { const graph = get(graphAtom); if (!graph.hasNode(nodeId)) return; if (parentId) { if (parentId === nodeId) return; let current = parentId; while (current) { if (current === nodeId) return; if (!graph.hasNode(current)) break; current = graph.getNodeAttribute(current, "parentId"); } } graph.setNodeAttribute(nodeId, "parentId", parentId); set(graphUpdateVersionAtom, (v) => v + 1); }); moveNodesToGroupAtom = atom7(null, (get, set, { nodeIds, groupId }) => { for (const nodeId of nodeIds) { set(setNodeParentAtom, { nodeId, parentId: groupId }); } }); removeFromGroupAtom = atom7(null, (get, set, nodeId) => { set(setNodeParentAtom, { nodeId, parentId: void 0 }); }); groupSelectedNodesAtom = atom7(null, (get, set, { nodeIds, groupNodeId }) => { set(pushHistoryAtom, `Group ${nodeIds.length} nodes`); const graph = get(graphAtom); let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const nodeId of nodeIds) { if (!graph.hasNode(nodeId)) continue; const attrs = graph.getNodeAttributes(nodeId); minX = Math.min(minX, attrs.x); minY = Math.min(minY, attrs.y); maxX = Math.max(maxX, attrs.x + (attrs.width || 200)); maxY = Math.max(maxY, attrs.y + (attrs.height || 100)); } const padding = 20; if (graph.hasNode(groupNodeId)) { graph.setNodeAttribute(groupNodeId, "x", minX - padding); graph.setNodeAttribute(groupNodeId, "y", minY - padding - 30); graph.setNodeAttribute(groupNodeId, "width", maxX - minX + 2 * padding); graph.setNodeAttribute(groupNodeId, "height", maxY - minY + 2 * padding + 30); } for (const nodeId of nodeIds) { if (nodeId !== groupNodeId && graph.hasNode(nodeId)) { graph.setNodeAttribute(nodeId, "parentId", groupNodeId); } } set(graphUpdateVersionAtom, (v) => v + 1); set(nodePositionUpdateCounterAtom, (c) => c + 1); }); ungroupNodesAtom = atom7(null, (get, set, groupId) => { set(pushHistoryAtom, "Ungroup nodes"); const graph = get(graphAtom); graph.forEachNode((nodeId, attrs) => { if (attrs.parentId === groupId) { graph.setNodeAttribute(nodeId, "parentId", void 0); } }); set(graphUpdateVersionAtom, (v) => v + 1); }); nestNodesOnDropAtom = atom7(null, (get, set, { nodeIds, targetId }) => { set(pushHistoryAtom, "Nest nodes"); for (const nodeId of nodeIds) { if (nodeId === targetId) continue; set(setNodeParentAtom, { nodeId, parentId: targetId }); } set(autoResizeGroupAtom, targetId); }); collapsedEdgeRemapAtom = atom7((get) => { const collapsed = get(collapsedGroupsAtom); if (collapsed.size === 0) return /* @__PURE__ */ new Map(); get(graphUpdateVersionAtom); const graph = get(graphAtom); const remap = /* @__PURE__ */ new Map(); for (const nodeId of graph.nodes()) { let current = nodeId; let outermost = null; while (true) { if (!graph.hasNode(current)) break; const parent = graph.getNodeAttribute(current, "parentId"); if (!parent) break; if (collapsed.has(parent)) outermost = parent; current = parent; } if (outermost) remap.set(nodeId, outermost); } return remap; }); autoResizeGroupAtom = atom7(null, (get, set, groupId) => { const graph = get(graphAtom); if (!graph.hasNode(groupId)) return; const children = []; graph.forEachNode((nodeId, attrs) => { if (attrs.parentId === groupId) { children.push(nodeId); } }); if (children.length === 0) return; const padding = 20; const headerHeight = 30; let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const childId of children) { const attrs = graph.getNodeAttributes(childId); minX = Math.min(minX, attrs.x); minY = Math.min(minY, attrs.y); maxX = Math.max(maxX, attrs.x + (attrs.width || 200)); maxY = Math.max(maxY, attrs.y + (attrs.height || 100)); } graph.setNodeAttribute(groupId, "x", minX - padding); graph.setNodeAttribute(groupId, "y", minY - padding - headerHeight); graph.setNodeAttribute(groupId, "width", maxX - minX + 2 * padding); graph.setNodeAttribute(groupId, "height", maxY - minY + 2 * padding + headerHeight); set(nodePositionUpdateCounterAtom, (c) => c + 1); }); } }); // src/core/graph-derived.ts import { atom as atom8 } from "jotai"; import { atomFamily as atomFamily2 } from "jotai-family"; function getEdgeCache(graph) { let cache = _edgeCacheByGraph.get(graph); if (!cache) { cache = /* @__PURE__ */ new Map(); _edgeCacheByGraph.set(graph, cache); } return cache; } var highestZIndexAtom, _prevUiNodesByGraph, uiNodesAtom, nodeKeysAtom, nodeFamilyAtom, edgeKeysAtom, edgeKeysWithTempEdgeAtom, _edgeCacheByGraph, edgeFamilyAtom; var init_graph_derived = __esm({ "src/core/graph-derived.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_viewport_store(); init_group_store(); highestZIndexAtom = atom8((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); let maxZ = 0; graph.forEachNode((_node, attributes) => { if (attributes.zIndex > maxZ) { maxZ = attributes.zIndex; } }); return maxZ; }); _prevUiNodesByGraph = /* @__PURE__ */ new WeakMap(); uiNodesAtom = atom8((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); const currentDraggingId = get(draggingNodeIdAtom); const collapsed = get(collapsedGroupsAtom); const nodes = []; graph.forEachNode((nodeId, attributes) => { if (collapsed.size > 0) { let current = nodeId; let hidden = false; while (true) { if (!graph.hasNode(current)) break; const pid = graph.getNodeAttributes(current).parentId; if (!pid) break; if (collapsed.has(pid)) { hidden = true; break; } current = pid; } if (hidden) return; } const position = get(nodePositionAtomFamily(nodeId)); nodes.push({ ...attributes, id: nodeId, position, isDragging: nodeId === currentDraggingId }); }); const prev = _prevUiNodesByGraph.get(graph) ?? []; if (nodes.length === prev.length && nodes.every((n, i) => n.id === prev[i].id && n.position === prev[i].position && n.isDragging === prev[i].isDragging)) { return prev; } _prevUiNodesByGraph.set(graph, nodes); return nodes; }); nodeKeysAtom = atom8((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); return graph.nodes(); }); nodeFamilyAtom = atomFamily2((nodeId) => atom8((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); if (!graph.hasNode(nodeId)) { return null; } const attributes = graph.getNodeAttributes(nodeId); const position = get(nodePositionAtomFamily(nodeId)); const currentDraggingId = get(draggingNodeIdAtom); return { ...attributes, id: nodeId, position, isDragging: nodeId === currentDraggingId }; }), (a, b) => a === b); edgeKeysAtom = atom8((get) => { get(graphUpdateVersionAtom); const graph = get(graphAtom); return graph.edges(); }); edgeKeysWithTempEdgeAtom = atom8((get) => { const keys = get(edgeKeysAtom); const edgeCreation = get(edgeCreationAtom); if (edgeCreation.isCreating) { return [...keys, "temp-creating-edge"]; } return keys; }); _edgeCacheByGraph = /* @__PURE__ */ new WeakMap(); edgeFamilyAtom = atomFamily2((key) => atom8((get) => { get(graphUpdateVersionAtom); if (key === "temp-creating-edge") { const edgeCreationState = get(edgeCreationAtom); const graph2 = get(graphAtom); if (edgeCreationState.isCreating && edgeCreationState.sourceNodeId && edgeCreationState.targetPosition) { const sourceNodeAttrs = graph2.getNodeAttributes(edgeCreationState.sourceNodeId); const sourceNodePosition = get(nodePositionAtomFamily(edgeCreationState.sourceNodeId)); const pan = get(panAtom); const zoom = get(zoomAtom); const viewportRect = get(viewportRectAtom); if (sourceNodeAttrs && viewportRect) { const mouseX = edgeCreationState.targetPosition.x - viewportRect.left; const mouseY = edgeCreationState.targetPosition.y - viewportRect.top; const worldTargetX = (mouseX - pan.x) / zoom; const worldTargetY = (mouseY - pan.y) / zoom; const tempEdge = { key: "temp-creating-edge", sourceId: edgeCreationState.sourceNodeId, targetId: "temp-cursor", sourcePosition: sourceNodePosition, targetPosition: { x: worldTargetX, y: worldTargetY }, sourceNodeSize: sourceNodeAttrs.size, sourceNodeWidth: sourceNodeAttrs.width, sourceNodeHeight: sourceNodeAttrs.height, targetNodeSize: 0, targetNodeWidth: 0, targetNodeHeight: 0, type: "dashed", color: "#FF9800", weight: 2, label: void 0, dbData: { id: "temp-creating-edge", graph_id: get(currentGraphIdAtom) || "", source_node_id: edgeCreationState.sourceNodeId, target_node_id: "temp-cursor", edge_type: "temp", filter_condition: null, ui_properties: null, data: null, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() } }; return tempEdge; } } return null; } const graph = get(graphAtom); if (!graph.hasEdge(key)) { getEdgeCache(graph).delete(key); return null; } const sourceId = graph.source(key); const targetId = graph.target(key); const attributes = graph.getEdgeAttributes(key); const remap = get(collapsedEdgeRemapAtom); const effectiveSourceId = remap.get(sourceId) ?? sourceId; const effectiveTargetId = remap.get(targetId) ?? targetId; if (!graph.hasNode(effectiveSourceId) || !graph.hasNode(effectiveTargetId)) { getEdgeCache(graph).delete(key); return null; } const sourceAttributes = graph.getNodeAttributes(effectiveSourceId); const targetAttributes = graph.getNodeAttributes(effectiveTargetId); const sourcePosition = get(nodePositionAtomFamily(effectiveSourceId)); const targetPosition = get(nodePositionAtomFamily(effectiveTargetId)); if (sourceAttributes && targetAttributes) { const next = { ...attributes, key, sourceId: effectiveSourceId, targetId: effectiveTargetId, sourcePosition, targetPosition, sourceNodeSize: sourceAttributes.size, targetNodeSize: targetAttributes.size, sourceNodeWidth: sourceAttributes.width ?? sourceAttributes.size, sourceNodeHeight: sourceAttributes.height ?? sourceAttributes.size, targetNodeWidth: targetAttributes.width ?? targetAttributes.size, targetNodeHeight: targetAttributes.height ?? targetAttributes.size }; const edgeCache = getEdgeCache(graph); const prev = edgeCache.get(key); if (prev && prev.sourcePosition === next.sourcePosition && prev.targetPosition === next.targetPosition && prev.sourceId === next.sourceId && prev.targetId === next.targetId && prev.type === next.type && prev.color === next.color && prev.weight === next.weight && prev.label === next.label && prev.sourceNodeSize === next.sourceNodeSize && prev.targetNodeSize === next.targetNodeSize && prev.sourceNodeWidth === next.sourceNodeWidth && prev.sourceNodeHeight === next.sourceNodeHeight && prev.targetNodeWidth === next.targetNodeWidth && prev.targetNodeHeight === next.targetNodeHeight) { return prev; } edgeCache.set(key, next); return next; } getEdgeCache(graph).delete(key); return null; }), (a, b) => a === b); } }); // src/core/reduced-motion-store.ts import { atom as atom9 } from "jotai"; var prefersReducedMotionAtom, watchReducedMotionAtom; var init_reduced_motion_store = __esm({ "src/core/reduced-motion-store.ts"() { "use strict"; prefersReducedMotionAtom = atom9(typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false); watchReducedMotionAtom = atom9(null, (_get, set) => { if (typeof window === "undefined" || typeof window.matchMedia !== "function") return; const mql = window.matchMedia("(prefers-reduced-motion: reduce)"); const handler = (e) => { set(prefersReducedMotionAtom, e.matches); }; set(prefersReducedMotionAtom, mql.matches); mql.addEventListener("change", handler); return () => mql.removeEventListener("change", handler); }); } }); // src/core/graph-mutations-edges.ts import { atom as atom10 } from "jotai"; var debug5, addEdgeToLocalGraphAtom, removeEdgeFromLocalGraphAtom, swapEdgeAtomicAtom, departingEdgesAtom, EDGE_ANIMATION_DURATION, removeEdgeWithAnimationAtom, editingEdgeLabelAtom, updateEdgeLabelAtom; var init_graph_mutations_edges = __esm({ "src/core/graph-mutations-edges.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_graph_derived(); init_debug(); init_reduced_motion_store(); debug5 = createDebug("graph:mutations:edges"); addEdgeToLocalGraphAtom = atom10(null, (get, set, newEdge) => { const graph = get(graphAtom); if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) { const uiProps = newEdge.ui_properties || {}; const attributes = { type: typeof uiProps.style === "string" ? uiProps.style : "solid", color: typeof uiProps.color === "string" ? uiProps.color : "#999", label: newEdge.edge_type ?? void 0, weight: typeof uiProps.weight === "number" ? uiProps.weight : 1, dbData: newEdge }; if (!graph.hasEdge(newEdge.id)) { try { debug5("Adding edge %s to local graph", newEdge.id); graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes); set(graphAtom, graph.copy()); set(graphUpdateVersionAtom, (v) => v + 1); } catch (e) { debug5("Failed to add edge %s: %o", newEdge.id, e); } } } }); removeEdgeFromLocalGraphAtom = atom10(null, (get, set, edgeId) => { const graph = get(graphAtom); if (graph.hasEdge(edgeId)) { graph.dropEdge(edgeId); set(graphAtom, graph.copy()); set(graphUpdateVersionAtom, (v) => v + 1); } }); swapEdgeAtomicAtom = atom10(null, (get, set, { tempEdgeId, newEdge }) => { const graph = get(graphAtom); if (graph.hasEdge(tempEdgeId)) { graph.dropEdge(tempEdgeId); } if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) { const uiProps = newEdge.ui_properties || {}; const attributes = { type: typeof uiProps.style === "string" ? uiProps.style : "solid", color: typeof uiProps.color === "string" ? uiProps.color : "#999", label: newEdge.edge_type ?? void 0, weight: typeof uiProps.weight === "number" ? uiProps.weight : 1, dbData: newEdge }; if (!graph.hasEdge(newEdge.id)) { try { debug5("Atomically swapping temp edge %s with real edge %s", tempEdgeId, newEdge.id); graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes); } catch (e) { debug5("Failed to add edge %s: %o", newEdge.id, e); } } } set(graphAtom, graph.copy()); set(graphUpdateVersionAtom, (v) => v + 1); }); departingEdgesAtom = atom10(/* @__PURE__ */ new Map()); EDGE_ANIMATION_DURATION = 300; removeEdgeWithAnimationAtom = atom10(null, (get, set, edgeKey) => { const edgeState = get(edgeFamilyAtom(edgeKey)); if (edgeState) { const departing = new Map(get(departingEdgesAtom)); departing.set(edgeKey, edgeState); set(departingEdgesAtom, departing); set(removeEdgeFromLocalGraphAtom, edgeKey); const duration = get(prefersReducedMotionAtom) ? 0 : EDGE_ANIMATION_DURATION; setTimeout(() => { const current = new Map(get(departingEdgesAtom)); current.delete(edgeKey); set(departingEdgesAtom, current); }, duration); } }); editingEdgeLabelAtom = atom10(null); updateEdgeLabelAtom = atom10(null, (get, set, { edgeKey, label }) => { const graph = get(graphAtom); if (graph.hasEdge(edgeKey)) { graph.setEdgeAttribute(edgeKey, "label", label || void 0); set(graphUpdateVersionAtom, (v) => v + 1); set(nodePositionUpdateCounterAtom, (c) => c + 1); } }); } }); // src/core/graph-mutations-advanced.ts import { atom as atom11 } from "jotai"; var debug6, dropTargetNodeIdAtom, splitNodeAtom, mergeNodesAtom; var init_graph_mutations_advanced = __esm({ "src/core/graph-mutations-advanced.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_history_store(); init_debug(); init_graph_mutations_edges(); init_graph_mutations(); debug6 = createDebug("graph:mutations:advanced"); dropTargetNodeIdAtom = atom11(null); splitNodeAtom = atom11(null, (get, set, { nodeId, position1, position2 }) => { const graph = get(graphAtom); if (!graph.hasNode(nodeId)) return; const attrs = graph.getNodeAttributes(nodeId); const graphId = get(currentGraphIdAtom) || attrs.dbData.graph_id; set(pushHistoryAtom, "Split node"); graph.setNodeAttribute(nodeId, "x", position1.x); graph.setNodeAttribute(nodeId, "y", position1.y); const edges = []; graph.forEachEdge(nodeId, (_key, eAttrs, source, target) => { edges.push({ source, target, attrs: eAttrs }); }); const cloneId = `split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const cloneDbNode = { ...attrs.dbData, id: cloneId, graph_id: graphId, ui_properties: { ...attrs.dbData.ui_properties || {}, x: position2.x, y: position2.y }, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }; set(addNodeToLocalGraphAtom, cloneDbNode); for (const edge of edges) { const newSource = edge.source === nodeId ? cloneId : edge.source; const newTarget = edge.target === nodeId ? cloneId : edge.target; set(addEdgeToLocalGraphAtom, { ...edge.attrs.dbData, id: `split-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, source_node_id: newSource, target_node_id: newTarget }); } set(graphUpdateVersionAtom, (v) => v + 1); set(nodePositionUpdateCounterAtom, (c) => c + 1); debug6("Split node %s \u2192 clone %s", nodeId, cloneId); }); mergeNodesAtom = atom11(null, (get, set, { nodeIds }) => { if (nodeIds.length < 2) return; const graph = get(graphAtom); const [survivorId, ...doomed] = nodeIds; if (!graph.hasNode(survivorId)) return; set(pushHistoryAtom, `Merge ${nodeIds.length} nodes`); const doomedSet = new Set(doomed); for (const doomedId of doomed) { if (!graph.hasNode(doomedId)) continue; const edges = []; graph.forEachEdge(doomedId, (_key, eAttrs, source, target) => { edges.push({ source, target, attrs: eAttrs }); }); for (const edge of edges) { const newSource = doomedSet.has(edge.source) ? survivorId : edge.source; const newTarget = doomedSet.has(edge.target) ? survivorId : edge.target; if (newSource === newTarget) continue; set(addEdgeToLocalGraphAtom, { ...edge.attrs.dbData, id: `merge-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, source_node_id: newSource, target_node_id: newTarget }); } set(optimisticDeleteNodeAtom, { nodeId: doomedId }); } set(graphUpdateVersionAtom, (v) => v + 1); debug6("Merged nodes %o \u2192 survivor %s", nodeIds, survivorId); }); } }); // src/core/graph-mutations.ts var graph_mutations_exports = {}; __export(graph_mutations_exports, { EDGE_ANIMATION_DURATION: () => EDGE_ANIMATION_DURATION, addNodeToLocalGraphAtom: () => addNodeToLocalGraphAtom, departingEdgesAtom: () => departingEdgesAtom, dropTargetNodeIdAtom: () => dropTargetNodeIdAtom, editingEdgeLabelAtom: () => editingEdgeLabelAtom, endNodeDragAtom: () => endNodeDragAtom, loadGraphFromDbAtom: () => loadGraphFromDbAtom, mergeNodesAtom: () => mergeNodesAtom, optimisticDeleteEdgeAtom: () => optimisticDeleteEdgeAtom, optimisticDeleteNodeAtom: () => optimisticDeleteNodeAtom, removeEdgeWithAnimationAtom: () => removeEdgeWithAnimationAtom, splitNodeAtom: () => splitNodeAtom, startNodeDragAtom: () => startNodeDragAtom, swapEdgeAtomicAtom: () => swapEdgeAtomicAtom, updateEdgeLabelAtom: () => updateEdgeLabelAtom }); import { atom as atom12 } from "jotai"; import Graph3 from "graphology"; var debug7, startNodeDragAtom, endNodeDragAtom, optimisticDeleteNodeAtom, optimisticDeleteEdgeAtom, addNodeToLocalGraphAtom, loadGraphFromDbAtom; var init_graph_mutations = __esm({ "src/core/graph-mutations.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_graph_derived(); init_group_store(); init_debug(); init_graph_mutations_edges(); init_graph_mutations_advanced(); debug7 = createDebug("graph:mutations"); startNodeDragAtom = atom12(null, (get, set, { nodeId }) => { const graph = get(graphAtom); if (!graph.hasNode(nodeId)) return; const currentAttributes = graph.getNodeAttributes(nodeId); set(preDragNodeAttributesAtom, JSON.parse(JSON.stringify(currentAttributes))); const currentHighestZIndex = get(highestZIndexAtom); const newZIndex = currentHighestZIndex + 1; graph.setNodeAttribute(nodeId, "zIndex", newZIndex); set(draggingNodeIdAtom, nodeId); }); endNodeDragAtom = atom12(null, (get, set, _payload) => { const currentDraggingId = get(draggingNodeIdAtom); if (currentDraggingId) { debug7("Node %s drag ended", currentDraggingId); const graph = get(graphAtom); if (graph.hasNode(currentDraggingId)) { const parentId = graph.getNodeAttribute(currentDraggingId, "parentId"); if (parentId) { set(autoResizeGroupAtom, parentId); } } } set(draggingNodeIdAtom, null); set(preDragNodeAttributesAtom, null); }); optimisticDeleteNodeAtom = atom12(null, (get, set, { nodeId }) => { const graph = get(graphAtom); if (graph.hasNode(nodeId)) { graph.dropNode(nodeId); set(cleanupNodePositionAtom, nodeId); set(graphAtom, graph.copy()); debug7("Optimistically deleted node %s", nodeId); } }); optimisticDeleteEdgeAtom = atom12(null, (get, set, { edgeKey }) => { const graph = get(graphAtom); if (graph.hasEdge(edgeKey)) { graph.dropEdge(edgeKey); set(graphAtom, graph.copy()); debug7("Optimistically deleted edge %s", edgeKey); } }); addNodeToLocalGraphAtom = atom12(null, (get, set, newNode) => { const graph = get(graphAtom); if (graph.hasNode(newNode.id)) { debug7("Node %s already exists, skipping", newNode.id); return; } const uiProps = newNode.ui_properties || {}; const attributes = { x: typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800, y: typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600, size: typeof uiProps.size === "number" ? uiProps.size : 15, width: typeof uiProps.width === "number" ? uiProps.width : 500, height: typeof uiProps.height === "number" ? uiProps.height : 500, color: typeof uiProps.color === "string" ? uiProps.color : "#ccc", label: newNode.label || newNode.node_type || newNode.id, zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0, dbData: newNode }; debug7("Adding node %s to local graph at (%d, %d)", newNode.id, attributes.x, attributes.y); graph.addNode(newNode.id, attributes); set(graphAtom, graph.copy()); set(graphUpdateVersionAtom, (v) => v + 1); set(nodePositionUpdateCounterAtom, (c) => c + 1); }); loadGraphFromDbAtom = atom12(null, (get, set, fetchedNodes, fetchedEdges) => { debug7("========== START SYNC =========="); debug7("Fetched nodes: %d, edges: %d", fetchedNodes.length, fetchedEdges.length); const currentGraphId = get(currentGraphIdAtom); if (fetchedNodes.length > 0 && fetchedNodes[0].graph_id !== currentGraphId) { debug7("Skipping sync - data belongs to different graph"); return; } const existingGraph = get(graphAtom); const isDragging = get(draggingNodeIdAtom) !== null; if (isDragging) { debug7("Skipping sync - drag in progress"); return; } const existingNodeIds = new Set(existingGraph.nodes()); const fetchedNodeIds = new Set(fetchedNodes.map((n) => n.id)); const hasAnyCommonNodes = Array.from(existingNodeIds).some((id) => fetchedNodeIds.has(id)); let graph; if (hasAnyCommonNodes && existingNodeIds.size > 0) { debug7("Merging DB data into existing graph"); graph = existingGraph.copy(); } else { debug7("Creating fresh graph (graph switch detected)"); graph = new Graph3(graphOptions); } const fetchedEdgeIds = new Set(fetchedEdges.map((e) => e.id)); if (hasAnyCommonNodes && existingNodeIds.size > 0) { graph.forEachNode((nodeId) => { if (!fetchedNodeIds.has(nodeId)) { debug7("Removing deleted node: %s", nodeId); graph.dropNode(nodeId); nodePositionAtomFamily.remove(nodeId); } }); } fetchedNodes.forEach((node) => { const uiProps = node.ui_properties || {}; const newX = typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800; const newY = typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600; if (graph.hasNode(node.id)) { const currentAttrs = graph.getNodeAttributes(node.id); const attributes = { x: newX, y: newY, size: typeof uiProps.size === "number" ? uiProps.size : currentAttrs.size, width: typeof uiProps.width === "number" ? uiProps.width : currentAttrs.width ?? 500, height: typeof uiProps.height === "number" ? uiProps.height : currentAttrs.height ?? 500, color: typeof uiProps.color === "string" ? uiProps.color : currentAttrs.color, label: node.label || node.node_type || node.id, zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : currentAttrs.zIndex, dbData: node }; graph.replaceNodeAttributes(node.id, attributes); } else { const attributes = { x: newX, y: newY, size: typeof uiProps.size === "number" ? uiProps.size : 15, width: typeof uiProps.width === "number" ? uiProps.width : 500, height: typeof uiProps.height === "number" ? uiProps.height : 500, color: typeof uiProps.color === "string" ? uiProps.color : "#ccc", label: node.label || node.node_type || node.id, zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0, dbData: node }; graph.addNode(node.id, attributes); } }); graph.forEachEdge((edgeId) => { if (!fetchedEdgeIds.has(edgeId)) { debug7("Removing deleted edge: %s", edgeId); graph.dropEdge(edgeId); } }); fetchedEdges.forEach((edge) => { if (graph.hasNode(edge.source_node_id) && graph.hasNode(edge.target_node_id)) { const uiProps = edge.ui_properties || {}; const attributes = { type: typeof uiProps.style === "string" ? uiProps.style : "solid", color: typeof uiProps.color === "string" ? uiProps.color : "#999", label: edge.edge_type ?? void 0, weight: typeof uiProps.weight === "number" ? uiProps.weight : 1, dbData: edge }; if (graph.hasEdge(edge.id)) { graph.replaceEdgeAttributes(edge.id, attributes); } else { try { graph.addEdgeWithKey(edge.id, edge.source_node_id, edge.target_node_id, attributes); } catch (e) { debug7("Failed to add edge %s: %o", edge.id, e); } } } }); set(graphAtom, graph); set(graphUpdateVersionAtom, (v) => v + 1); debug7("========== SYNC COMPLETE =========="); debug7("Final graph: %d nodes, %d edges", graph.order, graph.size); }); } }); // src/core/sync-store.ts import { atom as atom13 } from "jotai"; var debug8, syncStatusAtom, pendingMutationsCountAtom, isOnlineAtom, lastSyncErrorAtom, lastSyncTimeAtom, mutationQueueAtom, syncStateAtom, startMutationAtom, completeMutationAtom, trackMutationErrorAtom, setOnlineStatusAtom, queueMutationAtom, dequeueMutationAtom, incrementRetryCountAtom, getNextQueuedMutationAtom, clearMutationQueueAtom; var init_sync_store = __esm({ "src/core/sync-store.ts"() { "use strict"; init_debug(); debug8 = createDebug("sync"); syncStatusAtom = atom13("synced"); pendingMutationsCountAtom = atom13(0); isOnlineAtom = atom13(typeof navigator !== "undefined" ? navigator.onLine : true); lastSyncErrorAtom = atom13(null); lastSyncTimeAtom = atom13(Date.now()); mutationQueueAtom = atom13([]); syncStateAtom = atom13((get) => ({ status: get(syncStatusAtom), pendingMutations: get(pendingMutationsCountAtom), lastError: get(lastSyncErrorAtom), lastSyncTime: get(lastSyncTimeAtom), isOnline: get(isOnlineAtom), queuedMutations: get(mutationQueueAtom).length })); startMutationAtom = atom13(null, (get, set) => { const currentCount = get(pendingMutationsCountAtom); const newCount = currentCount + 1; set(pendingMutationsCountAtom, newCount); debug8("Mutation started. Pending count: %d -> %d", currentCount, newCount); if (newCount > 0 && get(syncStatusAtom) !== "syncing") { set(syncStatusAtom, "syncing"); debug8("Status -> syncing"); } }); completeMutationAtom = atom13(null, (get, set, success = true) => { const currentCount = get(pendingMutationsCountAtom); const newCount = Math.max(0, currentCount - 1); set(pendingMutationsCountAtom, newCount); debug8("Mutation completed (success: %s). Pending count: %d -> %d", success, currentCount, newCount); if (success) { set(lastSyncTimeAtom, Date.now()); if (newCount === 0) { set(lastSyncErrorAtom, null); } } if (newCount === 0) { const isOnline = get(isOnlineAtom); const hasError = get(lastSyncErrorAtom) !== null; if (hasError) { set(syncStatusAtom, "error"); debug8("Status -> error"); } else if (!isOnline) { set(syncStatusAtom, "offline"); debug8("Status -> offline"); } else { set(syncStatusAtom, "synced"); debug8("Status -> synced"); } } }); trackMutationErrorAtom = atom13(null, (_get, set, error) => { set(lastSyncErrorAtom, error); debug8("Mutation failed: %s", error); }); setOnlineStatusAtom = atom13(null, (get, set, isOnline) => { set(isOnlineAtom, isOnline); const pendingCount = get(pendingMutationsCountAtom); const hasError = get(lastSyncErrorAtom) !== null; const queueLength = get(mutationQueueAtom).length; if (pendingCount === 0) { if (hasError || queueLength > 0) { set(syncStatusAtom, "error"); } else { set(syncStatusAtom, isOnline ? "synced" : "offline"); } } }); queueMutationAtom = atom13(null, (get, set, mutation) => { const queue = get(mutationQueueAtom); const newMutation = { ...mutation, id: crypto.randomUUID(), timestamp: Date.now(), retryCount: 0, maxRetries: mutation.maxRetries ?? 3 }; const newQueue = [...queue, newMutation]; set(mutationQueueAtom, newQueue); debug8("Queued mutation: %s. Queue size: %d", mutation.type, newQueue.length); if (get(pendingMutationsCountAtom) === 0) { set(syncStatusAtom, "error"); } return newMutation.id; }); dequeueMutationAtom = atom13(null, (get, set, mutationId) => { const queue = get(mutationQueueAtom); const newQueue = queue.filter((m) => m.id !== mutationId); set(mutationQueueAtom, newQueue); debug8("Dequeued mutation: %s. Queue size: %d", mutationId, newQueue.length); if (newQueue.length === 0 && get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) { set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline"); } }); incrementRetryCountAtom = atom13(null, (get, set, mutationId) => { const queue = get(mutationQueueAtom); const newQueue = queue.map((m) => m.id === mutationId ? { ...m, retryCount: m.retryCount + 1 } : m); set(mutationQueueAtom, newQueue); }); getNextQueuedMutationAtom = atom13((get) => { const queue = get(mutationQueueAtom); return queue.find((m) => m.retryCount < m.maxRetries) ?? null; }); clearMutationQueueAtom = atom13(null, (get, set) => { set(mutationQueueAtom, []); debug8("Cleared mutation queue"); if (get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) { set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline"); } }); } }); // src/core/interaction-store.ts import { atom as atom14 } from "jotai"; var inputModeAtom, keyboardInteractionModeAtom, interactionFeedbackAtom, pendingInputResolverAtom, resetInputModeAtom, resetKeyboardInteractionModeAtom, setKeyboardInteractionModeAtom, startPickNodeAtom, startPickNodesAtom, startPickPointAtom, provideInputAtom, updateInteractionFeedbackAtom, isPickingModeAtom, isPickNodeModeAtom; var init_interaction_store = __esm({ "src/core/interaction-store.ts"() { "use strict"; inputModeAtom = atom14({ type: "normal" }); keyboardInteractionModeAtom = atom14("navigate"); interactionFeedbackAtom = atom14(null); pendingInputResolverAtom = atom14(null); resetInputModeAtom = atom14(null, (_get, set) => { set(inputModeAtom, { type: "normal" }); set(interactionFeedbackAtom, null); set(pendingInputResolverAtom, null); }); resetKeyboardInteractionModeAtom = atom14(null, (_get, set) => { set(keyboardInteractionModeAtom, "navigate"); }); setKeyboardInteractionModeAtom = atom14(null, (_get, set, mode) => { set(keyboardInteractionModeAtom, mode); }); startPickNodeAtom = atom14(null, (_get, set, options) => { set(inputModeAtom, { type: "pickNode", ...options }); }); startPickNodesAtom = atom14(null, (_get, set, options) => { set(inputModeAtom, { type: "pickNodes", ...options }); }); startPickPointAtom = atom14(null, (_get, set, options) => { set(inputModeAtom, { type: "pickPoint", ...options }); }); provideInputAtom = atom14(null, (get, set, value) => { set(pendingInputResolverAtom, value); }); updateInteractionFeedbackAtom = atom14(null, (get, set, feedback) => { const current = get(interactionFeedbackAtom); set(interactionFeedbackAtom, { ...current, ...feedback }); }); isPickingModeAtom = atom14((get) => { const mode = get(inputModeAtom); return mode.type !== "normal"; }); isPickNodeModeAtom = atom14((get) => { const mode = get(inputModeAtom); return mode.type === "pickNode" || mode.type === "pickNodes"; }); } }); // src/core/locked-node-store.ts import { atom as atom15 } from "jotai"; var lockedNodeIdAtom, lockedNodeDataAtom, lockedNodePageIndexAtom, lockedNodePageCountAtom, lockNodeAtom, unlockNodeAtom, nextLockedPageAtom, prevLockedPageAtom, goToLockedPageAtom, hasLockedNodeAtom; var init_locked_node_store = __esm({ "src/core/locked-node-store.ts"() { "use strict"; init_graph_derived(); lockedNodeIdAtom = atom15(null); lockedNodeDataAtom = atom15((get) => { const id = get(lockedNodeIdAtom); if (!id) return null; const nodes = get(uiNodesAtom); return nodes.find((n) => n.id === id) || null; }); lockedNodePageIndexAtom = atom15(0); lockedNodePageCountAtom = atom15(1); lockNodeAtom = atom15(null, (_get, set, payload) => { set(lockedNodeIdAtom, payload.nodeId); set(lockedNodePageIndexAtom, 0); }); unlockNodeAtom = atom15(null, (_get, set) => { set(lockedNodeIdAtom, null); }); nextLockedPageAtom = atom15(null, (get, set) => { const current = get(lockedNodePageIndexAtom); const pageCount = get(lockedNodePageCountAtom); set(lockedNodePageIndexAtom, (current + 1) % pageCount); }); prevLockedPageAtom = atom15(null, (get, set) => { const current = get(lockedNodePageIndexAtom); const pageCount = get(lockedNodePageCountAtom); set(lockedNodePageIndexAtom, (current - 1 + pageCount) % pageCount); }); goToLockedPageAtom = atom15(null, (get, set, index) => { const pageCount = get(lockedNodePageCountAtom); if (index >= 0 && index < pageCount) { set(lockedNodePageIndexAtom, index); } }); hasLockedNodeAtom = atom15((get) => get(lockedNodeIdAtom) !== null); } }); // src/core/node-type-registry.tsx import { c as _c } from "react/compiler-runtime"; import React from "react"; import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; function registerNodeType(nodeType, component) { nodeTypeRegistry.set(nodeType, component); } function registerNodeTypes(types) { for (const [nodeType, component] of Object.entries(types)) { nodeTypeRegistry.set(nodeType, component); } } function unregisterNodeType(nodeType) { return nodeTypeRegistry.delete(nodeType); } function getNodeTypeComponent(nodeType) { if (!nodeType) return void 0; return nodeTypeRegistry.get(nodeType); } function hasNodeTypeComponent(nodeType) { if (!nodeType) return false; return nodeTypeRegistry.has(nodeType); } function getRegisteredNodeTypes() { return Array.from(nodeTypeRegistry.keys()); } function clearNodeTypeRegistry() { nodeTypeRegistry.clear(); } var nodeTypeRegistry, FallbackNodeTypeComponent; var init_node_type_registry = __esm({ "src/core/node-type-registry.tsx"() { "use strict"; nodeTypeRegistry = /* @__PURE__ */ new Map(); FallbackNodeTypeComponent = (t0) => { const $ = _c(11); const { nodeData } = t0; let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { padding: "12px", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", color: "#666", fontSize: "12px" }; $[0] = t1; } else { t1 = $[0]; } const t2 = nodeData.dbData.node_type || "none"; let t3; if ($[1] !== t2) { t3 = /* @__PURE__ */ _jsxs("div", { children: ["Unknown type: ", t2] }); $[1] = t2; $[2] = t3; } else { t3 = $[2]; } let t4; if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { marginTop: "4px", opacity: 0.7 }; $[3] = t4; } else { t4 = $[3]; } let t5; if ($[4] !== nodeData.id) { t5 = nodeData.id.substring(0, 8); $[4] = nodeData.id; $[5] = t5; } else { t5 = $[5]; } let t6; if ($[6] !== t5) { t6 = /* @__PURE__ */ _jsx("div", { style: t4, children: t5 }); $[6] = t5; $[7] = t6; } else { t6 = $[7]; } let t7; if ($[8] !== t3 || $[9] !== t6) { t7 = /* @__PURE__ */ _jsxs("div", { style: t1, children: [t3, t6] }); $[8] = t3; $[9] = t6; $[10] = t7; } else { t7 = $[10]; } return t7; }; } }); // src/core/toast-store.ts var toast_store_exports = {}; __export(toast_store_exports, { canvasToastAtom: () => canvasToastAtom, showToastAtom: () => showToastAtom }); import { atom as atom16 } from "jotai"; var canvasToastAtom, showToastAtom; var init_toast_store = __esm({ "src/core/toast-store.ts"() { "use strict"; canvasToastAtom = atom16(null); showToastAtom = atom16(null, (_get, set, message) => { const id = `toast-${Date.now()}`; set(canvasToastAtom, { id, message, timestamp: Date.now() }); setTimeout(() => { set(canvasToastAtom, (current) => current?.id === id ? null : current); }, 2e3); }); } }); // src/core/snap-store.ts import { atom as atom17 } from "jotai"; function snapToGrid(pos, gridSize) { return { x: Math.round(pos.x / gridSize) * gridSize, y: Math.round(pos.y / gridSize) * gridSize }; } function conditionalSnap(pos, gridSize, isActive) { return isActive ? snapToGrid(pos, gridSize) : pos; } function getSnapGuides(pos, gridSize, tolerance = 5) { const snappedX = Math.round(pos.x / gridSize) * gridSize; const snappedY = Math.round(pos.y / gridSize) * gridSize; return { x: Math.abs(pos.x - snappedX) < tolerance ? snappedX : null, y: Math.abs(pos.y - snappedY) < tolerance ? snappedY : null }; } function findAlignmentGuides(dragged, others, tolerance = 5) { const verticals = /* @__PURE__ */ new Set(); const horizontals = /* @__PURE__ */ new Set(); const dragCX = dragged.x + dragged.width / 2; const dragCY = dragged.y + dragged.height / 2; const dragRight = dragged.x + dragged.width; const dragBottom = dragged.y + dragged.height; for (const other of others) { const otherCX = other.x + other.width / 2; const otherCY = other.y + other.height / 2; const otherRight = other.x + other.width; const otherBottom = other.y + other.height; if (Math.abs(dragCX - otherCX) < tolerance) verticals.add(otherCX); if (Math.abs(dragged.x - other.x) < tolerance) verticals.add(other.x); if (Math.abs(dragRight - otherRight) < tolerance) verticals.add(otherRight); if (Math.abs(dragged.x - otherRight) < tolerance) verticals.add(otherRight); if (Math.abs(dragRight - other.x) < tolerance) verticals.add(other.x); if (Math.abs(dragCX - other.x) < tolerance) verticals.add(other.x); if (Math.abs(dragCX - otherRight) < tolerance) verticals.add(otherRight); if (Math.abs(dragCY - otherCY) < tolerance) horizontals.add(otherCY); if (Math.abs(dragged.y - other.y) < tolerance) horizontals.add(other.y); if (Math.abs(dragBottom - otherBottom) < tolerance) horizontals.add(otherBottom); if (Math.abs(dragged.y - otherBottom) < tolerance) horizontals.add(otherBottom); if (Math.abs(dragBottom - other.y) < tolerance) horizontals.add(other.y); if (Math.abs(dragCY - other.y) < tolerance) horizontals.add(other.y); if (Math.abs(dragCY - otherBottom) < tolerance) horizontals.add(otherBottom); } return { verticalGuides: Array.from(verticals), horizontalGuides: Array.from(horizontals) }; } var snapEnabledAtom, snapGridSizeAtom, snapTemporaryDisableAtom, isSnappingActiveAtom, toggleSnapAtom, setGridSizeAtom, snapAlignmentEnabledAtom, toggleAlignmentGuidesAtom, alignmentGuidesAtom, clearAlignmentGuidesAtom; var init_snap_store = __esm({ "src/core/snap-store.ts"() { "use strict"; snapEnabledAtom = atom17(false); snapGridSizeAtom = atom17(20); snapTemporaryDisableAtom = atom17(false); isSnappingActiveAtom = atom17((get) => { return get(snapEnabledAtom) && !get(snapTemporaryDisableAtom); }); toggleSnapAtom = atom17(null, (get, set) => { set(snapEnabledAtom, !get(snapEnabledAtom)); }); setGridSizeAtom = atom17(null, (_get, set, size) => { set(snapGridSizeAtom, Math.max(5, Math.min(200, size))); }); snapAlignmentEnabledAtom = atom17(true); toggleAlignmentGuidesAtom = atom17(null, (get, set) => { set(snapAlignmentEnabledAtom, !get(snapAlignmentEnabledAtom)); }); alignmentGuidesAtom = atom17({ verticalGuides: [], horizontalGuides: [] }); clearAlignmentGuidesAtom = atom17(null, (_get, set) => { set(alignmentGuidesAtom, { verticalGuides: [], horizontalGuides: [] }); }); } }); // src/core/event-types.ts var CanvasEventType, EVENT_TYPE_INFO; var init_event_types = __esm({ "src/core/event-types.ts"() { "use strict"; CanvasEventType = /* @__PURE__ */ (function(CanvasEventType2) { CanvasEventType2["NodeClick"] = "node:click"; CanvasEventType2["NodeDoubleClick"] = "node:double-click"; CanvasEventType2["NodeTripleClick"] = "node:triple-click"; CanvasEventType2["NodeRightClick"] = "node:right-click"; CanvasEventType2["NodeLongPress"] = "node:long-press"; CanvasEventType2["EdgeClick"] = "edge:click"; CanvasEventType2["EdgeDoubleClick"] = "edge:double-click"; CanvasEventType2["EdgeRightClick"] = "edge:right-click"; CanvasEventType2["BackgroundClick"] = "background:click"; CanvasEventType2["BackgroundDoubleClick"] = "background:double-click"; CanvasEventType2["BackgroundRightClick"] = "background:right-click"; CanvasEventType2["BackgroundLongPress"] = "background:long-press"; return CanvasEventType2; })({}); EVENT_TYPE_INFO = { [CanvasEventType.NodeClick]: { type: CanvasEventType.NodeClick, label: "Click Node", description: "Triggered when clicking on a node", category: "node" }, [CanvasEventType.NodeDoubleClick]: { type: CanvasEventType.NodeDoubleClick, label: "Double-click Node", description: "Triggered when double-clicking on a node", category: "node" }, [CanvasEventType.NodeTripleClick]: { type: CanvasEventType.NodeTripleClick, label: "Triple-click Node", description: "Triggered when triple-clicking on a node", category: "node" }, [CanvasEventType.NodeRightClick]: { type: CanvasEventType.NodeRightClick, label: "Right-click Node", description: "Triggered when right-clicking on a node", category: "node" }, [CanvasEventType.NodeLongPress]: { type: CanvasEventType.NodeLongPress, label: "Long-press Node", description: "Triggered when long-pressing on a node (mobile/touch)", category: "node" }, [CanvasEventType.EdgeClick]: { type: CanvasEventType.EdgeClick, label: "Click Edge", description: "Triggered when clicking on an edge", category: "edge" }, [CanvasEventType.EdgeDoubleClick]: { type: CanvasEventType.EdgeDoubleClick, label: "Double-click Edge", description: "Triggered when double-clicking on an edge", category: "edge" }, [CanvasEventType.EdgeRightClick]: { type: CanvasEventType.EdgeRightClick, label: "Right-click Edge", description: "Triggered when right-clicking on an edge", category: "edge" }, [CanvasEventType.BackgroundClick]: { type: CanvasEventType.BackgroundClick, label: "Click Background", description: "Triggered when clicking on the canvas background", category: "background" }, [CanvasEventType.BackgroundDoubleClick]: { type: CanvasEventType.BackgroundDoubleClick, label: "Double-click Background", description: "Triggered when double-clicking on the canvas background", category: "background" }, [CanvasEventType.BackgroundRightClick]: { type: CanvasEventType.BackgroundRightClick, label: "Right-click Background", description: "Triggered when right-clicking on the canvas background", category: "background" }, [CanvasEventType.BackgroundLongPress]: { type: CanvasEventType.BackgroundLongPress, label: "Long-press Background", description: "Triggered when long-pressing on the canvas background (mobile/touch)", category: "background" } }; } }); // src/core/action-types.ts var ActionCategory, BuiltInActionId; var init_action_types = __esm({ "src/core/action-types.ts"() { "use strict"; ActionCategory = /* @__PURE__ */ (function(ActionCategory2) { ActionCategory2["None"] = "none"; ActionCategory2["Selection"] = "selection"; ActionCategory2["Viewport"] = "viewport"; ActionCategory2["Node"] = "node"; ActionCategory2["Layout"] = "layout"; ActionCategory2["History"] = "history"; ActionCategory2["Custom"] = "custom"; return ActionCategory2; })({}); BuiltInActionId = { // None None: "none", // Selection SelectNode: "select-node", SelectEdge: "select-edge", AddToSelection: "add-to-selection", ClearSelection: "clear-selection", DeleteSelected: "delete-selected", // Viewport FitToView: "fit-to-view", FitAllToView: "fit-all-to-view", CenterOnNode: "center-on-node", ResetViewport: "reset-viewport", // Node LockNode: "lock-node", UnlockNode: "unlock-node", ToggleLock: "toggle-lock", OpenContextMenu: "open-context-menu", SplitNode: "split-node", GroupNodes: "group-nodes", MergeNodes: "merge-nodes", // Layout ApplyForceLayout: "apply-force-layout", // History Undo: "undo", Redo: "redo", // Creation CreateNode: "create-node" }; } }); // src/core/settings-state-types.ts var DEFAULT_MAPPINGS; var init_settings_state_types = __esm({ "src/core/settings-state-types.ts"() { "use strict"; init_event_types(); init_action_types(); DEFAULT_MAPPINGS = { [CanvasEventType.NodeClick]: BuiltInActionId.None, [CanvasEventType.NodeDoubleClick]: BuiltInActionId.FitToView, [CanvasEventType.NodeTripleClick]: BuiltInActionId.ToggleLock, [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu, [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge, [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None, [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection, [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.FitAllToView, [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None, [CanvasEventType.BackgroundLongPress]: BuiltInActionId.CreateNode }; } }); // src/core/settings-types.ts var init_settings_types = __esm({ "src/core/settings-types.ts"() { "use strict"; init_event_types(); init_action_types(); init_settings_state_types(); } }); // src/core/actions-node.ts function registerSelectionActions() { registerAction({ id: BuiltInActionId.SelectNode, label: "Select Node", description: "Select this node (replacing current selection)", category: ActionCategory.Selection, icon: "pointer", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.selectNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.SelectEdge, label: "Select Edge", description: "Select this edge", category: ActionCategory.Selection, icon: "git-commit", isBuiltIn: true, handler: (context, helpers) => { if (context.edgeId) { helpers.selectEdge(context.edgeId); } } }); registerAction({ id: BuiltInActionId.AddToSelection, label: "Add to Selection", description: "Add this node to the current selection", category: ActionCategory.Selection, icon: "plus-square", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.addToSelection(context.nodeId); } } }); registerAction({ id: BuiltInActionId.ClearSelection, label: "Clear Selection", description: "Deselect all nodes", category: ActionCategory.Selection, icon: "x-square", isBuiltIn: true, handler: (_context, helpers) => { helpers.clearSelection(); } }); registerAction({ id: BuiltInActionId.DeleteSelected, label: "Delete Selected", description: "Delete all selected nodes", category: ActionCategory.Selection, icon: "trash-2", isBuiltIn: true, handler: async (_context, helpers) => { const selectedIds = helpers.getSelectedNodeIds(); for (const nodeId of selectedIds) { await helpers.deleteNode(nodeId); } } }); } function registerNodeActions() { registerAction({ id: BuiltInActionId.LockNode, label: "Lock Node", description: "Prevent this node from being moved", category: ActionCategory.Node, icon: "lock", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.lockNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.UnlockNode, label: "Unlock Node", description: "Allow this node to be moved", category: ActionCategory.Node, icon: "unlock", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.unlockNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.ToggleLock, label: "Toggle Lock", description: "Toggle whether this node can be moved", category: ActionCategory.Node, icon: "lock", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.toggleLock(context.nodeId); } } }); registerAction({ id: BuiltInActionId.OpenContextMenu, label: "Open Context Menu", description: "Show the context menu for this node", category: ActionCategory.Node, icon: "more-vertical", isBuiltIn: true, handler: (context, helpers) => { if (helpers.openContextMenu) { helpers.openContextMenu(context.screenPosition, context.nodeId); } } }); registerAction({ id: BuiltInActionId.CreateNode, label: "Create Node", description: "Create a new node at this position", category: ActionCategory.Node, icon: "plus", isBuiltIn: true, handler: async (context, helpers) => { if (helpers.createNode) { await helpers.createNode(context.worldPosition); } } }); registerAction({ id: BuiltInActionId.SplitNode, label: "Split Node", description: "Split a node into two separate nodes", category: ActionCategory.Node, icon: "split", isBuiltIn: true, handler: async (context, helpers) => { if (helpers.splitNode && context.nodeId) { await helpers.splitNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.GroupNodes, label: "Group Nodes", description: "Group selected nodes into a parent container", category: ActionCategory.Node, icon: "group", isBuiltIn: true, handler: async (context, helpers) => { if (helpers.groupNodes) { await helpers.groupNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds()); } } }); registerAction({ id: BuiltInActionId.MergeNodes, label: "Merge Nodes", description: "Merge selected nodes into one", category: ActionCategory.Node, icon: "merge", isBuiltIn: true, handler: async (context, helpers) => { if (helpers.mergeNodes) { await helpers.mergeNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds()); } } }); } var init_actions_node = __esm({ "src/core/actions-node.ts"() { "use strict"; init_settings_types(); init_action_registry(); } }); // src/core/actions-viewport.ts function registerViewportActions() { registerAction({ id: BuiltInActionId.FitToView, label: "Fit to View", description: "Zoom and pan to fit this node in view", category: ActionCategory.Viewport, icon: "maximize-2", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.centerOnNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.FitAllToView, label: "Fit All to View", description: "Zoom and pan to fit all nodes in view", category: ActionCategory.Viewport, icon: "maximize", isBuiltIn: true, handler: (_context, helpers) => { helpers.fitToBounds("graph"); } }); registerAction({ id: BuiltInActionId.CenterOnNode, label: "Center on Node", description: "Center the viewport on this node", category: ActionCategory.Viewport, icon: "crosshair", requiresNode: true, isBuiltIn: true, handler: (context, helpers) => { if (context.nodeId) { helpers.centerOnNode(context.nodeId); } } }); registerAction({ id: BuiltInActionId.ResetViewport, label: "Reset Viewport", description: "Reset zoom to 100% and center on origin", category: ActionCategory.Viewport, icon: "home", isBuiltIn: true, handler: (_context, helpers) => { helpers.resetViewport(); } }); } function registerHistoryActions() { registerAction({ id: BuiltInActionId.Undo, label: "Undo", description: "Undo the last action", category: ActionCategory.History, icon: "undo-2", isBuiltIn: true, handler: (_context, helpers) => { if (helpers.canUndo()) { helpers.undo(); } } }); registerAction({ id: BuiltInActionId.Redo, label: "Redo", description: "Redo the last undone action", category: ActionCategory.History, icon: "redo-2", isBuiltIn: true, handler: (_context, helpers) => { if (helpers.canRedo()) { helpers.redo(); } } }); registerAction({ id: BuiltInActionId.ApplyForceLayout, label: "Apply Force Layout", description: "Automatically arrange nodes using force-directed layout", category: ActionCategory.Layout, icon: "layout-grid", isBuiltIn: true, handler: async (_context, helpers) => { await helpers.applyForceLayout(); } }); } var init_actions_viewport = __esm({ "src/core/actions-viewport.ts"() { "use strict"; init_settings_types(); init_action_registry(); } }); // src/core/built-in-actions.ts function registerBuiltInActions() { registerAction({ id: BuiltInActionId.None, label: "None", description: "Do nothing", category: ActionCategory.None, icon: "ban", isBuiltIn: true, handler: () => { } }); registerSelectionActions(); registerNodeActions(); registerViewportActions(); registerHistoryActions(); } var init_built_in_actions = __esm({ "src/core/built-in-actions.ts"() { "use strict"; init_settings_types(); init_action_registry(); init_actions_node(); init_actions_viewport(); } }); // src/core/action-registry.ts function registerAction(action) { actionRegistry.set(action.id, action); } function getAction(id) { return actionRegistry.get(id); } function hasAction(id) { return actionRegistry.has(id); } function getAllActions() { return Array.from(actionRegistry.values()); } function getActionsByCategory(category) { return getAllActions().filter((action) => action.category === category); } function unregisterAction(id) { return actionRegistry.delete(id); } function clearActions() { actionRegistry.clear(); } function getActionsByCategories() { const categoryLabels = { [ActionCategory.None]: "None", [ActionCategory.Selection]: "Selection", [ActionCategory.Viewport]: "Viewport", [ActionCategory.Node]: "Node", [ActionCategory.Layout]: "Layout", [ActionCategory.History]: "History", [ActionCategory.Custom]: "Custom" }; const categoryOrder = [ActionCategory.None, ActionCategory.Selection, ActionCategory.Viewport, ActionCategory.Node, ActionCategory.Layout, ActionCategory.History, ActionCategory.Custom]; return categoryOrder.map((category) => ({ category, label: categoryLabels[category], actions: getActionsByCategory(category) })).filter((group) => group.actions.length > 0); } var actionRegistry; var init_action_registry = __esm({ "src/core/action-registry.ts"() { "use strict"; init_settings_types(); init_built_in_actions(); actionRegistry = /* @__PURE__ */ new Map(); registerBuiltInActions(); } }); // src/core/action-executor.ts async function executeAction(actionId, context, helpers) { if (actionId === BuiltInActionId.None) { return { success: true, actionId }; } const action = getAction(actionId); if (!action) { debug9.warn("Action not found: %s", actionId); return { success: false, actionId, error: new Error(`Action not found: ${actionId}`) }; } if (action.requiresNode && !context.nodeId) { debug9.warn("Action %s requires a node context", actionId); return { success: false, actionId, error: new Error(`Action ${actionId} requires a node context`) }; } try { const result = action.handler(context, helpers); if (result instanceof Promise) { await result; } return { success: true, actionId }; } catch (error) { debug9.error("Error executing action %s: %O", actionId, error); return { success: false, actionId, error: error instanceof Error ? error : new Error(String(error)) }; } } function createActionContext(eventType, screenEvent, worldPosition, options) { return { eventType, nodeId: options?.nodeId, nodeData: options?.nodeData, edgeId: options?.edgeId, edgeData: options?.edgeData, worldPosition, screenPosition: { x: screenEvent.clientX, y: screenEvent.clientY }, modifiers: { shift: false, ctrl: false, alt: false, meta: false } }; } function createActionContextFromReactEvent(eventType, event, worldPosition, options) { return { eventType, nodeId: options?.nodeId, nodeData: options?.nodeData, edgeId: options?.edgeId, edgeData: options?.edgeData, worldPosition, screenPosition: { x: event.clientX, y: event.clientY }, modifiers: { shift: event.shiftKey, ctrl: event.ctrlKey, alt: event.altKey, meta: event.metaKey } }; } function createActionContextFromTouchEvent(eventType, touch, worldPosition, options) { return { eventType, nodeId: options?.nodeId, nodeData: options?.nodeData, edgeId: options?.edgeId, edgeData: options?.edgeData, worldPosition, screenPosition: { x: touch.clientX, y: touch.clientY }, modifiers: { shift: false, ctrl: false, alt: false, meta: false } }; } function buildActionHelpers(store, options = {}) { return { selectNode: (nodeId) => store.set(selectSingleNodeAtom, nodeId), addToSelection: (nodeId) => store.set(addNodesToSelectionAtom, [nodeId]), clearSelection: () => store.set(clearSelectionAtom), getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)), fitToBounds: (mode, padding) => { const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection; store.set(fitToBoundsAtom, { mode: fitMode, padding }); }, centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId), resetViewport: () => store.set(resetViewportAtom), lockNode: (nodeId) => store.set(lockNodeAtom, { nodeId }), unlockNode: (_nodeId) => store.set(unlockNodeAtom), toggleLock: (nodeId) => { const currentLockedId = store.get(lockedNodeIdAtom); if (currentLockedId === nodeId) { store.set(unlockNodeAtom); } else { store.set(lockNodeAtom, { nodeId }); } }, deleteNode: async (nodeId) => { if (options.onDeleteNode) { await options.onDeleteNode(nodeId); } else { debug9.warn("deleteNode called but onDeleteNode callback not provided"); } }, isNodeLocked: (nodeId) => store.get(lockedNodeIdAtom) === nodeId, applyForceLayout: async () => { if (options.onApplyForceLayout) { await options.onApplyForceLayout(); } else { debug9.warn("applyForceLayout called but onApplyForceLayout callback not provided"); } }, undo: () => store.set(undoAtom), redo: () => store.set(redoAtom), canUndo: () => store.get(canUndoAtom), canRedo: () => store.get(canRedoAtom), selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId), clearEdgeSelection: () => store.set(clearEdgeSelectionAtom), openContextMenu: options.onOpenContextMenu, createNode: options.onCreateNode }; } var debug9; var init_action_executor = __esm({ "src/core/action-executor.ts"() { "use strict"; init_action_registry(); init_settings_types(); init_selection_store(); init_viewport_store(); init_locked_node_store(); init_history_store(); init_layout(); init_debug(); debug9 = createDebug("actions"); } }); // src/core/settings-presets.ts function getActionForEvent(mappings, event) { return mappings[event] || BuiltInActionId.None; } var BUILT_IN_PRESETS; var init_settings_presets = __esm({ "src/core/settings-presets.ts"() { "use strict"; init_settings_types(); BUILT_IN_PRESETS = [{ id: "default", name: "Default", description: "Standard canvas interactions", isBuiltIn: true, mappings: DEFAULT_MAPPINGS }, { id: "minimal", name: "Minimal", description: "Only essential selection and context menu actions", isBuiltIn: true, mappings: { [CanvasEventType.NodeClick]: BuiltInActionId.None, [CanvasEventType.NodeDoubleClick]: BuiltInActionId.None, [CanvasEventType.NodeTripleClick]: BuiltInActionId.None, [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu, [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge, [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None, [CanvasEventType.EdgeRightClick]: BuiltInActionId.None, [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection, [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.None, [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None, [CanvasEventType.BackgroundLongPress]: BuiltInActionId.None } }, { id: "power-user", name: "Power User", description: "Quick actions for experienced users", isBuiltIn: true, mappings: { [CanvasEventType.NodeClick]: BuiltInActionId.None, [CanvasEventType.NodeDoubleClick]: BuiltInActionId.ToggleLock, [CanvasEventType.NodeTripleClick]: BuiltInActionId.DeleteSelected, [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.NodeLongPress]: BuiltInActionId.AddToSelection, [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge, [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None, [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection, [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.CreateNode, [CanvasEventType.BackgroundRightClick]: BuiltInActionId.OpenContextMenu, [CanvasEventType.BackgroundLongPress]: BuiltInActionId.ApplyForceLayout } }]; } }); // src/core/settings-store.ts import { atom as atom18 } from "jotai"; import { atomWithStorage } from "jotai/utils"; var debug10, DEFAULT_STATE, canvasSettingsAtom, eventMappingsAtom, activePresetIdAtom, allPresetsAtom, activePresetAtom, isPanelOpenAtom, virtualizationEnabledAtom, hasUnsavedChangesAtom, setEventMappingAtom, applyPresetAtom, saveAsPresetAtom, updatePresetAtom, deletePresetAtom, resetSettingsAtom, togglePanelAtom, setPanelOpenAtom, setVirtualizationEnabledAtom, toggleVirtualizationAtom; var init_settings_store = __esm({ "src/core/settings-store.ts"() { "use strict"; init_settings_types(); init_debug(); init_settings_presets(); init_settings_presets(); debug10 = createDebug("settings"); DEFAULT_STATE = { mappings: DEFAULT_MAPPINGS, activePresetId: "default", customPresets: [], isPanelOpen: false, virtualizationEnabled: true }; canvasSettingsAtom = atomWithStorage("@blinksgg/canvas/settings", DEFAULT_STATE); eventMappingsAtom = atom18((get) => get(canvasSettingsAtom).mappings); activePresetIdAtom = atom18((get) => get(canvasSettingsAtom).activePresetId); allPresetsAtom = atom18((get) => { const state = get(canvasSettingsAtom); return [...BUILT_IN_PRESETS, ...state.customPresets]; }); activePresetAtom = atom18((get) => { const presetId = get(activePresetIdAtom); if (!presetId) return null; const allPresets = get(allPresetsAtom); return allPresets.find((p) => p.id === presetId) || null; }); isPanelOpenAtom = atom18((get) => get(canvasSettingsAtom).isPanelOpen); virtualizationEnabledAtom = atom18((get) => get(canvasSettingsAtom).virtualizationEnabled ?? true); hasUnsavedChangesAtom = atom18((get) => { const state = get(canvasSettingsAtom); const activePreset = get(activePresetAtom); if (!activePreset) return true; const events = Object.values(CanvasEventType); return events.some((event) => state.mappings[event] !== activePreset.mappings[event]); }); setEventMappingAtom = atom18(null, (get, set, { event, actionId }) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, mappings: { ...current.mappings, [event]: actionId }, // Clear active preset since mappings have changed activePresetId: null }); }); applyPresetAtom = atom18(null, (get, set, presetId) => { const allPresets = get(allPresetsAtom); const preset = allPresets.find((p) => p.id === presetId); if (!preset) { debug10.warn("Preset not found: %s", presetId); return; } const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, mappings: { ...preset.mappings }, activePresetId: presetId }); }); saveAsPresetAtom = atom18(null, (get, set, { name, description }) => { const current = get(canvasSettingsAtom); const id = `custom-${Date.now()}`; const newPreset = { id, name, description, mappings: { ...current.mappings }, isBuiltIn: false }; set(canvasSettingsAtom, { ...current, customPresets: [...current.customPresets, newPreset], activePresetId: id }); return id; }); updatePresetAtom = atom18(null, (get, set, presetId) => { const current = get(canvasSettingsAtom); const presetIndex = current.customPresets.findIndex((p) => p.id === presetId); if (presetIndex === -1) { debug10.warn("Cannot update preset: %s (not found or built-in)", presetId); return; } const updatedPresets = [...current.customPresets]; updatedPresets[presetIndex] = { ...updatedPresets[presetIndex], mappings: { ...current.mappings } }; set(canvasSettingsAtom, { ...current, customPresets: updatedPresets, activePresetId: presetId }); }); deletePresetAtom = atom18(null, (get, set, presetId) => { const current = get(canvasSettingsAtom); const newCustomPresets = current.customPresets.filter((p) => p.id !== presetId); if (newCustomPresets.length === current.customPresets.length) { debug10.warn("Cannot delete preset: %s (not found or built-in)", presetId); return; } const newActiveId = current.activePresetId === presetId ? "default" : current.activePresetId; const newMappings = newActiveId === "default" ? DEFAULT_MAPPINGS : current.mappings; set(canvasSettingsAtom, { ...current, customPresets: newCustomPresets, activePresetId: newActiveId, mappings: newMappings }); }); resetSettingsAtom = atom18(null, (get, set) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, mappings: DEFAULT_MAPPINGS, activePresetId: "default" }); }); togglePanelAtom = atom18(null, (get, set) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, isPanelOpen: !current.isPanelOpen }); }); setPanelOpenAtom = atom18(null, (get, set, isOpen) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, isPanelOpen: isOpen }); }); setVirtualizationEnabledAtom = atom18(null, (get, set, enabled) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, virtualizationEnabled: enabled }); }); toggleVirtualizationAtom = atom18(null, (get, set) => { const current = get(canvasSettingsAtom); set(canvasSettingsAtom, { ...current, virtualizationEnabled: !(current.virtualizationEnabled ?? true) }); }); } }); // src/core/canvas-serializer.ts var canvas_serializer_exports = {}; __export(canvas_serializer_exports, { SNAPSHOT_VERSION: () => SNAPSHOT_VERSION, exportGraph: () => exportGraph, importGraph: () => importGraph, validateSnapshot: () => validateSnapshot }); import Graph4 from "graphology"; function exportGraph(store, metadata) { const graph = store.get(graphAtom); const zoom = store.get(zoomAtom); const pan = store.get(panAtom); const collapsed = store.get(collapsedGroupsAtom); const nodes = []; const groups = []; const seenGroupParents = /* @__PURE__ */ new Set(); graph.forEachNode((nodeId, attrs) => { const a = attrs; nodes.push({ id: nodeId, position: { x: a.x, y: a.y }, dimensions: { width: a.width, height: a.height }, size: a.size, color: a.color, zIndex: a.zIndex, label: a.label, parentId: a.parentId, dbData: a.dbData }); if (a.parentId) { const key = `${nodeId}:${a.parentId}`; if (!seenGroupParents.has(key)) { seenGroupParents.add(key); groups.push({ nodeId, parentId: a.parentId, isCollapsed: collapsed.has(a.parentId) }); } } }); const edges = []; graph.forEachEdge((key, attrs, source, target) => { const a = attrs; edges.push({ key, sourceId: source, targetId: target, attributes: { weight: a.weight, type: a.type, color: a.color, label: a.label }, dbData: a.dbData }); }); return { version: SNAPSHOT_VERSION, exportedAt: (/* @__PURE__ */ new Date()).toISOString(), nodes, edges, groups, viewport: { zoom, pan: { ...pan } }, metadata }; } function importGraph(store, snapshot, options = {}) { const { clearExisting = true, offsetPosition, remapIds = false } = options; const idMap = /* @__PURE__ */ new Map(); if (remapIds) { for (const node of snapshot.nodes) { idMap.set(node.id, crypto.randomUUID()); } for (const edge of snapshot.edges) { idMap.set(edge.key, crypto.randomUUID()); } } const remap = (id) => idMap.get(id) ?? id; let graph; if (clearExisting) { graph = new Graph4(graphOptions); } else { graph = store.get(graphAtom); } const ox = offsetPosition?.x ?? 0; const oy = offsetPosition?.y ?? 0; for (const node of snapshot.nodes) { const nodeId = remap(node.id); const parentId = node.parentId ? remap(node.parentId) : void 0; const dbData = remapIds ? { ...node.dbData, id: nodeId } : node.dbData; const attrs = { x: node.position.x + ox, y: node.position.y + oy, width: node.dimensions.width, height: node.dimensions.height, size: node.size, color: node.color, zIndex: node.zIndex, label: node.label, parentId, dbData }; graph.addNode(nodeId, attrs); } for (const edge of snapshot.edges) { const edgeKey = remap(edge.key); const sourceId = remap(edge.sourceId); const targetId = remap(edge.targetId); if (!graph.hasNode(sourceId) || !graph.hasNode(targetId)) continue; const dbData = remapIds ? { ...edge.dbData, id: edgeKey, source_node_id: sourceId, target_node_id: targetId } : edge.dbData; const attrs = { weight: edge.attributes.weight, type: edge.attributes.type, color: edge.attributes.color, label: edge.attributes.label, dbData }; graph.addEdgeWithKey(edgeKey, sourceId, targetId, attrs); } store.set(graphAtom, graph); store.set(graphUpdateVersionAtom, (v) => v + 1); store.set(nodePositionUpdateCounterAtom, (c) => c + 1); const collapsedSet = /* @__PURE__ */ new Set(); for (const group of snapshot.groups) { if (group.isCollapsed) { collapsedSet.add(remap(group.parentId)); } } store.set(collapsedGroupsAtom, collapsedSet); store.set(zoomAtom, snapshot.viewport.zoom); store.set(panAtom, { ...snapshot.viewport.pan }); } function validateSnapshot(data) { const errors = []; if (!data || typeof data !== "object") { return { valid: false, errors: ["Snapshot must be a non-null object"] }; } const obj = data; if (obj.version !== SNAPSHOT_VERSION) { errors.push(`Expected version ${SNAPSHOT_VERSION}, got ${String(obj.version)}`); } if (typeof obj.exportedAt !== "string") { errors.push('Missing or invalid "exportedAt" (expected ISO string)'); } if (!Array.isArray(obj.nodes)) { errors.push('Missing or invalid "nodes" (expected array)'); } else { for (let i = 0; i < obj.nodes.length; i++) { const node = obj.nodes[i]; if (!node || typeof node !== "object") { errors.push(`nodes[${i}]: expected object`); continue; } if (typeof node.id !== "string") errors.push(`nodes[${i}]: missing "id"`); if (!node.position || typeof node.position !== "object") errors.push(`nodes[${i}]: missing "position"`); if (!node.dimensions || typeof node.dimensions !== "object") errors.push(`nodes[${i}]: missing "dimensions"`); if (!node.dbData || typeof node.dbData !== "object") errors.push(`nodes[${i}]: missing "dbData"`); } } if (!Array.isArray(obj.edges)) { errors.push('Missing or invalid "edges" (expected array)'); } else { for (let i = 0; i < obj.edges.length; i++) { const edge = obj.edges[i]; if (!edge || typeof edge !== "object") { errors.push(`edges[${i}]: expected object`); continue; } if (typeof edge.key !== "string") errors.push(`edges[${i}]: missing "key"`); if (typeof edge.sourceId !== "string") errors.push(`edges[${i}]: missing "sourceId"`); if (typeof edge.targetId !== "string") errors.push(`edges[${i}]: missing "targetId"`); if (!edge.dbData || typeof edge.dbData !== "object") errors.push(`edges[${i}]: missing "dbData"`); } } if (!Array.isArray(obj.groups)) { errors.push('Missing or invalid "groups" (expected array)'); } if (!obj.viewport || typeof obj.viewport !== "object") { errors.push('Missing or invalid "viewport" (expected object)'); } else { const vp = obj.viewport; if (typeof vp.zoom !== "number") errors.push('viewport: missing "zoom"'); if (!vp.pan || typeof vp.pan !== "object") errors.push('viewport: missing "pan"'); } return { valid: errors.length === 0, errors }; } var SNAPSHOT_VERSION; var init_canvas_serializer = __esm({ "src/core/canvas-serializer.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_viewport_store(); init_group_store(); SNAPSHOT_VERSION = 1; } }); // src/core/clipboard-store.ts var clipboard_store_exports = {}; __export(clipboard_store_exports, { PASTE_OFFSET: () => PASTE_OFFSET, clearClipboardAtom: () => clearClipboardAtom, clipboardAtom: () => clipboardAtom, clipboardNodeCountAtom: () => clipboardNodeCountAtom, copyToClipboardAtom: () => copyToClipboardAtom, cutToClipboardAtom: () => cutToClipboardAtom, duplicateSelectionAtom: () => duplicateSelectionAtom, hasClipboardContentAtom: () => hasClipboardContentAtom, pasteFromClipboardAtom: () => pasteFromClipboardAtom }); import { atom as atom19 } from "jotai"; function calculateBounds2(nodes) { if (nodes.length === 0) { return { minX: 0, minY: 0, maxX: 0, maxY: 0 }; } let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const node of nodes) { minX = Math.min(minX, node.attrs.x); minY = Math.min(minY, node.attrs.y); maxX = Math.max(maxX, node.attrs.x + node.attrs.width); maxY = Math.max(maxY, node.attrs.y + node.attrs.height); } return { minX, minY, maxX, maxY }; } function generatePasteId(index) { return `paste-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`; } var debug11, PASTE_OFFSET, clipboardAtom, hasClipboardContentAtom, clipboardNodeCountAtom, copyToClipboardAtom, cutToClipboardAtom, pasteFromClipboardAtom, duplicateSelectionAtom, clearClipboardAtom; var init_clipboard_store = __esm({ "src/core/clipboard-store.ts"() { "use strict"; init_graph_store(); init_graph_mutations(); init_graph_mutations_edges(); init_selection_store(); init_history_store(); init_debug(); debug11 = createDebug("clipboard"); PASTE_OFFSET = { x: 50, y: 50 }; clipboardAtom = atom19(null); hasClipboardContentAtom = atom19((get) => get(clipboardAtom) !== null); clipboardNodeCountAtom = atom19((get) => { const clipboard = get(clipboardAtom); return clipboard?.nodes.length ?? 0; }); copyToClipboardAtom = atom19(null, (get, set, nodeIds) => { const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom)); if (selectedIds.length === 0) { debug11("Nothing to copy - no nodes selected"); return; } const graph = get(graphAtom); const selectedSet = new Set(selectedIds); const nodes = []; const edges = []; for (const nodeId of selectedIds) { if (!graph.hasNode(nodeId)) { debug11("Node %s not found in graph, skipping", nodeId); continue; } const attrs = graph.getNodeAttributes(nodeId); nodes.push({ attrs: { ...attrs }, dbData: { ...attrs.dbData } }); } graph.forEachEdge((edgeKey, attrs, source, target) => { if (selectedSet.has(source) && selectedSet.has(target)) { edges.push({ source, target, attrs: { ...attrs }, dbData: { ...attrs.dbData } }); } }); const bounds = calculateBounds2(nodes); const clipboardData = { nodes, edges, bounds, timestamp: Date.now() }; set(clipboardAtom, clipboardData); debug11("Copied %d nodes and %d edges to clipboard", nodes.length, edges.length); }); cutToClipboardAtom = atom19(null, (get, set, nodeIds) => { const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom)); if (selectedIds.length === 0) return; set(copyToClipboardAtom, selectedIds); set(pushHistoryAtom, "Cut nodes"); for (const nodeId of selectedIds) { set(optimisticDeleteNodeAtom, { nodeId }); } set(clearSelectionAtom); debug11("Cut %d nodes \u2014 copied to clipboard and deleted from graph", selectedIds.length); }); pasteFromClipboardAtom = atom19(null, (get, set, offset) => { const clipboard = get(clipboardAtom); if (!clipboard || clipboard.nodes.length === 0) { debug11("Nothing to paste - clipboard empty"); return []; } const pasteOffset = offset ?? PASTE_OFFSET; const graph = get(graphAtom); set(pushHistoryAtom, "Paste nodes"); const idMap = /* @__PURE__ */ new Map(); const newNodeIds = []; for (let i = 0; i < clipboard.nodes.length; i++) { const nodeData = clipboard.nodes[i]; const newId = generatePasteId(i); idMap.set(nodeData.dbData.id, newId); newNodeIds.push(newId); const newDbNode = { ...nodeData.dbData, id: newId, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString(), ui_properties: { ...nodeData.dbData.ui_properties || {}, x: nodeData.attrs.x + pasteOffset.x, y: nodeData.attrs.y + pasteOffset.y } }; debug11("Pasting node %s -> %s at (%d, %d)", nodeData.dbData.id, newId, nodeData.attrs.x + pasteOffset.x, nodeData.attrs.y + pasteOffset.y); set(addNodeToLocalGraphAtom, newDbNode); } for (const edgeData of clipboard.edges) { const newSourceId = idMap.get(edgeData.source); const newTargetId = idMap.get(edgeData.target); if (!newSourceId || !newTargetId) { debug11("Edge %s: source or target not found in id map, skipping", edgeData.dbData.id); continue; } const newEdgeId = generatePasteId(clipboard.edges.indexOf(edgeData) + clipboard.nodes.length); const newDbEdge = { ...edgeData.dbData, id: newEdgeId, source_node_id: newSourceId, target_node_id: newTargetId, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }; debug11("Pasting edge %s -> %s (from %s to %s)", edgeData.dbData.id, newEdgeId, newSourceId, newTargetId); set(addEdgeToLocalGraphAtom, newDbEdge); } set(clearSelectionAtom); set(addNodesToSelectionAtom, newNodeIds); debug11("Pasted %d nodes and %d edges", newNodeIds.length, clipboard.edges.length); return newNodeIds; }); duplicateSelectionAtom = atom19(null, (get, set) => { set(copyToClipboardAtom); return set(pasteFromClipboardAtom); }); clearClipboardAtom = atom19(null, (_get, set) => { set(clipboardAtom, null); debug11("Clipboard cleared"); }); } }); // src/core/spatial-index.ts var SpatialGrid; var init_spatial_index = __esm({ "src/core/spatial-index.ts"() { "use strict"; SpatialGrid = class { constructor(cellSize = 500) { /** cell key → set of node IDs in that cell */ __publicField(this, "cells", /* @__PURE__ */ new Map()); /** node ID → entry data (for update/remove) */ __publicField(this, "entries", /* @__PURE__ */ new Map()); this.cellSize = cellSize; } /** Number of tracked entries */ get size() { return this.entries.size; } cellKey(cx, cy) { return `${cx},${cy}`; } getCellRange(x, y, w, h) { const cs = this.cellSize; return { minCX: Math.floor(x / cs), minCY: Math.floor(y / cs), maxCX: Math.floor((x + w) / cs), maxCY: Math.floor((y + h) / cs) }; } /** * Insert a node into the index. * If the node already exists, it is updated. */ insert(id, x, y, width, height) { if (this.entries.has(id)) { this.update(id, x, y, width, height); return; } const entry = { id, x, y, width, height }; this.entries.set(id, entry); const { minCX, minCY, maxCX, maxCY } = this.getCellRange(x, y, width, height); for (let cx = minCX; cx <= maxCX; cx++) { for (let cy = minCY; cy <= maxCY; cy++) { const key = this.cellKey(cx, cy); let cell = this.cells.get(key); if (!cell) { cell = /* @__PURE__ */ new Set(); this.cells.set(key, cell); } cell.add(id); } } } /** * Update a node's position/dimensions. */ update(id, x, y, width, height) { const prev = this.entries.get(id); if (!prev) { this.insert(id, x, y, width, height); return; } const prevRange = this.getCellRange(prev.x, prev.y, prev.width, prev.height); const newRange = this.getCellRange(x, y, width, height); prev.x = x; prev.y = y; prev.width = width; prev.height = height; if (prevRange.minCX === newRange.minCX && prevRange.minCY === newRange.minCY && prevRange.maxCX === newRange.maxCX && prevRange.maxCY === newRange.maxCY) { return; } for (let cx = prevRange.minCX; cx <= prevRange.maxCX; cx++) { for (let cy = prevRange.minCY; cy <= prevRange.maxCY; cy++) { const key = this.cellKey(cx, cy); const cell = this.cells.get(key); if (cell) { cell.delete(id); if (cell.size === 0) this.cells.delete(key); } } } for (let cx = newRange.minCX; cx <= newRange.maxCX; cx++) { for (let cy = newRange.minCY; cy <= newRange.maxCY; cy++) { const key = this.cellKey(cx, cy); let cell = this.cells.get(key); if (!cell) { cell = /* @__PURE__ */ new Set(); this.cells.set(key, cell); } cell.add(id); } } } /** * Remove a node from the index. */ remove(id) { const entry = this.entries.get(id); if (!entry) return; const { minCX, minCY, maxCX, maxCY } = this.getCellRange(entry.x, entry.y, entry.width, entry.height); for (let cx = minCX; cx <= maxCX; cx++) { for (let cy = minCY; cy <= maxCY; cy++) { const key = this.cellKey(cx, cy); const cell = this.cells.get(key); if (cell) { cell.delete(id); if (cell.size === 0) this.cells.delete(key); } } } this.entries.delete(id); } /** * Query all node IDs whose bounding box overlaps the given bounds. * Returns a Set for O(1) membership checks. */ query(bounds) { const result = /* @__PURE__ */ new Set(); const { minCX, minCY, maxCX, maxCY } = this.getCellRange(bounds.minX, bounds.minY, bounds.maxX - bounds.minX, bounds.maxY - bounds.minY); for (let cx = minCX; cx <= maxCX; cx++) { for (let cy = minCY; cy <= maxCY; cy++) { const cell = this.cells.get(this.cellKey(cx, cy)); if (!cell) continue; for (const id of cell) { if (result.has(id)) continue; const entry = this.entries.get(id); const entryRight = entry.x + entry.width; const entryBottom = entry.y + entry.height; if (entry.x <= bounds.maxX && entryRight >= bounds.minX && entry.y <= bounds.maxY && entryBottom >= bounds.minY) { result.add(id); } } } } return result; } /** * Clear all entries. */ clear() { this.cells.clear(); this.entries.clear(); } /** * Check if a node is tracked. */ has(id) { return this.entries.has(id); } }; } }); // src/core/virtualization-store.ts import { atom as atom20 } from "jotai"; var VIRTUALIZATION_BUFFER, spatialIndexAtom, visibleBoundsAtom, visibleNodeKeysAtom, visibleEdgeKeysAtom, virtualizationMetricsAtom; var init_virtualization_store = __esm({ "src/core/virtualization-store.ts"() { "use strict"; init_graph_store(); init_graph_position(); init_graph_derived(); init_viewport_store(); init_settings_store(); init_group_store(); init_spatial_index(); init_perf(); init_settings_store(); VIRTUALIZATION_BUFFER = 200; spatialIndexAtom = atom20((get) => { get(graphUpdateVersionAtom); get(nodePositionUpdateCounterAtom); const graph = get(graphAtom); const grid = new SpatialGrid(500); graph.forEachNode((nodeId, attrs) => { const a = attrs; grid.insert(nodeId, a.x, a.y, a.width || 200, a.height || 100); }); return grid; }); visibleBoundsAtom = atom20((get) => { const viewport = get(viewportRectAtom); const pan = get(panAtom); const zoom = get(zoomAtom); if (!viewport || zoom === 0) { return null; } const buffer = VIRTUALIZATION_BUFFER; return { minX: (-buffer - pan.x) / zoom, minY: (-buffer - pan.y) / zoom, maxX: (viewport.width + buffer - pan.x) / zoom, maxY: (viewport.height + buffer - pan.y) / zoom }; }); visibleNodeKeysAtom = atom20((get) => { const end = canvasMark("virtualization-cull"); const enabled = get(virtualizationEnabledAtom); const allKeys = get(nodeKeysAtom); if (!enabled) { end(); return allKeys; } const bounds = get(visibleBoundsAtom); if (!bounds) { end(); return allKeys; } const grid = get(spatialIndexAtom); const visibleSet = grid.query(bounds); const result = allKeys.filter((k) => visibleSet.has(k)); end(); return result; }); visibleEdgeKeysAtom = atom20((get) => { const enabled = get(virtualizationEnabledAtom); const allEdgeKeys = get(edgeKeysAtom); const edgeCreation = get(edgeCreationAtom); const remap = get(collapsedEdgeRemapAtom); const tempEdgeKey = edgeCreation.isCreating ? "temp-creating-edge" : null; get(graphUpdateVersionAtom); const graph = get(graphAtom); const filteredEdges = allEdgeKeys.filter((edgeKey) => { const source = graph.source(edgeKey); const target = graph.target(edgeKey); const effectiveSource = remap.get(source) ?? source; const effectiveTarget = remap.get(target) ?? target; if (effectiveSource === effectiveTarget) return false; return true; }); if (!enabled) { return tempEdgeKey ? [...filteredEdges, tempEdgeKey] : filteredEdges; } const visibleNodeKeys = get(visibleNodeKeysAtom); const visibleNodeSet = new Set(visibleNodeKeys); const visibleEdges = filteredEdges.filter((edgeKey) => { const source = graph.source(edgeKey); const target = graph.target(edgeKey); const effectiveSource = remap.get(source) ?? source; const effectiveTarget = remap.get(target) ?? target; return visibleNodeSet.has(effectiveSource) && visibleNodeSet.has(effectiveTarget); }); return tempEdgeKey ? [...visibleEdges, tempEdgeKey] : visibleEdges; }); virtualizationMetricsAtom = atom20((get) => { const enabled = get(virtualizationEnabledAtom); const totalNodes = get(nodeKeysAtom).length; const totalEdges = get(edgeKeysAtom).length; const visibleNodes = get(visibleNodeKeysAtom).length; const visibleEdges = get(visibleEdgeKeysAtom).length; const bounds = get(visibleBoundsAtom); return { enabled, totalNodes, totalEdges, visibleNodes, visibleEdges, culledNodes: totalNodes - visibleNodes, culledEdges: totalEdges - visibleEdges, bounds }; }); } }); // src/core/canvas-api.ts function createCanvasAPI(store, options = {}) { const helpers = buildActionHelpers(store, options); const api = { // Selection selectNode: (id) => store.set(selectSingleNodeAtom, id), addToSelection: (ids) => store.set(addNodesToSelectionAtom, ids), clearSelection: () => store.set(clearSelectionAtom), getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)), selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId), clearEdgeSelection: () => store.set(clearEdgeSelectionAtom), getSelectedEdgeId: () => store.get(selectedEdgeIdAtom), // Viewport getZoom: () => store.get(zoomAtom), setZoom: (zoom) => store.set(zoomAtom, zoom), getPan: () => store.get(panAtom), setPan: (pan) => store.set(panAtom, pan), resetViewport: () => store.set(resetViewportAtom), fitToBounds: (mode, padding) => { const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection; store.set(fitToBoundsAtom, { mode: fitMode, padding }); }, centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId), // Graph addNode: (node) => store.set(addNodeToLocalGraphAtom, node), removeNode: (nodeId) => store.set(optimisticDeleteNodeAtom, { nodeId }), addEdge: (edge) => store.set(addEdgeToLocalGraphAtom, edge), removeEdge: (edgeKey) => store.set(optimisticDeleteEdgeAtom, { edgeKey }), getNodeKeys: () => store.get(nodeKeysAtom), getEdgeKeys: () => store.get(edgeKeysAtom), getNodeAttributes: (id) => { const graph = store.get(graphAtom); return graph.hasNode(id) ? graph.getNodeAttributes(id) : void 0; }, // History undo: () => store.set(undoAtom), redo: () => store.set(redoAtom), canUndo: () => store.get(canUndoAtom), canRedo: () => store.get(canRedoAtom), recordSnapshot: (label) => store.set(pushHistoryAtom, label), clearHistory: () => store.set(clearHistoryAtom), // Clipboard copy: () => store.set(copyToClipboardAtom), cut: () => store.set(cutToClipboardAtom), paste: () => store.set(pasteFromClipboardAtom), duplicate: () => store.set(duplicateSelectionAtom), hasClipboardContent: () => store.get(clipboardAtom) !== null, // Snap isSnapEnabled: () => store.get(snapEnabledAtom), toggleSnap: () => store.set(toggleSnapAtom), getSnapGridSize: () => store.get(snapGridSizeAtom), // Virtualization isVirtualizationEnabled: () => store.get(virtualizationEnabledAtom), getVisibleNodeKeys: () => store.get(visibleNodeKeysAtom), getVisibleEdgeKeys: () => store.get(visibleEdgeKeysAtom), // Actions executeAction: (actionId, context) => executeAction(actionId, context, helpers), executeEventAction: (event, context) => { const mappings = store.get(eventMappingsAtom); const actionId = getActionForEvent(mappings, event); return executeAction(actionId, context, helpers); }, // Serialization exportSnapshot: (metadata) => exportGraph(store, metadata), importSnapshot: (snapshot, options2) => importGraph(store, snapshot, options2), validateSnapshot: (data) => validateSnapshot(data) }; return api; } var init_canvas_api = __esm({ "src/core/canvas-api.ts"() { "use strict"; init_action_executor(); init_canvas_serializer(); init_settings_store(); init_selection_store(); init_viewport_store(); init_graph_store(); init_graph_derived(); init_graph_mutations(); init_graph_mutations_edges(); init_history_store(); init_clipboard_store(); init_snap_store(); init_virtualization_store(); init_layout(); } }); // src/core/port-types.ts function calculatePortPosition(nodeX, nodeY, nodeWidth, nodeHeight, port) { switch (port.side) { case "left": return { x: nodeX, y: nodeY + nodeHeight * port.position }; case "right": return { x: nodeX + nodeWidth, y: nodeY + nodeHeight * port.position }; case "top": return { x: nodeX + nodeWidth * port.position, y: nodeY }; case "bottom": return { x: nodeX + nodeWidth * port.position, y: nodeY + nodeHeight }; } } function getNodePorts(ports) { if (ports && ports.length > 0) { return ports; } return [DEFAULT_PORT]; } function canPortAcceptConnection(port, currentConnections, isSource) { if (isSource && port.type === "input") { return false; } if (!isSource && port.type === "output") { return false; } if (port.maxConnections !== void 0 && currentConnections >= port.maxConnections) { return false; } return true; } function arePortsCompatible(sourcePort, targetPort) { if (sourcePort.type === "input") { return false; } if (targetPort.type === "output") { return false; } return true; } var DEFAULT_PORT; var init_port_types = __esm({ "src/core/port-types.ts"() { "use strict"; DEFAULT_PORT = { id: "default", type: "bidirectional", side: "right", position: 0.5 }; } }); // src/core/input-classifier.ts function classifyPointer(e) { const source = pointerTypeToSource(e.pointerType); return { source, pointerId: e.pointerId, pressure: e.pressure, tiltX: e.tiltX, tiltY: e.tiltY, isPrimary: e.isPrimary, rawPointerType: e.pointerType }; } function pointerTypeToSource(pointerType) { switch (pointerType) { case "pen": return "pencil"; case "touch": return "finger"; case "mouse": return "mouse"; default: return "mouse"; } } function detectInputCapabilities() { if (typeof window === "undefined") { return { hasTouch: false, hasStylus: false, hasMouse: true, hasCoarsePointer: false }; } const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0; const supportsMatchMedia = typeof window.matchMedia === "function"; const hasCoarsePointer = supportsMatchMedia ? window.matchMedia("(pointer: coarse)").matches : false; const hasFinePointer = supportsMatchMedia ? window.matchMedia("(pointer: fine)").matches : true; const hasMouse = hasFinePointer || !hasTouch; return { hasTouch, hasStylus: false, // Set to true on first pen event hasMouse, hasCoarsePointer }; } function getGestureThresholds(source) { switch (source) { case "finger": return { dragThreshold: 10, tapThreshold: 10, longPressDuration: 600, longPressMoveLimit: 10 }; case "pencil": return { dragThreshold: 2, tapThreshold: 3, longPressDuration: 500, longPressMoveLimit: 5 }; case "mouse": return { dragThreshold: 3, tapThreshold: 5, longPressDuration: 0, // Mouse uses right-click instead longPressMoveLimit: 0 }; } } function getHitTargetSize(source) { return HIT_TARGET_SIZES[source]; } var HIT_TARGET_SIZES; var init_input_classifier = __esm({ "src/core/input-classifier.ts"() { "use strict"; HIT_TARGET_SIZES = { /** Minimum touch target (Apple HIG: 44pt) */ finger: 44, /** Stylus target (precise, can use smaller targets) */ pencil: 24, /** Mouse target (hover-discoverable, smallest) */ mouse: 16 }; } }); // src/core/input-store.ts import { atom as atom21 } from "jotai"; var activePointersAtom, primaryInputSourceAtom, inputCapabilitiesAtom, isStylusActiveAtom, isMultiTouchAtom, fingerCountAtom, isTouchDeviceAtom, pointerDownAtom, pointerUpAtom, clearPointersAtom; var init_input_store = __esm({ "src/core/input-store.ts"() { "use strict"; init_input_classifier(); activePointersAtom = atom21(/* @__PURE__ */ new Map()); primaryInputSourceAtom = atom21("mouse"); inputCapabilitiesAtom = atom21(detectInputCapabilities()); isStylusActiveAtom = atom21((get) => { const pointers = get(activePointersAtom); for (const [, pointer] of pointers) { if (pointer.source === "pencil") return true; } return false; }); isMultiTouchAtom = atom21((get) => { const pointers = get(activePointersAtom); let fingerCount = 0; for (const [, pointer] of pointers) { if (pointer.source === "finger") fingerCount++; } return fingerCount > 1; }); fingerCountAtom = atom21((get) => { const pointers = get(activePointersAtom); let count = 0; for (const [, pointer] of pointers) { if (pointer.source === "finger") count++; } return count; }); isTouchDeviceAtom = atom21((get) => { const caps = get(inputCapabilitiesAtom); return caps.hasTouch; }); pointerDownAtom = atom21(null, (get, set, pointer) => { const pointers = new Map(get(activePointersAtom)); pointers.set(pointer.pointerId, pointer); set(activePointersAtom, pointers); set(primaryInputSourceAtom, pointer.source); if (pointer.source === "pencil") { const caps = get(inputCapabilitiesAtom); if (!caps.hasStylus) { set(inputCapabilitiesAtom, { ...caps, hasStylus: true }); } } }); pointerUpAtom = atom21(null, (get, set, pointerId) => { const pointers = new Map(get(activePointersAtom)); pointers.delete(pointerId); set(activePointersAtom, pointers); }); clearPointersAtom = atom21(null, (_get, set) => { set(activePointersAtom, /* @__PURE__ */ new Map()); }); } }); // src/core/selection-path-store.ts import { atom as atom22 } from "jotai"; function pointInPolygon(px, py, polygon) { let inside = false; const n = polygon.length; for (let i = 0, j = n - 1; i < n; j = i++) { const xi = polygon[i].x; const yi = polygon[i].y; const xj = polygon[j].x; const yj = polygon[j].y; if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) { inside = !inside; } } return inside; } var selectionPathAtom, isSelectingAtom, startSelectionAtom, updateSelectionAtom, cancelSelectionAtom, endSelectionAtom, selectionRectAtom; var init_selection_path_store = __esm({ "src/core/selection-path-store.ts"() { "use strict"; init_graph_derived(); init_selection_store(); selectionPathAtom = atom22(null); isSelectingAtom = atom22((get) => get(selectionPathAtom) !== null); startSelectionAtom = atom22(null, (_get, set, { type, point }) => { set(selectionPathAtom, { type, points: [point] }); }); updateSelectionAtom = atom22(null, (get, set, point) => { const current = get(selectionPathAtom); if (!current) return; if (current.type === "rect") { set(selectionPathAtom, { ...current, points: [current.points[0], point] }); } else { set(selectionPathAtom, { ...current, points: [...current.points, point] }); } }); cancelSelectionAtom = atom22(null, (_get, set) => { set(selectionPathAtom, null); }); endSelectionAtom = atom22(null, (get, set) => { const path = get(selectionPathAtom); if (!path || path.points.length < 2) { set(selectionPathAtom, null); return; } const nodes = get(uiNodesAtom); const selectedIds = []; if (path.type === "rect") { const [p1, p2] = [path.points[0], path.points[path.points.length - 1]]; const minX = Math.min(p1.x, p2.x); const maxX = Math.max(p1.x, p2.x); const minY = Math.min(p1.y, p2.y); const maxY = Math.max(p1.y, p2.y); for (const node of nodes) { const nodeRight = node.position.x + (node.width ?? 200); const nodeBottom = node.position.y + (node.height ?? 100); if (node.position.x < maxX && nodeRight > minX && node.position.y < maxY && nodeBottom > minY) { selectedIds.push(node.id); } } } else { const polygon = path.points; for (const node of nodes) { const cx = node.position.x + (node.width ?? 200) / 2; const cy = node.position.y + (node.height ?? 100) / 2; if (pointInPolygon(cx, cy, polygon)) { selectedIds.push(node.id); } } } set(selectedNodeIdsAtom, new Set(selectedIds)); set(selectionPathAtom, null); }); selectionRectAtom = atom22((get) => { const path = get(selectionPathAtom); if (!path || path.type !== "rect" || path.points.length < 2) return null; const [p1, p2] = [path.points[0], path.points[path.points.length - 1]]; return { x: Math.min(p1.x, p2.x), y: Math.min(p1.y, p2.y), width: Math.abs(p2.x - p1.x), height: Math.abs(p2.y - p1.y) }; }); } }); // src/core/search-store.ts var search_store_exports = {}; __export(search_store_exports, { clearSearchAtom: () => clearSearchAtom, fuzzyMatch: () => fuzzyMatch, highlightedSearchIndexAtom: () => highlightedSearchIndexAtom, highlightedSearchNodeIdAtom: () => highlightedSearchNodeIdAtom, isFilterActiveAtom: () => isFilterActiveAtom, nextSearchResultAtom: () => nextSearchResultAtom, prevSearchResultAtom: () => prevSearchResultAtom, searchEdgeResultCountAtom: () => searchEdgeResultCountAtom, searchEdgeResultsAtom: () => searchEdgeResultsAtom, searchQueryAtom: () => searchQueryAtom, searchResultCountAtom: () => searchResultCountAtom, searchResultsArrayAtom: () => searchResultsArrayAtom, searchResultsAtom: () => searchResultsAtom, searchTotalResultCountAtom: () => searchTotalResultCountAtom, setSearchQueryAtom: () => setSearchQueryAtom }); import { atom as atom23 } from "jotai"; function fuzzyMatch(query, ...haystacks) { const tokens = query.toLowerCase().split(/\s+/).filter(Boolean); if (tokens.length === 0) return false; const combined = haystacks.join(" ").toLowerCase(); return tokens.every((token) => combined.includes(token)); } var searchQueryAtom, setSearchQueryAtom, clearSearchAtom, searchResultsAtom, searchResultsArrayAtom, searchResultCountAtom, searchEdgeResultsAtom, searchEdgeResultCountAtom, isFilterActiveAtom, searchTotalResultCountAtom, highlightedSearchIndexAtom, nextSearchResultAtom, prevSearchResultAtom, highlightedSearchNodeIdAtom; var init_search_store = __esm({ "src/core/search-store.ts"() { "use strict"; init_graph_derived(); init_graph_store(); init_viewport_store(); init_selection_store(); searchQueryAtom = atom23(""); setSearchQueryAtom = atom23(null, (_get, set, query) => { set(searchQueryAtom, query); set(highlightedSearchIndexAtom, 0); }); clearSearchAtom = atom23(null, (_get, set) => { set(searchQueryAtom, ""); set(highlightedSearchIndexAtom, 0); }); searchResultsAtom = atom23((get) => { const query = get(searchQueryAtom).trim(); if (!query) return /* @__PURE__ */ new Set(); const nodes = get(uiNodesAtom); const matches = /* @__PURE__ */ new Set(); for (const node of nodes) { if (fuzzyMatch(query, node.label || "", node.dbData.node_type || "", node.id)) { matches.add(node.id); } } return matches; }); searchResultsArrayAtom = atom23((get) => { return Array.from(get(searchResultsAtom)); }); searchResultCountAtom = atom23((get) => { return get(searchResultsAtom).size; }); searchEdgeResultsAtom = atom23((get) => { const query = get(searchQueryAtom).trim(); if (!query) return /* @__PURE__ */ new Set(); get(graphUpdateVersionAtom); const graph = get(graphAtom); const matches = /* @__PURE__ */ new Set(); graph.forEachEdge((edgeKey, attrs) => { const label = attrs.label || ""; const edgeType = attrs.dbData?.edge_type || ""; if (fuzzyMatch(query, label, edgeType, edgeKey)) { matches.add(edgeKey); } }); return matches; }); searchEdgeResultCountAtom = atom23((get) => { return get(searchEdgeResultsAtom).size; }); isFilterActiveAtom = atom23((get) => { return get(searchQueryAtom).trim().length > 0; }); searchTotalResultCountAtom = atom23((get) => { return get(searchResultCountAtom) + get(searchEdgeResultCountAtom); }); highlightedSearchIndexAtom = atom23(0); nextSearchResultAtom = atom23(null, (get, set) => { const results = get(searchResultsArrayAtom); if (results.length === 0) return; const currentIndex = get(highlightedSearchIndexAtom); const nextIndex = (currentIndex + 1) % results.length; set(highlightedSearchIndexAtom, nextIndex); const nodeId = results[nextIndex]; set(centerOnNodeAtom, nodeId); set(selectSingleNodeAtom, nodeId); }); prevSearchResultAtom = atom23(null, (get, set) => { const results = get(searchResultsArrayAtom); if (results.length === 0) return; const currentIndex = get(highlightedSearchIndexAtom); const prevIndex = (currentIndex - 1 + results.length) % results.length; set(highlightedSearchIndexAtom, prevIndex); const nodeId = results[prevIndex]; set(centerOnNodeAtom, nodeId); set(selectSingleNodeAtom, nodeId); }); highlightedSearchNodeIdAtom = atom23((get) => { const results = get(searchResultsArrayAtom); if (results.length === 0) return null; const index = get(highlightedSearchIndexAtom); return results[index] ?? null; }); } }); // src/core/gesture-resolver.ts var init_gesture_resolver = __esm({ "src/core/gesture-resolver.ts"() { "use strict"; } }); // src/core/gesture-rules-defaults.ts function formatRuleLabel(pattern) { const parts = []; if (pattern.modifiers) { const mods = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).map((k) => k.charAt(0).toUpperCase() + k.slice(1)); if (mods.length) parts.push(mods.join("+")); } if (pattern.button !== void 0 && pattern.button !== 0) { parts.push(BUTTON_LABELS[pattern.button]); } if (pattern.source) { parts.push(SOURCE_LABELS[pattern.source]); } if (pattern.gesture) { parts.push(GESTURE_LABELS[pattern.gesture] ?? pattern.gesture); } if (pattern.target) { parts.push("on " + (TARGET_LABELS[pattern.target] ?? pattern.target)); } if (parts.length === 0) return "Any gesture"; if (pattern.modifiers) { const modCount = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).length; if (modCount > 0 && parts.length > modCount) { const modPart = parts.slice(0, 1).join(""); const rest = parts.slice(1).join(" ").toLowerCase(); return `${modPart} + ${rest}`; } } return parts.join(" "); } function mergeRules(defaults, overrides) { const overrideMap = new Map(overrides.map((r) => [r.id, r])); const result = []; for (const rule of defaults) { const override = overrideMap.get(rule.id); if (override) { result.push(override); overrideMap.delete(rule.id); } else { result.push(rule); } } for (const rule of overrideMap.values()) { result.push(rule); } return result; } var MODIFIER_KEYS, SOURCE_LABELS, GESTURE_LABELS, TARGET_LABELS, BUTTON_LABELS, DEFAULT_GESTURE_RULES; var init_gesture_rules_defaults = __esm({ "src/core/gesture-rules-defaults.ts"() { "use strict"; MODIFIER_KEYS = ["shift", "ctrl", "alt", "meta"]; SOURCE_LABELS = { mouse: "Mouse", pencil: "Pencil", finger: "Touch" }; GESTURE_LABELS = { tap: "Tap", "double-tap": "Double-tap", "triple-tap": "Triple-tap", drag: "Drag", "long-press": "Long-press", "right-click": "Right-click", pinch: "Pinch", scroll: "Scroll" }; TARGET_LABELS = { node: "node", edge: "edge", port: "port", "resize-handle": "resize handle", background: "background" }; BUTTON_LABELS = { 0: "Left", 1: "Middle", 2: "Right" }; DEFAULT_GESTURE_RULES = [ // ── Tap gestures ────────────────────────────────────────────── { id: "tap-node", pattern: { gesture: "tap", target: "node" }, actionId: "select-node" }, { id: "tap-edge", pattern: { gesture: "tap", target: "edge" }, actionId: "select-edge" }, { id: "tap-port", pattern: { gesture: "tap", target: "port" }, actionId: "select-node" }, { id: "tap-bg", pattern: { gesture: "tap", target: "background" }, actionId: "clear-selection" }, // ── Double-tap ──────────────────────────────────────────────── { id: "dtap-node", pattern: { gesture: "double-tap", target: "node" }, actionId: "fit-to-view" }, { id: "dtap-bg", pattern: { gesture: "double-tap", target: "background" }, actionId: "fit-all-to-view" }, // ── Triple-tap ──────────────────────────────────────────────── { id: "ttap-node", pattern: { gesture: "triple-tap", target: "node" }, actionId: "toggle-lock" }, // ── Left-button drag ────────────────────────────────────────── { id: "drag-node", pattern: { gesture: "drag", target: "node" }, actionId: "move-node" }, { id: "drag-port", pattern: { gesture: "drag", target: "port" }, actionId: "create-edge" }, { id: "drag-bg-finger", pattern: { gesture: "drag", target: "background", source: "finger" }, actionId: "pan" }, { id: "drag-bg-mouse", pattern: { gesture: "drag", target: "background", source: "mouse" }, actionId: "pan" }, { id: "drag-bg-pencil", pattern: { gesture: "drag", target: "background", source: "pencil" }, actionId: "lasso-select" }, // ── Shift+drag overrides ────────────────────────────────────── { id: "shift-drag-bg", pattern: { gesture: "drag", target: "background", modifiers: { shift: true } }, actionId: "rect-select" }, // ── Right-click tap (context menu) ──────────────────────────── { id: "rc-node", pattern: { gesture: "tap", target: "node", button: 2 }, actionId: "open-context-menu" }, { id: "rc-edge", pattern: { gesture: "tap", target: "edge", button: 2 }, actionId: "open-context-menu" }, { id: "rc-bg", pattern: { gesture: "tap", target: "background", button: 2 }, actionId: "open-context-menu" }, // ── Long-press ──────────────────────────────────────────────── { id: "lp-node", pattern: { gesture: "long-press", target: "node" }, actionId: "open-context-menu" }, { id: "lp-bg-finger", pattern: { gesture: "long-press", target: "background", source: "finger" }, actionId: "create-node" }, // ── Right-button drag (defaults to none — consumers override) ─ { id: "rdrag-node", pattern: { gesture: "drag", target: "node", button: 2 }, actionId: "none" }, { id: "rdrag-bg", pattern: { gesture: "drag", target: "background", button: 2 }, actionId: "none" }, // ── Middle-button drag (defaults to none) ───────────────────── { id: "mdrag-node", pattern: { gesture: "drag", target: "node", button: 1 }, actionId: "none" }, { id: "mdrag-bg", pattern: { gesture: "drag", target: "background", button: 1 }, actionId: "none" }, // ── Zoom ────────────────────────────────────────────────────── { id: "pinch-bg", pattern: { gesture: "pinch", target: "background" }, actionId: "zoom" }, { id: "scroll-any", pattern: { gesture: "scroll" }, actionId: "zoom" }, // ── Split ───────────────────────────────────────────────────── { id: "pinch-node", pattern: { gesture: "pinch", target: "node" }, actionId: "split-node" } ]; } }); // src/core/gesture-rules.ts function matchSpecificity(pattern, desc) { let score = 0; if (pattern.gesture !== void 0) { if (pattern.gesture !== desc.gesture) return -1; score += 32; } if (pattern.target !== void 0) { if (pattern.target !== desc.target) return -1; score += 16; } if (pattern.source !== void 0) { if (pattern.source !== desc.source) return -1; score += 4; } if (pattern.button !== void 0) { if (pattern.button !== (desc.button ?? 0)) return -1; score += 2; } if (pattern.modifiers !== void 0) { const dm = desc.modifiers ?? {}; for (const key of MODIFIER_KEYS2) { const required = pattern.modifiers[key]; if (required === void 0) continue; const actual = dm[key] ?? false; if (required !== actual) return -1; score += 8; } } return score; } function resolveGesture(desc, rules, options) { const palmRejection = options?.palmRejection !== false; if (palmRejection && desc.isStylusActive && desc.source === "finger") { if (desc.gesture === "tap" || desc.gesture === "long-press" || desc.gesture === "double-tap" || desc.gesture === "triple-tap") { return { actionId: "none", rule: PALM_REJECTION_RULE, score: Infinity }; } if (desc.gesture === "drag" && desc.target !== "background") { return resolveGesture({ ...desc, target: "background", isStylusActive: false }, rules, { palmRejection: false }); } } let best = null; for (const rule of rules) { const specificity2 = matchSpecificity(rule.pattern, desc); if (specificity2 < 0) continue; const effectiveScore = specificity2 * 1e3 + (rule.priority ?? 0); if (!best || effectiveScore > best.score) { best = { actionId: rule.actionId, rule, score: effectiveScore }; } } return best; } function buildRuleIndex(rules) { const buckets = /* @__PURE__ */ new Map(); const wildcardRules = []; for (const rule of rules) { const key = rule.pattern.gesture; if (key === void 0) { wildcardRules.push(rule); } else { const bucket = buckets.get(key); if (bucket) { bucket.push(rule); } else { buckets.set(key, [rule]); } } } const index = /* @__PURE__ */ new Map(); if (wildcardRules.length > 0) { for (const [key, bucket] of buckets) { index.set(key, bucket.concat(wildcardRules)); } index.set("__wildcard__", wildcardRules); } else { for (const [key, bucket] of buckets) { index.set(key, bucket); } } return index; } function resolveGestureIndexed(desc, index, options) { const rules = index.get(desc.gesture) ?? index.get("__wildcard__") ?? []; return resolveGesture(desc, rules, options); } var MODIFIER_KEYS2, PALM_REJECTION_RULE; var init_gesture_rules = __esm({ "src/core/gesture-rules.ts"() { "use strict"; init_gesture_rules_defaults(); MODIFIER_KEYS2 = ["shift", "ctrl", "alt", "meta"]; PALM_REJECTION_RULE = { id: "__palm-rejection__", pattern: {}, actionId: "none", label: "Palm rejection" }; } }); // src/core/gesture-rule-store.ts import { atom as atom24 } from "jotai"; import { atomWithStorage as atomWithStorage2 } from "jotai/utils"; var DEFAULT_RULE_STATE, gestureRuleSettingsAtom, consumerGestureRulesAtom, gestureRulesAtom, gestureRuleIndexAtom, palmRejectionEnabledAtom, addGestureRuleAtom, removeGestureRuleAtom, updateGestureRuleAtom, resetGestureRulesAtom; var init_gesture_rule_store = __esm({ "src/core/gesture-rule-store.ts"() { "use strict"; init_gesture_rules(); DEFAULT_RULE_STATE = { customRules: [], palmRejection: true }; gestureRuleSettingsAtom = atomWithStorage2("canvas-gesture-rules", DEFAULT_RULE_STATE); consumerGestureRulesAtom = atom24([]); gestureRulesAtom = atom24((get) => { const settings = get(gestureRuleSettingsAtom); const consumerRules = get(consumerGestureRulesAtom); let rules = mergeRules(DEFAULT_GESTURE_RULES, settings.customRules); if (consumerRules.length > 0) { rules = mergeRules(rules, consumerRules); } return rules; }); gestureRuleIndexAtom = atom24((get) => { return buildRuleIndex(get(gestureRulesAtom)); }); palmRejectionEnabledAtom = atom24((get) => get(gestureRuleSettingsAtom).palmRejection, (get, set, enabled) => { const current = get(gestureRuleSettingsAtom); set(gestureRuleSettingsAtom, { ...current, palmRejection: enabled }); }); addGestureRuleAtom = atom24(null, (get, set, rule) => { const current = get(gestureRuleSettingsAtom); const existing = current.customRules.findIndex((r) => r.id === rule.id); const newRules = [...current.customRules]; if (existing >= 0) { newRules[existing] = rule; } else { newRules.push(rule); } set(gestureRuleSettingsAtom, { ...current, customRules: newRules }); }); removeGestureRuleAtom = atom24(null, (get, set, ruleId) => { const current = get(gestureRuleSettingsAtom); set(gestureRuleSettingsAtom, { ...current, customRules: current.customRules.filter((r) => r.id !== ruleId) }); }); updateGestureRuleAtom = atom24(null, (get, set, { id, updates }) => { const current = get(gestureRuleSettingsAtom); const index = current.customRules.findIndex((r) => r.id === id); if (index < 0) return; const newRules = [...current.customRules]; newRules[index] = { ...newRules[index], ...updates }; set(gestureRuleSettingsAtom, { ...current, customRules: newRules }); }); resetGestureRulesAtom = atom24(null, (get, set) => { const current = get(gestureRuleSettingsAtom); set(gestureRuleSettingsAtom, { ...current, customRules: [] }); }); } }); // src/core/external-keyboard-store.ts import { atom as atom25 } from "jotai"; var hasExternalKeyboardAtom, watchExternalKeyboardAtom; var init_external_keyboard_store = __esm({ "src/core/external-keyboard-store.ts"() { "use strict"; hasExternalKeyboardAtom = atom25(false); watchExternalKeyboardAtom = atom25(null, (get, set) => { if (typeof window === "undefined") return; const handler = (e) => { if (e.key && e.key.length === 1 || ["Tab", "Escape", "Enter", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) { set(hasExternalKeyboardAtom, true); window.removeEventListener("keydown", handler); } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }); } }); // src/core/plugin-types.ts var PluginError; var init_plugin_types = __esm({ "src/core/plugin-types.ts"() { "use strict"; PluginError = class extends Error { constructor(message, pluginId, code) { super(`[Plugin "${pluginId}"] ${message}`); this.pluginId = pluginId; this.code = code; this.name = "PluginError"; } }; } }); // src/gestures/types.ts function isKeyInputEvent(event) { return event.kind === "key"; } function isPointerGestureEvent(event) { return event.kind !== "key"; } var NO_MODIFIERS, NO_HELD_KEYS; var init_types2 = __esm({ "src/gestures/types.ts"() { "use strict"; NO_MODIFIERS = Object.freeze({ shift: false, ctrl: false, alt: false, meta: false }); NO_HELD_KEYS = Object.freeze({ byKey: Object.freeze({}), byCode: Object.freeze({}) }); } }); // src/gestures/dispatcher.ts function registerAction2(actionId, handler) { handlers.set(actionId, handler); } function unregisterAction2(actionId) { handlers.delete(actionId); } function getHandler(actionId) { return handlers.get(actionId); } function clearHandlers() { handlers.clear(); } function dispatch(event, resolution) { if (resolution.actionId === "none") return true; const handler = handlers.get(resolution.actionId); if (!handler) return false; if (typeof handler === "function") { if (isKeyInputEvent(event) && event.phase === "down" || !isKeyInputEvent(event) && (event.phase === "start" || event.phase === "instant")) { handler(event); } return true; } routePhase(handler, event.phase, event); return true; } function routePhase(handler, phase, event) { if (isKeyInputEvent(event)) { routeKeyPhase(handler, phase, event); return; } switch (phase) { case "start": handler.onStart?.(event); break; case "move": handler.onMove?.(event); break; case "end": handler.onEnd?.(event); break; case "instant": handler.onInstant?.(event); break; case "cancel": handler.onCancel?.(event); break; } } function routeKeyPhase(handler, phase, event) { switch (phase) { case "down": handler.onDown?.(event); break; case "up": handler.onUp?.(event); break; } } var handlers; var init_dispatcher = __esm({ "src/gestures/dispatcher.ts"() { "use strict"; init_types2(); handlers = /* @__PURE__ */ new Map(); } }); // src/commands/registry.ts function registerCommand(command) { commandRegistry.register(command); } var CommandRegistry, commandRegistry; var init_registry = __esm({ "src/commands/registry.ts"() { "use strict"; CommandRegistry = class { constructor() { __publicField(this, "commands", /* @__PURE__ */ new Map()); __publicField(this, "aliases", /* @__PURE__ */ new Map()); } // alias -> command name /** * Register a command with the registry. * @param command The command definition to register * @throws Error if command name or alias already exists */ register(command) { if (this.commands.has(command.name)) { throw new Error(`Command "${command.name}" is already registered`); } this.commands.set(command.name, command); if (command.aliases) { for (const alias of command.aliases) { if (this.aliases.has(alias)) { throw new Error(`Alias "${alias}" is already registered for command "${this.aliases.get(alias)}"`); } if (this.commands.has(alias)) { throw new Error(`Alias "${alias}" conflicts with existing command name`); } this.aliases.set(alias, command.name); } } } /** * Unregister a command by name. * @param name The command name to remove */ unregister(name) { const command = this.commands.get(name); if (command) { if (command.aliases) { for (const alias of command.aliases) { this.aliases.delete(alias); } } this.commands.delete(name); } } /** * Get a command by name or alias. * @param nameOrAlias Command name or alias * @returns The command definition or undefined if not found */ get(nameOrAlias) { const direct = this.commands.get(nameOrAlias); if (direct) return direct; const commandName = this.aliases.get(nameOrAlias); if (commandName) { return this.commands.get(commandName); } return void 0; } /** * Check if a command exists by name or alias. * @param nameOrAlias Command name or alias */ has(nameOrAlias) { return this.commands.has(nameOrAlias) || this.aliases.has(nameOrAlias); } /** * Search for commands matching a query. * Searches command names, aliases, and descriptions. * @param query Search query (case-insensitive) * @returns Array of matching commands, sorted by relevance */ search(query) { if (!query.trim()) { return this.all(); } const lowerQuery = query.toLowerCase().trim(); const results = []; const commands = Array.from(this.commands.values()); for (const command of commands) { let score = 0; if (command.name.toLowerCase() === lowerQuery) { score = 100; } else if (command.name.toLowerCase().startsWith(lowerQuery)) { score = 80; } else if (command.name.toLowerCase().includes(lowerQuery)) { score = 60; } else if (command.aliases?.some((a) => a.toLowerCase() === lowerQuery)) { score = 90; } else if (command.aliases?.some((a) => a.toLowerCase().startsWith(lowerQuery))) { score = 70; } else if (command.aliases?.some((a) => a.toLowerCase().includes(lowerQuery))) { score = 50; } else if (command.description.toLowerCase().includes(lowerQuery)) { score = 30; } if (score > 0) { results.push({ command, score }); } } return results.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name)).map((r) => r.command); } /** * Get all registered commands. * @returns Array of all commands, sorted alphabetically by name */ all() { return Array.from(this.commands.values()).sort((a, b) => a.name.localeCompare(b.name)); } /** * Get commands by category. * @param category The category to filter by * @returns Array of commands in the category */ byCategory(category) { return this.all().filter((cmd) => cmd.category === category); } /** * Get all available categories. * @returns Array of unique categories */ categories() { const categories = /* @__PURE__ */ new Set(); const commands = Array.from(this.commands.values()); for (const command of commands) { categories.add(command.category); } return Array.from(categories).sort(); } /** * Get the count of registered commands. */ get size() { return this.commands.size; } /** * Clear all registered commands. * Useful for testing. */ clear() { this.commands.clear(); this.aliases.clear(); } /** * Get a serializable list of commands for API responses. */ toJSON() { return this.all().map((cmd) => ({ name: cmd.name, aliases: cmd.aliases || [], description: cmd.description, category: cmd.category, inputs: cmd.inputs.map((input) => ({ name: input.name, type: input.type, prompt: input.prompt, required: input.required !== false })) })); } }; commandRegistry = new CommandRegistry(); } }); // src/utils/edge-path-calculators.ts function getEdgePathCalculator(type) { switch (type) { case "bezier": return bezierHorizontal; case "bezier-vertical": return bezierVertical; case "bezier-smart": return bezierSmart; case "straight": return straight; case "step": return stepHorizontal; case "step-vertical": return stepVertical; case "step-smart": return stepSmart; case "smooth-step": return smoothStep; default: return bezierHorizontal; } } var bezierHorizontal, bezierVertical, bezierSmart, straight, stepHorizontal, stepVertical, stepSmart, smoothStep, defaultEdgePathCalculator; var init_edge_path_calculators = __esm({ "src/utils/edge-path-calculators.ts"() { "use strict"; bezierHorizontal = ({ x1, y1, x2, y2 }) => { const dist = Math.abs(x2 - x1); const offset = Math.max(dist * 0.5, 50); const cp1x = x1 + offset; const cp1y = y1; const cp2x = x2 - offset; const cp2y = y2; const path = `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`; const labelX = (x1 + x2) / 2; const labelY = (y1 + y2) / 2; return { path, labelX, labelY }; }; bezierVertical = ({ x1, y1, x2, y2 }) => { const dist = Math.abs(y2 - y1); const offset = Math.max(dist * 0.5, 50); const cp1x = x1; const cp1y = y1 + offset; const cp2x = x2; const cp2y = y2 - offset; const path = `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`; const labelX = (x1 + x2) / 2; const labelY = (y1 + y2) / 2; return { path, labelX, labelY }; }; bezierSmart = (input) => { const { x1, y1, x2, y2 } = input; const dx = Math.abs(x2 - x1); const dy = Math.abs(y2 - y1); return dx > dy ? bezierHorizontal(input) : bezierVertical(input); }; straight = ({ x1, y1, x2, y2 }) => { const path = `M ${x1} ${y1} L ${x2} ${y2}`; const labelX = (x1 + x2) / 2; const labelY = (y1 + y2) / 2; return { path, labelX, labelY }; }; stepHorizontal = ({ x1, y1, x2, y2 }) => { const midX = (x1 + x2) / 2; const path = `M ${x1} ${y1} L ${midX} ${y1} L ${midX} ${y2} L ${x2} ${y2}`; const labelX = midX; const labelY = (y1 + y2) / 2; return { path, labelX, labelY }; }; stepVertical = ({ x1, y1, x2, y2 }) => { const midY = (y1 + y2) / 2; const path = `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`; const labelX = (x1 + x2) / 2; const labelY = midY; return { path, labelX, labelY }; }; stepSmart = (input) => { const { x1, y1, x2, y2 } = input; const dx = Math.abs(x2 - x1); const dy = Math.abs(y2 - y1); return dx > dy ? stepHorizontal(input) : stepVertical(input); }; smoothStep = ({ x1, y1, x2, y2 }) => { const midX = (x1 + x2) / 2; const radius = Math.min(20, Math.abs(x2 - x1) / 4, Math.abs(y2 - y1) / 2); if (radius < 5 || Math.abs(y2 - y1) < radius * 2) { return stepHorizontal({ x1, y1, x2, y2, sourceWidth: 0, sourceHeight: 0, targetWidth: 0, targetHeight: 0 }); } const yDir = y2 > y1 ? 1 : -1; const path = ` M ${x1} ${y1} L ${midX - radius} ${y1} Q ${midX} ${y1}, ${midX} ${y1 + radius * yDir} L ${midX} ${y2 - radius * yDir} Q ${midX} ${y2}, ${midX + radius} ${y2} L ${x2} ${y2} `.replace(/\s+/g, " ").trim(); const labelX = midX; const labelY = (y1 + y2) / 2; return { path, labelX, labelY }; }; defaultEdgePathCalculator = bezierHorizontal; } }); // src/utils/edge-path-registry.ts function registerEdgePathCalculator(name, calculator) { customCalculators.set(name, calculator); } function unregisterEdgePathCalculator(name) { return customCalculators.delete(name); } function resolveEdgePathCalculator(name) { return customCalculators.get(name) ?? getEdgePathCalculator(name); } function hasCustomEdgePathCalculator(name) { return customCalculators.has(name); } function getCustomEdgePathCalculatorNames() { return Array.from(customCalculators.keys()); } function clearCustomEdgePathCalculators() { customCalculators.clear(); } var customCalculators; var init_edge_path_registry = __esm({ "src/utils/edge-path-registry.ts"() { "use strict"; init_edge_path_calculators(); customCalculators = /* @__PURE__ */ new Map(); } }); // src/core/plugin-registry.ts function registerPlugin(plugin) { debug12("Registering plugin: %s", plugin.id); if (plugins.has(plugin.id)) { throw new PluginError("Plugin is already registered", plugin.id, "ALREADY_REGISTERED"); } if (plugin.dependencies) { for (const depId of plugin.dependencies) { if (!plugins.has(depId)) { throw new PluginError(`Missing dependency: "${depId}"`, plugin.id, "MISSING_DEPENDENCY"); } } } detectConflicts(plugin); const cleanups = []; try { if (plugin.nodeTypes) { const nodeTypeNames = Object.keys(plugin.nodeTypes); registerNodeTypes(plugin.nodeTypes); cleanups.push(() => { for (const name of nodeTypeNames) { unregisterNodeType(name); } }); } if (plugin.edgePathCalculators) { for (const [name, calc] of Object.entries(plugin.edgePathCalculators)) { registerEdgePathCalculator(name, calc); cleanups.push(() => unregisterEdgePathCalculator(name)); } } if (plugin.actionHandlers) { for (const [actionId, handler] of Object.entries(plugin.actionHandlers)) { registerAction2(actionId, handler); cleanups.push(() => unregisterAction2(actionId)); } } if (plugin.commands) { for (const cmd of plugin.commands) { commandRegistry.register(cmd); cleanups.push(() => commandRegistry.unregister(cmd.name)); } } if (plugin.actions) { for (const action of plugin.actions) { registerAction(action); cleanups.push(() => unregisterAction(action.id)); } } let lifecycleCleanup = null; if (plugin.onRegister) { const ctx = makePluginContext(plugin.id); try { const result = plugin.onRegister(ctx); if (typeof result === "function") { lifecycleCleanup = result; } } catch (err) { for (const cleanup of cleanups.reverse()) { try { cleanup(); } catch { } } throw new PluginError(`onRegister failed: ${err instanceof Error ? err.message : String(err)}`, plugin.id, "LIFECYCLE_ERROR"); } } plugins.set(plugin.id, { plugin, cleanup: () => { for (const cleanup of cleanups.reverse()) { try { cleanup(); } catch { } } if (lifecycleCleanup) { try { lifecycleCleanup(); } catch { } } }, registeredAt: Date.now() }); debug12("Plugin registered: %s (%d node types, %d commands, %d actions)", plugin.id, Object.keys(plugin.nodeTypes ?? {}).length, plugin.commands?.length ?? 0, plugin.actions?.length ?? 0); } catch (err) { if (err instanceof PluginError) throw err; for (const cleanup of cleanups.reverse()) { try { cleanup(); } catch { } } throw err; } } function unregisterPlugin(pluginId) { const registration = plugins.get(pluginId); if (!registration) { throw new PluginError("Plugin is not registered", pluginId, "NOT_FOUND"); } for (const [otherId, other] of plugins) { if (other.plugin.dependencies?.includes(pluginId)) { throw new PluginError(`Cannot unregister: plugin "${otherId}" depends on it`, pluginId, "CONFLICT"); } } if (registration.cleanup) { registration.cleanup(); } plugins.delete(pluginId); debug12("Plugin unregistered: %s", pluginId); } function getPlugin(id) { return plugins.get(id)?.plugin; } function hasPlugin(id) { return plugins.has(id); } function getAllPlugins() { return Array.from(plugins.values()).map((r) => r.plugin); } function getPluginIds() { return Array.from(plugins.keys()); } function getPluginGestureContexts() { const contexts = []; for (const registration of plugins.values()) { if (registration.plugin.gestureContexts) { contexts.push(...registration.plugin.gestureContexts); } } return contexts; } function clearPlugins() { const ids = Array.from(plugins.keys()).reverse(); for (const id of ids) { const reg = plugins.get(id); if (reg?.cleanup) { try { reg.cleanup(); } catch { } } plugins.delete(id); } debug12("All plugins cleared"); } function detectConflicts(plugin) { if (plugin.commands) { for (const cmd of plugin.commands) { if (commandRegistry.has(cmd.name)) { throw new PluginError(`Command "${cmd.name}" is already registered`, plugin.id, "CONFLICT"); } } } if (plugin.edgePathCalculators) { for (const name of Object.keys(plugin.edgePathCalculators)) { for (const [otherId, other] of plugins) { if (other.plugin.edgePathCalculators?.[name]) { throw new PluginError(`Edge path calculator "${name}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT"); } } } } if (plugin.nodeTypes) { for (const nodeType of Object.keys(plugin.nodeTypes)) { for (const [otherId, other] of plugins) { if (other.plugin.nodeTypes?.[nodeType]) { throw new PluginError(`Node type "${nodeType}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT"); } } } } if (plugin.actionHandlers) { for (const actionId of Object.keys(plugin.actionHandlers)) { for (const [otherId, other] of plugins) { if (other.plugin.actionHandlers?.[actionId]) { throw new PluginError(`Action handler "${actionId}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT"); } } } } } function makePluginContext(pluginId) { return { pluginId, getPlugin, hasPlugin }; } var debug12, plugins; var init_plugin_registry = __esm({ "src/core/plugin-registry.ts"() { "use strict"; init_plugin_types(); init_node_type_registry(); init_action_registry(); init_dispatcher(); init_registry(); init_edge_path_registry(); init_debug(); debug12 = createDebug("plugins"); plugins = /* @__PURE__ */ new Map(); } }); // src/core/index.ts var core_exports = {}; __export(core_exports, { ActionCategory: () => ActionCategory, BUILT_IN_PRESETS: () => BUILT_IN_PRESETS, BuiltInActionId: () => BuiltInActionId, CanvasEventType: () => CanvasEventType, DEFAULT_GESTURE_RULES: () => DEFAULT_GESTURE_RULES, DEFAULT_MAPPINGS: () => DEFAULT_MAPPINGS, DEFAULT_PORT: () => DEFAULT_PORT, EDGE_ANIMATION_DURATION: () => EDGE_ANIMATION_DURATION, EVENT_TYPE_INFO: () => EVENT_TYPE_INFO, FallbackNodeTypeComponent: () => FallbackNodeTypeComponent, HIT_TARGET_SIZES: () => HIT_TARGET_SIZES, PASTE_OFFSET: () => PASTE_OFFSET, PluginError: () => PluginError, SNAPSHOT_VERSION: () => SNAPSHOT_VERSION, SpatialGrid: () => SpatialGrid, VIRTUALIZATION_BUFFER: () => VIRTUALIZATION_BUFFER, ZOOM_EXIT_THRESHOLD: () => ZOOM_EXIT_THRESHOLD, ZOOM_TRANSITION_THRESHOLD: () => ZOOM_TRANSITION_THRESHOLD, activePointersAtom: () => activePointersAtom, activePresetAtom: () => activePresetAtom, activePresetIdAtom: () => activePresetIdAtom, addGestureRuleAtom: () => addGestureRuleAtom, addNodeToLocalGraphAtom: () => addNodeToLocalGraphAtom, addNodesToSelectionAtom: () => addNodesToSelectionAtom, alignmentGuidesAtom: () => alignmentGuidesAtom, allPresetsAtom: () => allPresetsAtom, animateFitToBoundsAtom: () => animateFitToBoundsAtom, animateZoomToNodeAtom: () => animateZoomToNodeAtom, applyDelta: () => applyDelta, applyPresetAtom: () => applyPresetAtom, arePortsCompatible: () => arePortsCompatible, autoResizeGroupAtom: () => autoResizeGroupAtom, buildActionHelpers: () => buildActionHelpers, buildRuleIndex: () => buildRuleIndex, calculatePortPosition: () => calculatePortPosition, canPortAcceptConnection: () => canPortAcceptConnection, canRedoAtom: () => canRedoAtom, canUndoAtom: () => canUndoAtom, cancelSelectionAtom: () => cancelSelectionAtom, canvasMark: () => canvasMark, canvasSettingsAtom: () => canvasSettingsAtom, canvasToastAtom: () => canvasToastAtom, canvasWrap: () => canvasWrap, centerOnNodeAtom: () => centerOnNodeAtom, classifyPointer: () => classifyPointer, cleanupAllNodePositionsAtom: () => cleanupAllNodePositionsAtom, cleanupNodePositionAtom: () => cleanupNodePositionAtom, clearActions: () => clearActions, clearAlignmentGuidesAtom: () => clearAlignmentGuidesAtom, clearClipboardAtom: () => clearClipboardAtom, clearEdgeSelectionAtom: () => clearEdgeSelectionAtom, clearGraphOnSwitchAtom: () => clearGraphOnSwitchAtom, clearHistoryAtom: () => clearHistoryAtom, clearMutationQueueAtom: () => clearMutationQueueAtom, clearNodeTypeRegistry: () => clearNodeTypeRegistry, clearPlugins: () => clearPlugins, clearPointersAtom: () => clearPointersAtom, clearSearchAtom: () => clearSearchAtom, clearSelectionAtom: () => clearSelectionAtom, clipboardAtom: () => clipboardAtom, clipboardNodeCountAtom: () => clipboardNodeCountAtom, collapseGroupAtom: () => collapseGroupAtom, collapsedEdgeRemapAtom: () => collapsedEdgeRemapAtom, collapsedGroupsAtom: () => collapsedGroupsAtom, completeMutationAtom: () => completeMutationAtom, conditionalSnap: () => conditionalSnap, consumerGestureRulesAtom: () => consumerGestureRulesAtom, copyToClipboardAtom: () => copyToClipboardAtom, createActionContext: () => createActionContext, createActionContextFromReactEvent: () => createActionContextFromReactEvent, createActionContextFromTouchEvent: () => createActionContextFromTouchEvent, createCanvasAPI: () => createCanvasAPI, createSnapshot: () => createSnapshot, currentGraphIdAtom: () => currentGraphIdAtom, cutToClipboardAtom: () => cutToClipboardAtom, deletePresetAtom: () => deletePresetAtom, departingEdgesAtom: () => departingEdgesAtom, dequeueMutationAtom: () => dequeueMutationAtom, detectInputCapabilities: () => detectInputCapabilities, draggingNodeIdAtom: () => draggingNodeIdAtom, dropTargetNodeIdAtom: () => dropTargetNodeIdAtom, duplicateSelectionAtom: () => duplicateSelectionAtom, edgeCreationAtom: () => edgeCreationAtom, edgeFamilyAtom: () => edgeFamilyAtom, edgeKeysAtom: () => edgeKeysAtom, edgeKeysWithTempEdgeAtom: () => edgeKeysWithTempEdgeAtom, editingEdgeLabelAtom: () => editingEdgeLabelAtom, endNodeDragAtom: () => endNodeDragAtom, endSelectionAtom: () => endSelectionAtom, eventMappingsAtom: () => eventMappingsAtom, executeAction: () => executeAction, expandGroupAtom: () => expandGroupAtom, exportGraph: () => exportGraph, findAlignmentGuides: () => findAlignmentGuides, fingerCountAtom: () => fingerCountAtom, fitToBoundsAtom: () => fitToBoundsAtom, focusedNodeIdAtom: () => focusedNodeIdAtom, formatRuleLabel: () => formatRuleLabel, fuzzyMatch: () => fuzzyMatch, gestureRuleIndexAtom: () => gestureRuleIndexAtom, gestureRuleSettingsAtom: () => gestureRuleSettingsAtom, gestureRulesAtom: () => gestureRulesAtom, getAction: () => getAction, getActionForEvent: () => getActionForEvent, getActionsByCategories: () => getActionsByCategories, getActionsByCategory: () => getActionsByCategory, getAllActions: () => getAllActions, getAllPlugins: () => getAllPlugins, getGestureThresholds: () => getGestureThresholds, getHitTargetSize: () => getHitTargetSize, getNextQueuedMutationAtom: () => getNextQueuedMutationAtom, getNodeDescendants: () => getNodeDescendants, getNodePorts: () => getNodePorts, getNodeTypeComponent: () => getNodeTypeComponent, getPlugin: () => getPlugin, getPluginGestureContexts: () => getPluginGestureContexts, getPluginIds: () => getPluginIds, getRegisteredNodeTypes: () => getRegisteredNodeTypes, getSnapGuides: () => getSnapGuides, goToLockedPageAtom: () => goToLockedPageAtom, graphAtom: () => graphAtom, graphOptions: () => graphOptions, graphUpdateVersionAtom: () => graphUpdateVersionAtom, groupChildCountAtom: () => groupChildCountAtom, groupSelectedNodesAtom: () => groupSelectedNodesAtom, handleNodePointerDownSelectionAtom: () => handleNodePointerDownSelectionAtom, hasAction: () => hasAction, hasClipboardContentAtom: () => hasClipboardContentAtom, hasExternalKeyboardAtom: () => hasExternalKeyboardAtom, hasFocusedNodeAtom: () => hasFocusedNodeAtom, hasLockedNodeAtom: () => hasLockedNodeAtom, hasNodeTypeComponent: () => hasNodeTypeComponent, hasPlugin: () => hasPlugin, hasSelectionAtom: () => hasSelectionAtom, hasUnsavedChangesAtom: () => hasUnsavedChangesAtom, highestZIndexAtom: () => highestZIndexAtom, highlightedSearchIndexAtom: () => highlightedSearchIndexAtom, highlightedSearchNodeIdAtom: () => highlightedSearchNodeIdAtom, historyLabelsAtom: () => historyLabelsAtom, historyStateAtom: () => historyStateAtom, importGraph: () => importGraph, incrementRetryCountAtom: () => incrementRetryCountAtom, inputCapabilitiesAtom: () => inputCapabilitiesAtom, inputModeAtom: () => inputModeAtom, interactionFeedbackAtom: () => interactionFeedbackAtom, invertDelta: () => invertDelta, isFilterActiveAtom: () => isFilterActiveAtom, isGroupNodeAtom: () => isGroupNodeAtom, isMultiTouchAtom: () => isMultiTouchAtom, isNodeCollapsed: () => isNodeCollapsed, isOnlineAtom: () => isOnlineAtom, isPanelOpenAtom: () => isPanelOpenAtom, isPickNodeModeAtom: () => isPickNodeModeAtom, isPickingModeAtom: () => isPickingModeAtom, isSelectingAtom: () => isSelectingAtom, isSnappingActiveAtom: () => isSnappingActiveAtom, isStylusActiveAtom: () => isStylusActiveAtom, isTouchDeviceAtom: () => isTouchDeviceAtom, isZoomTransitioningAtom: () => isZoomTransitioningAtom, keyboardInteractionModeAtom: () => keyboardInteractionModeAtom, lastSyncErrorAtom: () => lastSyncErrorAtom, lastSyncTimeAtom: () => lastSyncTimeAtom, loadGraphFromDbAtom: () => loadGraphFromDbAtom, lockNodeAtom: () => lockNodeAtom, lockedNodeDataAtom: () => lockedNodeDataAtom, lockedNodeIdAtom: () => lockedNodeIdAtom, lockedNodePageCountAtom: () => lockedNodePageCountAtom, lockedNodePageIndexAtom: () => lockedNodePageIndexAtom, matchSpecificity: () => matchSpecificity, mergeNodesAtom: () => mergeNodesAtom, mergeRules: () => mergeRules, moveNodesToGroupAtom: () => moveNodesToGroupAtom, mutationQueueAtom: () => mutationQueueAtom, nestNodesOnDropAtom: () => nestNodesOnDropAtom, nextLockedPageAtom: () => nextLockedPageAtom, nextSearchResultAtom: () => nextSearchResultAtom, nodeChildrenAtom: () => nodeChildrenAtom, nodeFamilyAtom: () => nodeFamilyAtom, nodeKeysAtom: () => nodeKeysAtom, nodeParentAtom: () => nodeParentAtom, nodePositionAtomFamily: () => nodePositionAtomFamily, nodePositionUpdateCounterAtom: () => nodePositionUpdateCounterAtom, optimisticDeleteEdgeAtom: () => optimisticDeleteEdgeAtom, optimisticDeleteNodeAtom: () => optimisticDeleteNodeAtom, palmRejectionEnabledAtom: () => palmRejectionEnabledAtom, panAtom: () => panAtom, pasteFromClipboardAtom: () => pasteFromClipboardAtom, pendingInputResolverAtom: () => pendingInputResolverAtom, pendingMutationsCountAtom: () => pendingMutationsCountAtom, perfEnabledAtom: () => perfEnabledAtom, pointInPolygon: () => pointInPolygon, pointerDownAtom: () => pointerDownAtom, pointerUpAtom: () => pointerUpAtom, preDragNodeAttributesAtom: () => preDragNodeAttributesAtom, prefersReducedMotionAtom: () => prefersReducedMotionAtom, prevLockedPageAtom: () => prevLockedPageAtom, prevSearchResultAtom: () => prevSearchResultAtom, primaryInputSourceAtom: () => primaryInputSourceAtom, provideInputAtom: () => provideInputAtom, pushDeltaAtom: () => pushDeltaAtom, pushHistoryAtom: () => pushHistoryAtom, queueMutationAtom: () => queueMutationAtom, redoAtom: () => redoAtom, redoCountAtom: () => redoCountAtom, registerAction: () => registerAction, registerNodeType: () => registerNodeType, registerNodeTypes: () => registerNodeTypes, registerPlugin: () => registerPlugin, removeEdgeWithAnimationAtom: () => removeEdgeWithAnimationAtom, removeFromGroupAtom: () => removeFromGroupAtom, removeGestureRuleAtom: () => removeGestureRuleAtom, removeNodesFromSelectionAtom: () => removeNodesFromSelectionAtom, resetGestureRulesAtom: () => resetGestureRulesAtom, resetInputModeAtom: () => resetInputModeAtom, resetKeyboardInteractionModeAtom: () => resetKeyboardInteractionModeAtom, resetSettingsAtom: () => resetSettingsAtom, resetViewportAtom: () => resetViewportAtom, resolveGesture: () => resolveGesture, resolveGestureIndexed: () => resolveGestureIndexed, saveAsPresetAtom: () => saveAsPresetAtom, screenToWorldAtom: () => screenToWorldAtom, searchEdgeResultCountAtom: () => searchEdgeResultCountAtom, searchEdgeResultsAtom: () => searchEdgeResultsAtom, searchQueryAtom: () => searchQueryAtom, searchResultCountAtom: () => searchResultCountAtom, searchResultsArrayAtom: () => searchResultsArrayAtom, searchResultsAtom: () => searchResultsAtom, searchTotalResultCountAtom: () => searchTotalResultCountAtom, selectEdgeAtom: () => selectEdgeAtom, selectSingleNodeAtom: () => selectSingleNodeAtom, selectedEdgeIdAtom: () => selectedEdgeIdAtom, selectedNodeIdsAtom: () => selectedNodeIdsAtom, selectedNodesCountAtom: () => selectedNodesCountAtom, selectionPathAtom: () => selectionPathAtom, selectionRectAtom: () => selectionRectAtom, setEventMappingAtom: () => setEventMappingAtom, setFocusedNodeAtom: () => setFocusedNodeAtom, setGridSizeAtom: () => setGridSizeAtom, setKeyboardInteractionModeAtom: () => setKeyboardInteractionModeAtom, setNodeParentAtom: () => setNodeParentAtom, setOnlineStatusAtom: () => setOnlineStatusAtom, setPanelOpenAtom: () => setPanelOpenAtom, setPerfEnabled: () => setPerfEnabled, setSearchQueryAtom: () => setSearchQueryAtom, setVirtualizationEnabledAtom: () => setVirtualizationEnabledAtom, setZoomAtom: () => setZoomAtom, showToastAtom: () => showToastAtom, snapAlignmentEnabledAtom: () => snapAlignmentEnabledAtom, snapEnabledAtom: () => snapEnabledAtom, snapGridSizeAtom: () => snapGridSizeAtom, snapTemporaryDisableAtom: () => snapTemporaryDisableAtom, snapToGrid: () => snapToGrid, spatialIndexAtom: () => spatialIndexAtom, splitNodeAtom: () => splitNodeAtom, startMutationAtom: () => startMutationAtom, startNodeDragAtom: () => startNodeDragAtom, startPickNodeAtom: () => startPickNodeAtom, startPickNodesAtom: () => startPickNodesAtom, startPickPointAtom: () => startPickPointAtom, startSelectionAtom: () => startSelectionAtom, swapEdgeAtomicAtom: () => swapEdgeAtomicAtom, syncStateAtom: () => syncStateAtom, syncStatusAtom: () => syncStatusAtom, toggleAlignmentGuidesAtom: () => toggleAlignmentGuidesAtom, toggleGroupCollapseAtom: () => toggleGroupCollapseAtom, toggleNodeInSelectionAtom: () => toggleNodeInSelectionAtom, togglePanelAtom: () => togglePanelAtom, toggleSnapAtom: () => toggleSnapAtom, toggleVirtualizationAtom: () => toggleVirtualizationAtom, trackMutationErrorAtom: () => trackMutationErrorAtom, uiNodesAtom: () => uiNodesAtom, undoAtom: () => undoAtom, undoCountAtom: () => undoCountAtom, ungroupNodesAtom: () => ungroupNodesAtom, unlockNodeAtom: () => unlockNodeAtom, unregisterAction: () => unregisterAction, unregisterNodeType: () => unregisterNodeType, unregisterPlugin: () => unregisterPlugin, updateEdgeLabelAtom: () => updateEdgeLabelAtom, updateGestureRuleAtom: () => updateGestureRuleAtom, updateInteractionFeedbackAtom: () => updateInteractionFeedbackAtom, updateNodePositionAtom: () => updateNodePositionAtom, updatePresetAtom: () => updatePresetAtom, updateSelectionAtom: () => updateSelectionAtom, validateSnapshot: () => validateSnapshot, viewportRectAtom: () => viewportRectAtom, virtualizationEnabledAtom: () => virtualizationEnabledAtom, virtualizationMetricsAtom: () => virtualizationMetricsAtom, visibleBoundsAtom: () => visibleBoundsAtom, visibleEdgeKeysAtom: () => visibleEdgeKeysAtom, visibleNodeKeysAtom: () => visibleNodeKeysAtom, watchExternalKeyboardAtom: () => watchExternalKeyboardAtom, watchReducedMotionAtom: () => watchReducedMotionAtom, worldToScreenAtom: () => worldToScreenAtom, zoomAnimationTargetAtom: () => zoomAnimationTargetAtom, zoomAtom: () => zoomAtom, zoomFocusNodeIdAtom: () => zoomFocusNodeIdAtom, zoomTransitionProgressAtom: () => zoomTransitionProgressAtom }); var init_core = __esm({ "src/core/index.ts"() { "use strict"; init_types(); init_graph_store(); init_graph_position(); init_graph_derived(); init_graph_mutations(); init_viewport_store(); init_selection_store(); init_sync_store(); init_interaction_store(); init_locked_node_store(); init_node_type_registry(); init_history_store(); init_toast_store(); init_snap_store(); init_settings_types(); init_action_registry(); init_action_executor(); init_settings_store(); init_canvas_api(); init_virtualization_store(); init_port_types(); init_clipboard_store(); init_input_classifier(); init_input_store(); init_selection_path_store(); init_group_store(); init_search_store(); init_gesture_resolver(); init_gesture_rules(); init_gesture_rule_store(); init_reduced_motion_store(); init_external_keyboard_store(); init_perf(); init_spatial_index(); init_plugin_types(); init_plugin_registry(); init_canvas_serializer(); } }); // src/index.ts init_core(); // src/hooks/useNodeSelection.ts init_selection_store(); import { c as _c2 } from "react/compiler-runtime"; import { useAtom, useSetAtom } from "jotai"; function useNodeSelection(nodeId) { const $ = _c2(13); const [selectedIds] = useAtom(selectedNodeIdsAtom); const selectSingle = useSetAtom(selectSingleNodeAtom); const toggleNode = useSetAtom(toggleNodeInSelectionAtom); let t0; if ($[0] !== nodeId || $[1] !== selectedIds) { t0 = selectedIds.has(nodeId); $[0] = nodeId; $[1] = selectedIds; $[2] = t0; } else { t0 = $[2]; } let t1; if ($[3] !== nodeId || $[4] !== selectSingle) { t1 = () => selectSingle(nodeId); $[3] = nodeId; $[4] = selectSingle; $[5] = t1; } else { t1 = $[5]; } let t2; if ($[6] !== nodeId || $[7] !== toggleNode) { t2 = () => toggleNode(nodeId); $[6] = nodeId; $[7] = toggleNode; $[8] = t2; } else { t2 = $[8]; } let t3; if ($[9] !== t0 || $[10] !== t1 || $[11] !== t2) { t3 = { isSelected: t0, selectNode: t1, toggleNode: t2 }; $[9] = t0; $[10] = t1; $[11] = t2; $[12] = t3; } else { t3 = $[12]; } return t3; } // src/hooks/useNodeDrag.ts init_graph_store(); init_graph_position(); init_graph_mutations(); init_viewport_store(); init_selection_store(); init_sync_store(); init_history_store(); init_group_store(); import { c as _c3 } from "react/compiler-runtime"; import { useAtom as useAtom2, useAtomValue, useSetAtom as useSetAtom2, atom as atom26 } from "jotai"; import { useGesture } from "@use-gesture/react"; import { useRef } from "react"; // src/utils/gesture-configs.ts var fingerGestureConfig = { eventOptions: { passive: false, capture: false }, drag: { pointer: { touch: true, keys: false, capture: false, buttons: -1 }, filterTaps: true, tapsThreshold: 10, // Was 3 — too strict for fingers threshold: 10 // Was 3 — needs larger dead zone } }; var pencilGestureConfig = { eventOptions: { passive: false, capture: false }, drag: { pointer: { touch: true, keys: false, capture: false, buttons: -1 }, filterTaps: true, tapsThreshold: 3, threshold: 2 // Very precise — small dead zone } }; var mouseGestureConfig = { eventOptions: { passive: false, capture: false }, drag: { pointer: { touch: true, keys: false, capture: false, buttons: -1 }, filterTaps: true, tapsThreshold: 5, // Was 3 threshold: 3 } }; function getNodeGestureConfig(source) { switch (source) { case "finger": return fingerGestureConfig; case "pencil": return pencilGestureConfig; case "mouse": return mouseGestureConfig; } } function getViewportGestureConfig(source) { const base = getNodeGestureConfig(source); return { ...base, eventOptions: { passive: false }, pinch: { pointer: { touch: true } }, wheel: { eventOptions: { passive: false } } }; } // src/hooks/useNodeDrag.ts init_input_store(); init_mutation_queue(); init_debug(); // src/utils/hit-test.ts var defaultProvider = { elementFromPoint: (x, y) => document.elementFromPoint(x, y), elementsFromPoint: (x, y) => document.elementsFromPoint(x, y) }; var _provider = defaultProvider; function setHitTestProvider(provider) { _provider = provider ?? defaultProvider; } function hitTestNode(screenX, screenY) { const element = _provider.elementFromPoint(screenX, screenY); const nodeElement = element?.closest("[data-node-id]") ?? null; const nodeId = nodeElement?.getAttribute("data-node-id") ?? null; return { nodeId, element: nodeElement }; } function hitTestPort(screenX, screenY) { const elements = _provider.elementsFromPoint(screenX, screenY); for (const el of elements) { const portElement = el.closest("[data-drag-port-id]"); if (portElement) { const portId = portElement.dataset.dragPortId; const portBar = portElement.closest("[data-port-bar]"); const nodeId = portBar?.dataset.nodeId; if (portId && nodeId) { return { nodeId, portId }; } } } return null; } // src/hooks/useDragStateMachine.ts init_group_store(); function buildDragPositions(graph, selectedNodeIds) { const positions = /* @__PURE__ */ new Map(); for (const nodeId of selectedNodeIds) { if (graph.hasNode(nodeId)) { const attrs = graph.getNodeAttributes(nodeId); positions.set(nodeId, { x: attrs.x, y: attrs.y }); } } const currentKeys = Array.from(positions.keys()); for (const nodeId of currentKeys) { const descendants = getNodeDescendants(graph, nodeId); for (const descId of descendants) { if (!positions.has(descId) && graph.hasNode(descId)) { const attrs = graph.getNodeAttributes(descId); positions.set(descId, { x: attrs.x, y: attrs.y }); } } } return positions; } function computeDragUpdates(initialPositions, movementX, movementY, zoom, graph) { const deltaX = movementX / zoom; const deltaY = movementY / zoom; const updates = []; initialPositions.forEach((initialPos, nodeId) => { if (graph.hasNode(nodeId)) { updates.push({ id: nodeId, pos: { x: initialPos.x + deltaX, y: initialPos.y + deltaY } }); } }); return updates; } function isDragPrevented(target) { return !!target.closest('[data-no-drag="true"]') || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT"; } // src/hooks/useNodeDrag.ts var debug13 = createDebug("drag"); function useNodeDrag(id, t0) { const $ = _c3(49); let t1; if ($[0] !== t0) { t1 = t0 === void 0 ? {} : t0; $[0] = t0; $[1] = t1; } else { t1 = $[1]; } const options = t1; const { onPersist, onPersistError, heldKeys } = options; const graph = useAtomValue(graphAtom); const [pan, setPan] = useAtom2(panAtom); const startMutation = useSetAtom2(startMutationAtom); const completeMutation = useSetAtom2(completeMutationAtom); const setStartDrag = useSetAtom2(startNodeDragAtom); const setEndDrag = useSetAtom2(endNodeDragAtom); const getPreDragAttributes = useAtomValue(preDragNodeAttributesAtom); const currentZoom = useAtomValue(zoomAtom); const getSelectedNodeIds2 = useAtomValue(selectedNodeIdsAtom); const currentGraphId = useAtomValue(currentGraphIdAtom); const edgeCreation = useAtomValue(edgeCreationAtom); const setGraph = useSetAtom2(graphAtom); useSetAtom2(nodePositionUpdateCounterAtom); const pushHistory = useSetAtom2(pushHistoryAtom); const setDropTarget = useSetAtom2(dropTargetNodeIdAtom); const nestNodesOnDrop = useSetAtom2(nestNodesOnDropAtom); const inputSource = useAtomValue(primaryInputSourceAtom); let t2; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = atom26(null, _temp2); $[2] = t2; } else { t2 = $[2]; } const batchUpdatePositions = useSetAtom2(t2); let t3; if ($[3] !== batchUpdatePositions) { t3 = (updates_0) => { batchUpdatePositions(updates_0); }; $[3] = batchUpdatePositions; $[4] = t3; } else { t3 = $[4]; } const updateNodePositions = t3; const gestureInstanceRef = useRef(0); let t4; if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { x: 0, y: 0 }; $[5] = t4; } else { t4 = $[5]; } const panStartRef = useRef(t4); const isSpaceHeld = Boolean(heldKeys?.byCode.Space || heldKeys?.byKey[" "] || heldKeys?.byKey.Spacebar); let t5; if ($[6] !== isSpaceHeld || $[7] !== pan) { t5 = (state) => { if (isDragPrevented(state.event.target)) { return; } gestureInstanceRef.current = gestureInstanceRef.current + 1; if (isSpaceHeld) { panStartRef.current = pan; } }; $[6] = isSpaceHeld; $[7] = pan; $[8] = t5; } else { t5 = $[8]; } let t6; if ($[9] !== currentZoom || $[10] !== edgeCreation || $[11] !== getSelectedNodeIds2 || $[12] !== graph || $[13] !== id || $[14] !== isSpaceHeld || $[15] !== pushHistory || $[16] !== setDropTarget || $[17] !== setPan || $[18] !== setStartDrag || $[19] !== startMutation || $[20] !== updateNodePositions) { t6 = (state_0) => { if (isDragPrevented(state_0.event.target)) { return; } if (edgeCreation.isCreating) { return; } state_0.event.stopPropagation(); if (isSpaceHeld) { if (!state_0.tap && state_0.active) { const [mx, my] = state_0.movement; setPan({ x: panStartRef.current.x + mx, y: panStartRef.current.y + my }); } return state_0.memo; } const { movement: t72, first, active, down, pinching, cancel, tap } = state_0; const [mx_0, my_0] = t72; let currentMemo = state_0.memo; if (tap || !active) { return currentMemo; } const currentGestureInstance = gestureInstanceRef.current; if (first) { const selectionSize = getSelectedNodeIds2.size; const label = selectionSize > 1 ? `Move ${selectionSize} nodes` : "Move node"; pushHistory(label); setStartDrag({ nodeId: id }); const initialPositions = buildDragPositions(graph, getSelectedNodeIds2); initialPositions.forEach(() => startMutation()); currentMemo = { initialPositions, gestureInstance: currentGestureInstance }; } if (!currentMemo || currentMemo.gestureInstance !== currentGestureInstance || !currentMemo.initialPositions) { if (cancel && !pinching && !down && !tap && !active) { cancel(); } return currentMemo; } const updates_1 = computeDragUpdates(currentMemo.initialPositions, mx_0, my_0, currentZoom, graph); if (updates_1.length > 0) { updateNodePositions(updates_1); } if (state_0.event && "clientX" in state_0.event) { const { nodeId: hoveredId } = hitTestNode(state_0.event.clientX, state_0.event.clientY); const validTarget = hoveredId && !currentMemo.initialPositions.has(hoveredId) ? hoveredId : null; setDropTarget(validTarget); } return currentMemo; }; $[9] = currentZoom; $[10] = edgeCreation; $[11] = getSelectedNodeIds2; $[12] = graph; $[13] = id; $[14] = isSpaceHeld; $[15] = pushHistory; $[16] = setDropTarget; $[17] = setPan; $[18] = setStartDrag; $[19] = startMutation; $[20] = updateNodePositions; $[21] = t6; } else { t6 = $[21]; } let t7; if ($[22] !== completeMutation || $[23] !== currentGraphId || $[24] !== currentZoom || $[25] !== edgeCreation || $[26] !== getPreDragAttributes || $[27] !== getSelectedNodeIds2 || $[28] !== graph || $[29] !== id || $[30] !== isSpaceHeld || $[31] !== nestNodesOnDrop || $[32] !== onPersist || $[33] !== onPersistError || $[34] !== setDropTarget || $[35] !== setEndDrag || $[36] !== setGraph || $[37] !== startMutation || $[38] !== updateNodePositions) { t7 = (state_1) => { if (isDragPrevented(state_1.event.target)) { return; } if (edgeCreation.isCreating) { setEndDrag({ nodeId: id }); return; } if (isSpaceHeld) { return; } state_1.event.stopPropagation(); const memo = state_1.memo; setDropTarget(null); if (state_1.event && "clientX" in state_1.event && memo?.initialPositions) { const { nodeId: hoveredId_0 } = hitTestNode(state_1.event.clientX, state_1.event.clientY); if (hoveredId_0 && !memo.initialPositions.has(hoveredId_0)) { const draggedNodeIds = Array.from(memo.initialPositions.keys()).filter((nid) => getSelectedNodeIds2.has(nid)); if (draggedNodeIds.length > 0) { nestNodesOnDrop({ nodeIds: draggedNodeIds, targetId: hoveredId_0 }); } } } if (!currentGraphId) { debug13("Cannot update node position: currentGraphId is not set"); setEndDrag({ nodeId: id }); return; } const nodesToUpdate = memo?.initialPositions ? Array.from(memo.initialPositions.keys()) : [id]; nodesToUpdate.forEach((draggedNodeId) => { if (!graph.hasNode(draggedNodeId)) { completeMutation(false); return; } const finalAttrs = graph.getNodeAttributes(draggedNodeId); const initialPos = memo?.initialPositions.get(draggedNodeId); if (!initialPos) { completeMutation(false); return; } const [mx_1, my_1] = state_1.movement; const deltaX = mx_1 / currentZoom; const deltaY = my_1 / currentZoom; const finalPosition = { x: initialPos.x + deltaX, y: initialPos.y + deltaY }; updateNodePositions([{ id: draggedNodeId, pos: finalPosition }]); if (!onPersist) { completeMutation(true); setEndDrag({ nodeId: draggedNodeId }); return; } const existingDbUiProps = typeof finalAttrs.dbData.ui_properties === "object" && finalAttrs.dbData.ui_properties !== null && !Array.isArray(finalAttrs.dbData.ui_properties) ? finalAttrs.dbData.ui_properties : {}; const newUiProperties = { ...existingDbUiProps, x: finalPosition.x, y: finalPosition.y, zIndex: finalAttrs.zIndex }; const pendingState = getPendingState(draggedNodeId); if (pendingState.inFlight) { pendingState.queuedPosition = finalPosition; pendingState.queuedUiProperties = newUiProperties; pendingState.graphId = currentGraphId; return; } pendingState.inFlight = true; pendingState.graphId = currentGraphId; const processQueuedUpdate = async (nodeId) => { const state_2 = getPendingState(nodeId); if (state_2 && state_2.queuedPosition && state_2.queuedUiProperties && state_2.graphId) { const queuedProps = state_2.queuedUiProperties; const queuedGraphId = state_2.graphId; state_2.queuedPosition = null; state_2.queuedUiProperties = null; startMutation(); ; try { await onPersist(nodeId, queuedGraphId, queuedProps); completeMutation(true); } catch (t82) { const error = t82; completeMutation(false); onPersistError?.(nodeId, error); } state_2.inFlight = false; processQueuedUpdate(nodeId); } else { if (state_2) { state_2.inFlight = false; } } }; onPersist(draggedNodeId, currentGraphId, newUiProperties).then(() => { completeMutation(true); processQueuedUpdate(draggedNodeId); }).catch((error_0) => { completeMutation(false); const state_3 = getPendingState(draggedNodeId); if (state_3) { state_3.inFlight = false; } const preDragAttrsForNode = getPreDragAttributes; if (preDragAttrsForNode && preDragAttrsForNode.dbData.id === draggedNodeId && graph.hasNode(draggedNodeId)) { graph.replaceNodeAttributes(draggedNodeId, preDragAttrsForNode); setGraph(graph); } onPersistError?.(draggedNodeId, error_0); processQueuedUpdate(draggedNodeId); }).finally(() => { setEndDrag({ nodeId: draggedNodeId }); }); }); }; $[22] = completeMutation; $[23] = currentGraphId; $[24] = currentZoom; $[25] = edgeCreation; $[26] = getPreDragAttributes; $[27] = getSelectedNodeIds2; $[28] = graph; $[29] = id; $[30] = isSpaceHeld; $[31] = nestNodesOnDrop; $[32] = onPersist; $[33] = onPersistError; $[34] = setDropTarget; $[35] = setEndDrag; $[36] = setGraph; $[37] = startMutation; $[38] = updateNodePositions; $[39] = t7; } else { t7 = $[39]; } let t8; if ($[40] !== t5 || $[41] !== t6 || $[42] !== t7) { t8 = { onPointerDown: t5, onDrag: t6, onDragEnd: t7 }; $[40] = t5; $[41] = t6; $[42] = t7; $[43] = t8; } else { t8 = $[43]; } let t9; if ($[44] !== inputSource) { t9 = getNodeGestureConfig(inputSource); $[44] = inputSource; $[45] = t9; } else { t9 = $[45]; } const bind = useGesture(t8, t9); let t10; if ($[46] !== bind || $[47] !== updateNodePositions) { t10 = { bind, updateNodePositions }; $[46] = bind; $[47] = updateNodePositions; $[48] = t10; } else { t10 = $[48]; } return t10; } function _temp2(get, set, updates) { const graph_0 = get(graphAtom); updates.forEach((u) => { if (graph_0.hasNode(u.id)) { graph_0.setNodeAttribute(u.id, "x", u.pos.x); graph_0.setNodeAttribute(u.id, "y", u.pos.y); } }); set(nodePositionUpdateCounterAtom, _temp); } function _temp(c) { return c + 1; } // src/hooks/useNodeResize.ts init_graph_store(); init_graph_position(); init_viewport_store(); init_sync_store(); init_debug(); import { c as _c4 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue2, useSetAtom as useSetAtom3 } from "jotai"; import { useRef as useRef2, useState, useEffect } from "react"; import { flushSync } from "react-dom"; var debug14 = createDebug("resize"); function useNodeResize(t0) { const $ = _c4(38); const { id, nodeData, updateNodePositions, options: t1 } = t0; let t2; if ($[0] !== t1) { t2 = t1 === void 0 ? {} : t1; $[0] = t1; $[1] = t2; } else { t2 = $[1]; } const options = t2; const { onPersist, onPersistError, minWidth: t3, minHeight: t4 } = options; const minWidth = t3 === void 0 ? 200 : t3; const minHeight = t4 === void 0 ? 150 : t4; const [localWidth, setLocalWidth] = useState(nodeData.width || 500); const [localHeight, setLocalHeight] = useState(nodeData.height || 500); const [isResizing, setIsResizing] = useState(false); const resizeStartRef = useRef2(null); const graph = useAtomValue2(graphAtom); const currentZoom = useAtomValue2(zoomAtom); const currentGraphId = useAtomValue2(currentGraphIdAtom); const startMutation = useSetAtom3(startMutationAtom); const completeMutation = useSetAtom3(completeMutationAtom); const setGraphUpdateVersion = useSetAtom3(graphUpdateVersionAtom); const setNodePositionUpdateCounter = useSetAtom3(nodePositionUpdateCounterAtom); let t5; let t6; if ($[2] !== isResizing || $[3] !== nodeData.height || $[4] !== nodeData.width) { t5 = () => { if (!isResizing) { setLocalWidth(nodeData.width || 500); setLocalHeight(nodeData.height || 500); } }; t6 = [nodeData.width, nodeData.height, isResizing]; $[2] = isResizing; $[3] = nodeData.height; $[4] = nodeData.width; $[5] = t5; $[6] = t6; } else { t5 = $[5]; t6 = $[6]; } useEffect(t5, t6); let t7; if ($[7] !== graph || $[8] !== id || $[9] !== localHeight || $[10] !== localWidth) { t7 = (direction) => (e) => { e.stopPropagation(); e.preventDefault(); setIsResizing(true); const nodeAttrs = graph.hasNode(id) ? graph.getNodeAttributes(id) : { x: 0, y: 0 }; resizeStartRef.current = { width: localWidth, height: localHeight, startX: e.clientX, startY: e.clientY, startNodeX: nodeAttrs.x, startNodeY: nodeAttrs.y, direction }; e.target.setPointerCapture(e.pointerId); }; $[7] = graph; $[8] = id; $[9] = localHeight; $[10] = localWidth; $[11] = t7; } else { t7 = $[11]; } const createResizeStart = t7; let t8; if ($[12] !== currentZoom || $[13] !== graph || $[14] !== id || $[15] !== minHeight || $[16] !== minWidth || $[17] !== setGraphUpdateVersion || $[18] !== setNodePositionUpdateCounter || $[19] !== updateNodePositions) { t8 = (e_0) => { if (!resizeStartRef.current) { return; } e_0.stopPropagation(); e_0.preventDefault(); const deltaX = (e_0.clientX - resizeStartRef.current.startX) / currentZoom; const deltaY = (e_0.clientY - resizeStartRef.current.startY) / currentZoom; const { direction: direction_0, width: startWidth, height: startHeight, startNodeX, startNodeY } = resizeStartRef.current; let newWidth = startWidth; let newHeight = startHeight; let newX = startNodeX; let newY = startNodeY; if (direction_0.includes("e")) { newWidth = Math.max(minWidth, startWidth + deltaX); } if (direction_0.includes("w")) { newWidth = Math.max(minWidth, startWidth - deltaX); newX = startNodeX + (startWidth - newWidth); } if (direction_0.includes("s")) { newHeight = Math.max(minHeight, startHeight + deltaY); } if (direction_0.includes("n")) { newHeight = Math.max(minHeight, startHeight - deltaY); newY = startNodeY + (startHeight - newHeight); } if (graph.hasNode(id)) { graph.setNodeAttribute(id, "width", newWidth); graph.setNodeAttribute(id, "height", newHeight); graph.setNodeAttribute(id, "x", newX); graph.setNodeAttribute(id, "y", newY); } flushSync(() => { setLocalWidth(newWidth); setLocalHeight(newHeight); setGraphUpdateVersion(_temp3); }); if (direction_0.includes("n") || direction_0.includes("w")) { updateNodePositions([{ id, pos: { x: newX, y: newY } }]); } else { setNodePositionUpdateCounter(_temp22); } }; $[12] = currentZoom; $[13] = graph; $[14] = id; $[15] = minHeight; $[16] = minWidth; $[17] = setGraphUpdateVersion; $[18] = setNodePositionUpdateCounter; $[19] = updateNodePositions; $[20] = t8; } else { t8 = $[20]; } const handleResizeMove = t8; let t9; if ($[21] !== completeMutation || $[22] !== currentGraphId || $[23] !== graph || $[24] !== id || $[25] !== localHeight || $[26] !== localWidth || $[27] !== onPersist || $[28] !== onPersistError || $[29] !== startMutation) { t9 = (e_1) => { if (!resizeStartRef.current) { return; } e_1.stopPropagation(); e_1.target.releasePointerCapture(e_1.pointerId); setIsResizing(false); if (!currentGraphId || !resizeStartRef.current) { resizeStartRef.current = null; return; } const finalAttrs = graph.hasNode(id) ? graph.getNodeAttributes(id) : null; if (!finalAttrs) { resizeStartRef.current = null; return; } const finalWidth = finalAttrs.width || localWidth; const finalHeight = finalAttrs.height || localHeight; const finalX = finalAttrs.x; const finalY = finalAttrs.y; setLocalWidth(finalWidth); setLocalHeight(finalHeight); if (!onPersist) { resizeStartRef.current = null; return; } const existingDbUiProps = typeof finalAttrs.dbData.ui_properties === "object" && finalAttrs.dbData.ui_properties !== null && !Array.isArray(finalAttrs.dbData.ui_properties) ? finalAttrs.dbData.ui_properties : {}; const newUiProperties = { ...existingDbUiProps, width: finalWidth, height: finalHeight, x: finalX, y: finalY }; startMutation(); onPersist(id, currentGraphId, newUiProperties).then(() => { completeMutation(true); }).catch((error) => { completeMutation(false); if (resizeStartRef.current) { setLocalWidth(resizeStartRef.current.width); setLocalHeight(resizeStartRef.current.height); } onPersistError?.(id, error); }).finally(() => { resizeStartRef.current = null; }); }; $[21] = completeMutation; $[22] = currentGraphId; $[23] = graph; $[24] = id; $[25] = localHeight; $[26] = localWidth; $[27] = onPersist; $[28] = onPersistError; $[29] = startMutation; $[30] = t9; } else { t9 = $[30]; } const handleResizeEnd = t9; let t10; if ($[31] !== createResizeStart || $[32] !== handleResizeEnd || $[33] !== handleResizeMove || $[34] !== isResizing || $[35] !== localHeight || $[36] !== localWidth) { t10 = { localWidth, localHeight, isResizing, createResizeStart, handleResizeMove, handleResizeEnd }; $[31] = createResizeStart; $[32] = handleResizeEnd; $[33] = handleResizeMove; $[34] = isResizing; $[35] = localHeight; $[36] = localWidth; $[37] = t10; } else { t10 = $[37]; } return t10; } function _temp22(c) { return c + 1; } function _temp3(v) { return v + 1; } // src/hooks/useCanvasHistory.ts init_history_store(); init_toast_store(); import { c as _c5 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue3, useSetAtom as useSetAtom4, useStore } from "jotai"; function useCanvasHistory(t0) { const $ = _c5(22); const options = t0 === void 0 ? {} : t0; const { enableKeyboardShortcuts: t1 } = options; t1 === void 0 ? false : t1; const canUndo = useAtomValue3(canUndoAtom); const canRedo = useAtomValue3(canRedoAtom); const undoCount = useAtomValue3(undoCountAtom); const redoCount = useAtomValue3(redoCountAtom); const historyLabels = useAtomValue3(historyLabelsAtom); const undoAction = useSetAtom4(undoAtom); const redoAction = useSetAtom4(redoAtom); const pushHistory = useSetAtom4(pushHistoryAtom); const clearHistory = useSetAtom4(clearHistoryAtom); const showToast = useSetAtom4(showToastAtom); const store = useStore(); let t2; if ($[0] !== showToast || $[1] !== store || $[2] !== undoAction) { t2 = () => { const state = store.get(historyStateAtom); const label = state.past[state.past.length - 1]?.label; const result = undoAction(); if (result && label) { showToast(`Undo: ${label}`); } return result; }; $[0] = showToast; $[1] = store; $[2] = undoAction; $[3] = t2; } else { t2 = $[3]; } const undo = t2; let t3; if ($[4] !== redoAction || $[5] !== showToast || $[6] !== store) { t3 = () => { const state_0 = store.get(historyStateAtom); const label_0 = state_0.future[0]?.label; const result_0 = redoAction(); if (result_0 && label_0) { showToast(`Redo: ${label_0}`); } return result_0; }; $[4] = redoAction; $[5] = showToast; $[6] = store; $[7] = t3; } else { t3 = $[7]; } const redo = t3; let t4; if ($[8] !== pushHistory) { t4 = (label_1) => { pushHistory(label_1); }; $[8] = pushHistory; $[9] = t4; } else { t4 = $[9]; } const recordSnapshot = t4; let t5; if ($[10] !== clearHistory) { t5 = () => { clearHistory(); }; $[10] = clearHistory; $[11] = t5; } else { t5 = $[11]; } const clear = t5; let t6; if ($[12] !== canRedo || $[13] !== canUndo || $[14] !== clear || $[15] !== historyLabels || $[16] !== recordSnapshot || $[17] !== redo || $[18] !== redoCount || $[19] !== undo || $[20] !== undoCount) { t6 = { undo, redo, canUndo, canRedo, undoCount, redoCount, historyLabels, recordSnapshot, clear }; $[12] = canRedo; $[13] = canUndo; $[14] = clear; $[15] = historyLabels; $[16] = recordSnapshot; $[17] = redo; $[18] = redoCount; $[19] = undo; $[20] = undoCount; $[21] = t6; } else { t6 = $[21]; } return t6; } // src/hooks/useCanvasSelection.ts init_selection_store(); import { c as _c6 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue4 } from "jotai"; function useCanvasSelection() { const $ = _c6(6); const selectedNodeIds = useAtomValue4(selectedNodeIdsAtom); const selectedEdgeId = useAtomValue4(selectedEdgeIdAtom); const count = useAtomValue4(selectedNodesCountAtom); const hasSelection = useAtomValue4(hasSelectionAtom); const t0 = selectedEdgeId !== null; let t1; if ($[0] !== count || $[1] !== hasSelection || $[2] !== selectedEdgeId || $[3] !== selectedNodeIds || $[4] !== t0) { t1 = { selectedNodeIds, selectedEdgeId, count, hasSelection, hasEdgeSelection: t0 }; $[0] = count; $[1] = hasSelection; $[2] = selectedEdgeId; $[3] = selectedNodeIds; $[4] = t0; $[5] = t1; } else { t1 = $[5]; } return t1; } // src/hooks/useCanvasViewport.ts init_viewport_store(); import { c as _c7 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue5 } from "jotai"; function useCanvasViewport() { const $ = _c7(9); const zoom = useAtomValue5(zoomAtom); const pan = useAtomValue5(panAtom); const viewportRect = useAtomValue5(viewportRectAtom); const screenToWorld = useAtomValue5(screenToWorldAtom); const worldToScreen = useAtomValue5(worldToScreenAtom); const zoomFocusNodeId = useAtomValue5(zoomFocusNodeIdAtom); const zoomTransitionProgress = useAtomValue5(zoomTransitionProgressAtom); const isZoomTransitioning = useAtomValue5(isZoomTransitioningAtom); let t0; if ($[0] !== isZoomTransitioning || $[1] !== pan || $[2] !== screenToWorld || $[3] !== viewportRect || $[4] !== worldToScreen || $[5] !== zoom || $[6] !== zoomFocusNodeId || $[7] !== zoomTransitionProgress) { t0 = { zoom, pan, viewportRect, screenToWorld, worldToScreen, zoomFocusNodeId, zoomTransitionProgress, isZoomTransitioning, zoomTransitionThreshold: ZOOM_TRANSITION_THRESHOLD, zoomExitThreshold: ZOOM_EXIT_THRESHOLD }; $[0] = isZoomTransitioning; $[1] = pan; $[2] = screenToWorld; $[3] = viewportRect; $[4] = worldToScreen; $[5] = zoom; $[6] = zoomFocusNodeId; $[7] = zoomTransitionProgress; $[8] = t0; } else { t0 = $[8]; } return t0; } // src/hooks/useCanvasDrag.ts init_graph_store(); import { c as _c8 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue6 } from "jotai"; function useCanvasDrag() { const $ = _c8(3); const draggingNodeId = useAtomValue6(draggingNodeIdAtom); const t0 = draggingNodeId !== null; let t1; if ($[0] !== draggingNodeId || $[1] !== t0) { t1 = { draggingNodeId, isDragging: t0 }; $[0] = draggingNodeId; $[1] = t0; $[2] = t1; } else { t1 = $[2]; } return t1; } // src/hooks/useLayout.ts init_graph_store(); init_graph_position(); init_graph_derived(); init_viewport_store(); init_selection_store(); init_layout(); init_layout(); import { c as _c9 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue7, useSetAtom as useSetAtom5 } from "jotai"; var useGetGraphBounds = () => { const $ = _c9(6); const graph = useAtomValue7(graphAtom); useAtomValue7(nodePositionUpdateCounterAtom); let nodes; let t0; if ($[0] !== graph) { nodes = graph.nodes().map((node) => { const nodeAttributes = graph.getNodeAttributes(node); return { x: nodeAttributes.x, y: nodeAttributes.y, width: nodeAttributes.width || 500, height: nodeAttributes.height || 500 }; }); t0 = calculateBounds(nodes); $[0] = graph; $[1] = nodes; $[2] = t0; } else { nodes = $[1]; t0 = $[2]; } const bounds = t0; let t1; if ($[3] !== bounds || $[4] !== nodes) { t1 = { bounds, nodes }; $[3] = bounds; $[4] = nodes; $[5] = t1; } else { t1 = $[5]; } return t1; }; var useSelectionBounds = () => { const $ = _c9(5); const selectedNodeIds = useAtomValue7(selectedNodeIdsAtom); const nodes = useAtomValue7(uiNodesAtom); let t0; if ($[0] !== nodes || $[1] !== selectedNodeIds) { let t1; if ($[3] !== selectedNodeIds) { t1 = (node) => selectedNodeIds.has(node.id); $[3] = selectedNodeIds; $[4] = t1; } else { t1 = $[4]; } const selectedNodes = nodes.filter(t1).map(_temp4); t0 = calculateBounds(selectedNodes); $[0] = nodes; $[1] = selectedNodeIds; $[2] = t0; } else { t0 = $[2]; } return t0; }; var useFitToBounds = () => { const $ = _c9(2); const setFitToBounds = useSetAtom5(fitToBoundsAtom); let t0; if ($[0] !== setFitToBounds) { const fitToBounds = (mode, t1) => { const padding = t1 === void 0 ? 20 : t1; setFitToBounds({ mode, padding }); }; t0 = { fitToBounds }; $[0] = setFitToBounds; $[1] = t0; } else { t0 = $[1]; } return t0; }; var useLayout = () => { const $ = _c9(5); const { fitToBounds } = useFitToBounds(); const { bounds: graphBounds, nodes: graphNodes } = useGetGraphBounds(); const selectionBounds = useSelectionBounds(); let t0; if ($[0] !== fitToBounds || $[1] !== graphBounds || $[2] !== graphNodes || $[3] !== selectionBounds) { t0 = { fitToBounds, graphBounds, graphNodes, selectionBounds }; $[0] = fitToBounds; $[1] = graphBounds; $[2] = graphNodes; $[3] = selectionBounds; $[4] = t0; } else { t0 = $[4]; } return t0; }; function _temp4(node_0) { return { x: node_0.position.x, y: node_0.position.y, width: node_0.width ?? 500, height: node_0.height ?? 500 }; } // src/hooks/useForceLayout.ts init_graph_store(); init_graph_position(); init_graph_derived(); init_layout(); init_debug(); import * as d3 from "d3-force"; import { useAtomValue as useAtomValue8, useSetAtom as useSetAtom6 } from "jotai"; import { useRef as useRef3 } from "react"; var debug15 = createDebug("force-layout"); var useForceLayout = (options = {}) => { const { onPositionsChanged, maxIterations = 1e3, chargeStrength = -6e3, linkStrength = 0.03 } = options; const nodes = useAtomValue8(uiNodesAtom); const graph = useAtomValue8(graphAtom); const updateNodePosition = useSetAtom6(updateNodePositionAtom); const isRunningRef = useRef3(false); const createVirtualLinks = () => { const links = []; for (let i = 0; i < nodes.length; i++) { for (let j = 1; j <= 3; j++) { const targetIndex = (i + j) % nodes.length; links.push({ source: nodes[i].id, target: nodes[targetIndex].id, strength: 0.05 // Very weak connection }); } } return links; }; const applyForceLayout = async () => { if (isRunningRef.current) { debug15.warn("Layout already running, ignoring request."); return; } if (nodes.length === 0) { debug15.warn("No nodes to layout."); return; } isRunningRef.current = true; const simulationNodes = nodes.map((node) => { const width = node.width || 80; const height = node.height || 80; return { id: node.id, x: node.position?.x || 0, y: node.position?.y || 0, width, height, radius: Math.max(width, height) + 80 }; }); const simulation = d3.forceSimulation(simulationNodes).force("charge", d3.forceManyBody().strength(chargeStrength).distanceMax(900)).force("collide", d3.forceCollide().radius((d) => d.radius).strength(2).iterations(8)).force("link", d3.forceLink(createVirtualLinks()).id((d_0) => d_0.id).strength(linkStrength)).force("center", d3.forceCenter(0, 0)).stop(); debug15("Starting simulation..."); return new Promise((resolve2) => { let iterations = 0; function runSimulationStep() { if (iterations >= maxIterations) { debug15("Reached max iterations (%d), finalizing.", maxIterations); finalizeLayout(); return; } simulation.tick(); iterations++; let hasOverlaps = false; for (let i_0 = 0; i_0 < simulationNodes.length; i_0++) { for (let j_0 = i_0 + 1; j_0 < simulationNodes.length; j_0++) { if (checkNodesOverlap(simulationNodes[i_0], simulationNodes[j_0])) { hasOverlaps = true; break; } } if (hasOverlaps) break; } if (!hasOverlaps) { debug15("No overlaps after %d iterations.", iterations); finalizeLayout(); return; } requestAnimationFrame(runSimulationStep); } function finalizeLayout() { const positionUpdates = []; simulationNodes.forEach((simNode) => { if (graph.hasNode(simNode.id)) { const newPosition = { x: Math.round(simNode.x), y: Math.round(simNode.y) }; updateNodePosition({ nodeId: simNode.id, position: newPosition }); positionUpdates.push({ nodeId: simNode.id, position: newPosition }); } }); if (onPositionsChanged && positionUpdates.length > 0) { debug15("Saving %d positions via callback...", positionUpdates.length); Promise.resolve(onPositionsChanged(positionUpdates)).then(() => debug15("Positions saved successfully.")).catch((err) => debug15.error("Error saving positions: %O", err)); } debug15("Layout complete."); isRunningRef.current = false; resolve2(); } requestAnimationFrame(runSimulationStep); }); }; return { applyForceLayout, isRunning: isRunningRef.current }; }; // src/hooks/useCanvasSettings.ts init_settings_store(); import { c as _c10 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue9, useSetAtom as useSetAtom7 } from "jotai"; function useCanvasSettings() { const $ = _c10(34); const mappings = useAtomValue9(eventMappingsAtom); const activePresetId = useAtomValue9(activePresetIdAtom); const activePreset = useAtomValue9(activePresetAtom); const allPresets = useAtomValue9(allPresetsAtom); const hasUnsavedChanges = useAtomValue9(hasUnsavedChangesAtom); const isPanelOpen = useAtomValue9(isPanelOpenAtom); const setEventMappingAction = useSetAtom7(setEventMappingAtom); const applyPresetAction = useSetAtom7(applyPresetAtom); const saveAsPresetAction = useSetAtom7(saveAsPresetAtom); const updatePresetAction = useSetAtom7(updatePresetAtom); const deletePresetAction = useSetAtom7(deletePresetAtom); const resetSettingsAction = useSetAtom7(resetSettingsAtom); const togglePanelAction = useSetAtom7(togglePanelAtom); const setPanelOpenAction = useSetAtom7(setPanelOpenAtom); let t0; if ($[0] !== setEventMappingAction) { t0 = (event, actionId) => { setEventMappingAction({ event, actionId }); }; $[0] = setEventMappingAction; $[1] = t0; } else { t0 = $[1]; } const setEventMapping = t0; let t1; if ($[2] !== applyPresetAction) { t1 = (presetId) => { applyPresetAction(presetId); }; $[2] = applyPresetAction; $[3] = t1; } else { t1 = $[3]; } const applyPreset = t1; let t2; if ($[4] !== saveAsPresetAction) { t2 = (name, description) => saveAsPresetAction({ name, description }); $[4] = saveAsPresetAction; $[5] = t2; } else { t2 = $[5]; } const saveAsPreset = t2; let t3; if ($[6] !== updatePresetAction) { t3 = (presetId_0) => { updatePresetAction(presetId_0); }; $[6] = updatePresetAction; $[7] = t3; } else { t3 = $[7]; } const updatePreset = t3; let t4; if ($[8] !== deletePresetAction) { t4 = (presetId_1) => { deletePresetAction(presetId_1); }; $[8] = deletePresetAction; $[9] = t4; } else { t4 = $[9]; } const deletePreset = t4; let t5; if ($[10] !== resetSettingsAction) { t5 = () => { resetSettingsAction(); }; $[10] = resetSettingsAction; $[11] = t5; } else { t5 = $[11]; } const resetSettings = t5; let t6; if ($[12] !== togglePanelAction) { t6 = () => { togglePanelAction(); }; $[12] = togglePanelAction; $[13] = t6; } else { t6 = $[13]; } const togglePanel = t6; let t7; if ($[14] !== setPanelOpenAction) { t7 = (isOpen) => { setPanelOpenAction(isOpen); }; $[14] = setPanelOpenAction; $[15] = t7; } else { t7 = $[15]; } const setPanelOpen = t7; let t8; if ($[16] !== mappings) { t8 = (event_0) => getActionForEvent(mappings, event_0); $[16] = mappings; $[17] = t8; } else { t8 = $[17]; } const getActionForEventFn = t8; let t9; if ($[18] !== activePreset || $[19] !== activePresetId || $[20] !== allPresets || $[21] !== applyPreset || $[22] !== deletePreset || $[23] !== getActionForEventFn || $[24] !== hasUnsavedChanges || $[25] !== isPanelOpen || $[26] !== mappings || $[27] !== resetSettings || $[28] !== saveAsPreset || $[29] !== setEventMapping || $[30] !== setPanelOpen || $[31] !== togglePanel || $[32] !== updatePreset) { t9 = { mappings, activePresetId, activePreset, allPresets, hasUnsavedChanges, isPanelOpen, setEventMapping, applyPreset, saveAsPreset, updatePreset, deletePreset, resetSettings, togglePanel, setPanelOpen, getActionForEvent: getActionForEventFn }; $[18] = activePreset; $[19] = activePresetId; $[20] = allPresets; $[21] = applyPreset; $[22] = deletePreset; $[23] = getActionForEventFn; $[24] = hasUnsavedChanges; $[25] = isPanelOpen; $[26] = mappings; $[27] = resetSettings; $[28] = saveAsPreset; $[29] = setEventMapping; $[30] = setPanelOpen; $[31] = togglePanel; $[32] = updatePreset; $[33] = t9; } else { t9 = $[33]; } return t9; } // src/hooks/useActionExecutor.ts init_action_executor(); init_settings_store(); import { c as _c11 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue10, useStore as useStore2 } from "jotai"; function useActionExecutor(t0) { const $ = _c11(13); const options = t0 === void 0 ? {} : t0; const store = useStore2(); const mappings = useAtomValue10(eventMappingsAtom); const helpers = buildActionHelpers(store, { onDeleteNode: options.onDeleteNode, onOpenContextMenu: options.onOpenContextMenu, onCreateNode: options.onCreateNode, onApplyForceLayout: options.onApplyForceLayout }); let t1; if ($[0] !== helpers) { t1 = async (actionId, context) => executeAction(actionId, context, helpers); $[0] = helpers; $[1] = t1; } else { t1 = $[1]; } const executeActionById = t1; let t2; if ($[2] !== helpers || $[3] !== mappings) { t2 = async (event, context_0) => { const actionId_0 = getActionForEvent(mappings, event); return executeAction(actionId_0, context_0, helpers); }; $[2] = helpers; $[3] = mappings; $[4] = t2; } else { t2 = $[4]; } const executeEventAction = t2; let t3; if ($[5] !== mappings) { t3 = (event_0) => getActionForEvent(mappings, event_0); $[5] = mappings; $[6] = t3; } else { t3 = $[6]; } const getActionForEventFn = t3; let t4; if ($[7] !== executeActionById || $[8] !== executeEventAction || $[9] !== getActionForEventFn || $[10] !== helpers || $[11] !== mappings) { t4 = { executeActionById, executeEventAction, getActionForEvent: getActionForEventFn, mappings, helpers }; $[7] = executeActionById; $[8] = executeEventAction; $[9] = getActionForEventFn; $[10] = helpers; $[11] = mappings; $[12] = t4; } else { t4 = $[12]; } return t4; } // src/hooks/useGestureResolver.ts init_gesture_rules(); init_gesture_rule_store(); init_input_store(); import { c as _c12 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue11 } from "jotai"; function useGestureResolver() { const $ = _c12(4); const index = useAtomValue11(gestureRuleIndexAtom); const palmRejection = useAtomValue11(palmRejectionEnabledAtom); const isStylusActive = useAtomValue11(isStylusActiveAtom); let t0; if ($[0] !== index || $[1] !== isStylusActive || $[2] !== palmRejection) { t0 = (desc) => resolveGestureIndexed({ ...desc, isStylusActive: desc.isStylusActive ?? isStylusActive }, index, { palmRejection }); $[0] = index; $[1] = isStylusActive; $[2] = palmRejection; $[3] = t0; } else { t0 = $[3]; } return t0; } // src/hooks/useCommandLine.ts import { c as _c13 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue12, useSetAtom as useSetAtom8 } from "jotai"; // src/commands/store.ts init_registry(); import { atom as atom28 } from "jotai"; // src/commands/store-atoms.ts import { atom as atom27 } from "jotai"; import { atomWithStorage as atomWithStorage3 } from "jotai/utils"; var inputModeAtom2 = atom27({ type: "normal" }); var commandLineVisibleAtom = atom27(false); var commandLineStateAtom = atom27({ phase: "idle" }); var commandFeedbackAtom = atom27(null); var commandHistoryAtom = atomWithStorage3("canvas-command-history", []); var selectedSuggestionIndexAtom = atom27(0); var pendingInputResolverAtom2 = atom27(null); var isCommandActiveAtom = atom27((get) => { const state = get(commandLineStateAtom); return state.phase === "collecting" || state.phase === "executing"; }); var currentInputAtom = atom27((get) => { const state = get(commandLineStateAtom); if (state.phase !== "collecting") return null; return state.command.inputs[state.inputIndex]; }); var commandProgressAtom = atom27((get) => { const state = get(commandLineStateAtom); if (state.phase !== "collecting") return null; return { current: state.inputIndex + 1, total: state.command.inputs.length }; }); // src/commands/store.ts var openCommandLineAtom = atom28(null, (get, set) => { set(commandLineVisibleAtom, true); set(commandLineStateAtom, { phase: "searching", query: "", suggestions: commandRegistry.all() }); set(selectedSuggestionIndexAtom, 0); }); var closeCommandLineAtom = atom28(null, (get, set) => { set(commandLineVisibleAtom, false); set(commandLineStateAtom, { phase: "idle" }); set(inputModeAtom2, { type: "normal" }); set(commandFeedbackAtom, null); set(pendingInputResolverAtom2, null); }); var updateSearchQueryAtom = atom28(null, (get, set, query) => { const suggestions = commandRegistry.search(query); set(commandLineStateAtom, { phase: "searching", query, suggestions }); set(selectedSuggestionIndexAtom, 0); }); var selectCommandAtom = atom28(null, (get, set, command) => { const history = get(commandHistoryAtom); const newHistory = [command.name, ...history.filter((h) => h !== command.name)].slice(0, 50); set(commandHistoryAtom, newHistory); if (command.inputs.length === 0) { set(commandLineStateAtom, { phase: "executing", command }); return; } set(commandLineStateAtom, { phase: "collecting", command, inputIndex: 0, collected: {} }); const firstInput = command.inputs[0]; set(inputModeAtom2, inputDefToMode(firstInput)); }); var provideInputAtom2 = atom28(null, (get, set, value) => { const state = get(commandLineStateAtom); if (state.phase !== "collecting") return; const { command, inputIndex, collected } = state; const currentInput = command.inputs[inputIndex]; if (currentInput.validate) { const result = currentInput.validate(value, collected); if (result !== true) { set(commandLineStateAtom, { phase: "error", message: typeof result === "string" ? result : `Invalid value for ${currentInput.name}` }); return; } } const newCollected = { ...collected, [currentInput.name]: value }; if (inputIndex < command.inputs.length - 1) { const nextInputIndex = inputIndex + 1; const nextInput = command.inputs[nextInputIndex]; set(commandLineStateAtom, { phase: "collecting", command, inputIndex: nextInputIndex, collected: newCollected }); set(inputModeAtom2, inputDefToMode(nextInput, newCollected)); if (command.feedback) { const feedback = command.feedback(newCollected, nextInput); if (feedback) { const feedbackState = { hoveredNodeId: feedback.highlightNodeId, ghostNode: feedback.ghostNode, crosshair: feedback.crosshair, // Handle previewEdge conversion - toCursor variant needs cursorWorldPos previewEdge: feedback.previewEdge && "to" in feedback.previewEdge ? { from: feedback.previewEdge.from, to: feedback.previewEdge.to } : void 0 }; set(commandFeedbackAtom, feedbackState); } else { set(commandFeedbackAtom, null); } } } else { set(commandLineStateAtom, { phase: "collecting", command, inputIndex, collected: newCollected }); set(inputModeAtom2, { type: "normal" }); } }); var skipInputAtom = atom28(null, (get, set) => { const state = get(commandLineStateAtom); if (state.phase !== "collecting") return; const { command, inputIndex } = state; const currentInput = command.inputs[inputIndex]; if (currentInput.required !== false) { return; } const value = currentInput.default; set(provideInputAtom2, value); }); var goBackInputAtom = atom28(null, (get, set) => { const state = get(commandLineStateAtom); if (state.phase !== "collecting") return; const { command, inputIndex, collected } = state; if (inputIndex === 0) { set(commandLineStateAtom, { phase: "searching", query: command.name, suggestions: [command] }); set(inputModeAtom2, { type: "normal" }); return; } const prevInputIndex = inputIndex - 1; const prevInput = command.inputs[prevInputIndex]; const newCollected = { ...collected }; delete newCollected[prevInput.name]; set(commandLineStateAtom, { phase: "collecting", command, inputIndex: prevInputIndex, collected: newCollected }); set(inputModeAtom2, inputDefToMode(prevInput, newCollected)); }); var setCommandErrorAtom = atom28(null, (get, set, message) => { set(commandLineStateAtom, { phase: "error", message }); set(inputModeAtom2, { type: "normal" }); }); var clearCommandErrorAtom = atom28(null, (get, set) => { set(commandLineStateAtom, { phase: "idle" }); }); function inputDefToMode(input, collected) { switch (input.type) { case "point": return { type: "pickPoint", prompt: input.prompt, snapToGrid: input.snapToGrid }; case "node": return { type: "pickNode", prompt: input.prompt, filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0 }; case "nodes": return { type: "pickNodes", prompt: input.prompt, filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0 }; case "select": return { type: "select", prompt: input.prompt, options: input.options || [] }; case "text": case "number": case "color": case "boolean": default: return { type: "text", prompt: input.prompt }; } } // src/hooks/useCommandLine.ts init_registry(); function useCommandLine() { const $ = _c13(16); const visible = useAtomValue12(commandLineVisibleAtom); const state = useAtomValue12(commandLineStateAtom); const history = useAtomValue12(commandHistoryAtom); const currentInput = useAtomValue12(currentInputAtom); const progress = useAtomValue12(commandProgressAtom); const open = useSetAtom8(openCommandLineAtom); const close = useSetAtom8(closeCommandLineAtom); const updateQuery = useSetAtom8(updateSearchQueryAtom); const selectCommand = useSetAtom8(selectCommandAtom); const t0 = state.phase === "searching"; const t1 = state.phase === "collecting"; const t2 = state.phase === "executing"; const t3 = state.phase === "error"; const t4 = state.phase === "error" ? state.message : null; let t5; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t5 = commandRegistry.all(); $[0] = t5; } else { t5 = $[0]; } let t6; if ($[1] !== close || $[2] !== currentInput || $[3] !== history || $[4] !== open || $[5] !== progress || $[6] !== selectCommand || $[7] !== state || $[8] !== t0 || $[9] !== t1 || $[10] !== t2 || $[11] !== t3 || $[12] !== t4 || $[13] !== updateQuery || $[14] !== visible) { t6 = { visible, state, history, currentInput, progress, open, close, updateQuery, selectCommand, isSearching: t0, isCollecting: t1, isExecuting: t2, hasError: t3, errorMessage: t4, allCommands: t5, searchCommands: _temp5 }; $[1] = close; $[2] = currentInput; $[3] = history; $[4] = open; $[5] = progress; $[6] = selectCommand; $[7] = state; $[8] = t0; $[9] = t1; $[10] = t2; $[11] = t3; $[12] = t4; $[13] = updateQuery; $[14] = visible; $[15] = t6; } else { t6 = $[15]; } return t6; } function _temp5(query) { return commandRegistry.search(query); } // src/hooks/useVirtualization.ts init_virtualization_store(); init_settings_store(); import { c as _c14 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue13, useSetAtom as useSetAtom9 } from "jotai"; function useVirtualization() { const $ = _c14(8); const metrics = useAtomValue13(virtualizationMetricsAtom); const setEnabled = useSetAtom9(setVirtualizationEnabledAtom); const toggle = useSetAtom9(toggleVirtualizationAtom); let t0; let t1; if ($[0] !== setEnabled) { t0 = () => setEnabled(true); t1 = () => setEnabled(false); $[0] = setEnabled; $[1] = t0; $[2] = t1; } else { t0 = $[1]; t1 = $[2]; } let t2; if ($[3] !== metrics || $[4] !== t0 || $[5] !== t1 || $[6] !== toggle) { t2 = { ...metrics, enable: t0, disable: t1, toggle }; $[3] = metrics; $[4] = t0; $[5] = t1; $[6] = toggle; $[7] = t2; } else { t2 = $[7]; } return t2; } // src/hooks/useTapGesture.ts import { c as _c15 } from "react/compiler-runtime"; import { useRef as useRef4 } from "react"; function useTapGesture(t0) { const $ = _c15(12); let t1; if ($[0] !== t0) { t1 = t0 === void 0 ? {} : t0; $[0] = t0; $[1] = t1; } else { t1 = $[1]; } const options = t1; const { onSingleTap, onDoubleTap, onTripleTap, tapDelay: t2, tapDistance: t3 } = options; const tapDelay = t2 === void 0 ? 300 : t2; const tapDistance = t3 === void 0 ? 25 : t3; let t4; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { count: 0, timer: null, lastX: 0, lastY: 0, lastEvent: null }; $[2] = t4; } else { t4 = $[2]; } const stateRef = useRef4(t4); let t5; if ($[3] !== onDoubleTap || $[4] !== onSingleTap || $[5] !== onTripleTap || $[6] !== tapDelay || $[7] !== tapDistance) { t5 = (event) => { const state = stateRef.current; const dx = Math.abs(event.clientX - state.lastX); const dy = Math.abs(event.clientY - state.lastY); const isSameSpot = state.count === 0 || dx < tapDistance && dy < tapDistance; if (!isSameSpot) { if (state.timer) { clearTimeout(state.timer); } state.count = 0; } state.count = state.count + 1; state.lastX = event.clientX; state.lastY = event.clientY; state.lastEvent = event; if (state.timer) { clearTimeout(state.timer); } if (state.count >= 3) { onTripleTap?.(event); state.count = 0; state.timer = null; return; } if (state.count === 2) { onDoubleTap?.(event); state.timer = setTimeout(() => { state.count = 0; state.timer = null; }, tapDelay); return; } state.timer = setTimeout(() => { if (state.count === 1 && state.lastEvent) { onSingleTap?.(state.lastEvent); } state.count = 0; state.timer = null; }, tapDelay); }; $[3] = onDoubleTap; $[4] = onSingleTap; $[5] = onTripleTap; $[6] = tapDelay; $[7] = tapDistance; $[8] = t5; } else { t5 = $[8]; } const handleTap = t5; let t6; if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t6 = () => { const state_0 = stateRef.current; if (state_0.timer) { clearTimeout(state_0.timer); state_0.timer = null; } state_0.count = 0; }; $[9] = t6; } else { t6 = $[9]; } const cleanup = t6; let t7; if ($[10] !== handleTap) { t7 = { handleTap, cleanup }; $[10] = handleTap; $[11] = t7; } else { t7 = $[11]; } return t7; } // src/hooks/useArrowKeyNavigation.ts init_selection_store(); import { c as _c16 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue14 } from "jotai"; function useArrowKeyNavigation() { const $ = _c16(2); const focusedNodeId = useAtomValue14(focusedNodeIdAtom); let t0; if ($[0] !== focusedNodeId) { t0 = { focusedNodeId }; $[0] = focusedNodeId; $[1] = t0; } else { t0 = $[1]; } return t0; } // src/hooks/useCanvasGraph.ts init_graph_store(); init_graph_derived(); import { c as _c17 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue15 } from "jotai"; function useCanvasGraph() { const $ = _c17(9); const graph = useAtomValue15(graphAtom); const nodeKeys = useAtomValue15(nodeKeysAtom); const edgeKeys = useAtomValue15(edgeKeysAtom); let t0; if ($[0] !== graph) { t0 = (id) => graph.hasNode(id) ? graph.getNodeAttributes(id) : void 0; $[0] = graph; $[1] = t0; } else { t0 = $[1]; } const getNode = t0; let t1; if ($[2] !== graph) { t1 = (id_0) => graph.hasEdge(id_0) ? graph.getEdgeAttributes(id_0) : void 0; $[2] = graph; $[3] = t1; } else { t1 = $[3]; } const getEdge = t1; let t2; if ($[4] !== edgeKeys || $[5] !== getEdge || $[6] !== getNode || $[7] !== nodeKeys) { t2 = { nodeCount: nodeKeys.length, edgeCount: edgeKeys.length, nodeKeys, edgeKeys, getNode, getEdge }; $[4] = edgeKeys; $[5] = getEdge; $[6] = getNode; $[7] = nodeKeys; $[8] = t2; } else { t2 = $[8]; } return t2; } // src/hooks/useZoomTransition.ts init_viewport_store(); import { c as _c18 } from "react/compiler-runtime"; import { useEffect as useEffect2, useRef as useRef5 } from "react"; import { useAtomValue as useAtomValue16, useSetAtom as useSetAtom10 } from "jotai"; function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function useZoomTransition() { const $ = _c18(15); const target = useAtomValue16(zoomAnimationTargetAtom); const setZoom = useSetAtom10(zoomAtom); const setPan = useSetAtom10(panAtom); const setTarget = useSetAtom10(zoomAnimationTargetAtom); const setProgress = useSetAtom10(zoomTransitionProgressAtom); const setFocusNode = useSetAtom10(zoomFocusNodeIdAtom); const progress = useAtomValue16(zoomTransitionProgressAtom); const rafRef = useRef5(null); let t0; if ($[0] !== setFocusNode || $[1] !== setProgress || $[2] !== setTarget) { t0 = () => { if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } setTarget(null); setProgress(0); setFocusNode(null); }; $[0] = setFocusNode; $[1] = setProgress; $[2] = setTarget; $[3] = t0; } else { t0 = $[3]; } const cancel = t0; let t1; let t2; if ($[4] !== setPan || $[5] !== setProgress || $[6] !== setTarget || $[7] !== setZoom || $[8] !== target) { t1 = () => { if (!target) { return; } const animate = () => { const elapsed = performance.now() - target.startTime; const rawT = Math.min(1, elapsed / target.duration); const t = easeInOutCubic(rawT); const currentZoom = target.startZoom + (target.targetZoom - target.startZoom) * t; const currentPanX = target.startPan.x + (target.targetPan.x - target.startPan.x) * t; const currentPanY = target.startPan.y + (target.targetPan.y - target.startPan.y) * t; setZoom(currentZoom); setPan({ x: currentPanX, y: currentPanY }); setProgress(t); if (rawT < 1) { rafRef.current = requestAnimationFrame(animate); } else { rafRef.current = null; setTarget(null); } }; rafRef.current = requestAnimationFrame(animate); return () => { if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } }; }; t2 = [target, setZoom, setPan, setTarget, setProgress]; $[4] = setPan; $[5] = setProgress; $[6] = setTarget; $[7] = setZoom; $[8] = target; $[9] = t1; $[10] = t2; } else { t1 = $[9]; t2 = $[10]; } useEffect2(t1, t2); const t3 = target !== null; let t4; if ($[11] !== cancel || $[12] !== progress || $[13] !== t3) { t4 = { isAnimating: t3, progress, cancel }; $[11] = cancel; $[12] = progress; $[13] = t3; $[14] = t4; } else { t4 = $[14]; } return t4; } // src/hooks/useSplitGesture.ts init_graph_mutations(); init_viewport_store(); import { c as _c19 } from "react/compiler-runtime"; import { useRef as useRef6 } from "react"; import { useAtomValue as useAtomValue17, useSetAtom as useSetAtom11 } from "jotai"; var SPLIT_THRESHOLD = 80; function useSplitGesture(nodeId) { const $ = _c19(9); const splitNode = useSetAtom11(splitNodeAtom); const screenToWorld = useAtomValue17(screenToWorldAtom); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ new Map(); $[0] = t0; } else { t0 = $[0]; } const pointersRef = useRef6(t0); const initialDistanceRef = useRef6(null); const splitFiredRef = useRef6(false); const getDistance = _temp6; let t1; if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = (e) => { if (e.pointerType !== "touch") { return; } pointersRef.current.set(e.pointerId, { pointerId: e.pointerId, x: e.clientX, y: e.clientY }); if (pointersRef.current.size === 2) { const [p1, p2] = Array.from(pointersRef.current.values()); initialDistanceRef.current = getDistance(p1, p2); splitFiredRef.current = false; } }; $[1] = t1; } else { t1 = $[1]; } const onPointerDown = t1; let t2; if ($[2] !== nodeId || $[3] !== screenToWorld || $[4] !== splitNode) { t2 = (e_0) => { if (e_0.pointerType !== "touch") { return; } if (!pointersRef.current.has(e_0.pointerId)) { return; } pointersRef.current.set(e_0.pointerId, { pointerId: e_0.pointerId, x: e_0.clientX, y: e_0.clientY }); if (pointersRef.current.size === 2 && initialDistanceRef.current !== null && !splitFiredRef.current) { const [p1_0, p2_0] = Array.from(pointersRef.current.values()); const currentDistance = getDistance(p1_0, p2_0); const delta = currentDistance - initialDistanceRef.current; if (delta > SPLIT_THRESHOLD) { splitFiredRef.current = true; e_0.stopPropagation(); const world1 = screenToWorld(p1_0.x, p1_0.y); const world2 = screenToWorld(p2_0.x, p2_0.y); splitNode({ nodeId, position1: world1, position2: world2 }); } } }; $[2] = nodeId; $[3] = screenToWorld; $[4] = splitNode; $[5] = t2; } else { t2 = $[5]; } const onPointerMove = t2; let t3; if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t3 = (e_1) => { pointersRef.current.delete(e_1.pointerId); if (pointersRef.current.size < 2) { initialDistanceRef.current = null; splitFiredRef.current = false; } }; $[6] = t3; } else { t3 = $[6]; } const onPointerUp = t3; let t4; if ($[7] !== onPointerMove) { t4 = { onPointerDown, onPointerMove, onPointerUp }; $[7] = onPointerMove; $[8] = t4; } else { t4 = $[8]; } return t4; } function _temp6(a, b) { return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); } // src/hooks/useAnimatedLayout.ts init_graph_store(); init_graph_position(); init_history_store(); init_debug(); init_reduced_motion_store(); import { useAtomValue as useAtomValue18, useSetAtom as useSetAtom12 } from "jotai"; import { useRef as useRef7 } from "react"; var debug16 = createDebug("animated-layout"); function easeInOutCubic2(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function useAnimatedLayout(options = {}) { const { onPositionsChanged, duration = 400 } = options; const graph = useAtomValue18(graphAtom); const updateNodePosition = useSetAtom12(updateNodePositionAtom); const pushHistory = useSetAtom12(pushHistoryAtom); const setPositionCounter = useSetAtom12(nodePositionUpdateCounterAtom); const reducedMotion = useAtomValue18(prefersReducedMotionAtom); const isAnimatingRef = useRef7(false); const animate = async (targets, label) => { if (isAnimatingRef.current) return; if (targets.size === 0) return; if (label) pushHistory(label); isAnimatingRef.current = true; if (reducedMotion) { for (const [nodeId, target] of targets) { updateNodePosition({ nodeId, position: target }); } isAnimatingRef.current = false; setPositionCounter((c) => c + 1); if (onPositionsChanged) { const updates = []; for (const [nodeId_0, target_0] of targets) { updates.push({ nodeId: nodeId_0, position: target_0 }); } Promise.resolve(onPositionsChanged(updates)).catch((err) => debug16.error("Position change callback failed: %O", err)); } return; } const starts = /* @__PURE__ */ new Map(); for (const [nodeId_1] of targets) { if (graph.hasNode(nodeId_1)) { const attrs = graph.getNodeAttributes(nodeId_1); starts.set(nodeId_1, { x: attrs.x, y: attrs.y }); } } return new Promise((resolve2) => { const startTime = performance.now(); function tick() { const elapsed = performance.now() - startTime; const t = Math.min(elapsed / duration, 1); const eased = easeInOutCubic2(t); for (const [nodeId_2, target_1] of targets) { const start = starts.get(nodeId_2); if (!start) continue; const x = Math.round(start.x + (target_1.x - start.x) * eased); const y = Math.round(start.y + (target_1.y - start.y) * eased); updateNodePosition({ nodeId: nodeId_2, position: { x, y } }); } if (t < 1) { requestAnimationFrame(tick); } else { isAnimatingRef.current = false; setPositionCounter((c_0) => c_0 + 1); if (onPositionsChanged) { const updates_0 = []; for (const [nodeId_3, target_2] of targets) { updates_0.push({ nodeId: nodeId_3, position: target_2 }); } Promise.resolve(onPositionsChanged(updates_0)).catch((err_0) => debug16.error("Position change callback failed: %O", err_0)); } resolve2(); } } requestAnimationFrame(tick); }); }; return { animate, isAnimating: isAnimatingRef.current }; } // src/hooks/useTreeLayout.ts init_graph_store(); init_graph_derived(); import { useAtomValue as useAtomValue19 } from "jotai"; import { useRef as useRef8 } from "react"; function useTreeLayout(options = {}) { const { direction = "top-down", levelGap = 200, nodeGap = 100, ...animateOptions } = options; const graph = useAtomValue19(graphAtom); const nodes = useAtomValue19(uiNodesAtom); const { animate, isAnimating } = useAnimatedLayout(animateOptions); const isRunningRef = useRef8(false); const applyLayout = async () => { if (isRunningRef.current || isAnimating) return; if (nodes.length === 0) return; isRunningRef.current = true; const nodeIds = new Set(nodes.map((n) => n.id)); const children = /* @__PURE__ */ new Map(); const hasIncoming = /* @__PURE__ */ new Set(); for (const nodeId of nodeIds) { children.set(nodeId, []); } graph.forEachEdge((_key, _attrs, source, target) => { if (nodeIds.has(source) && nodeIds.has(target) && source !== target) { children.get(source)?.push(target); hasIncoming.add(target); } }); const roots = [...nodeIds].filter((id) => !hasIncoming.has(id)); if (roots.length === 0) { roots.push(nodes[0].id); } const levels = /* @__PURE__ */ new Map(); const queue = [...roots]; for (const r of roots) levels.set(r, 0); while (queue.length > 0) { const current = queue.shift(); const level = levels.get(current); for (const child of children.get(current) || []) { if (!levels.has(child)) { levels.set(child, level + 1); queue.push(child); } } } for (const nodeId_0 of nodeIds) { if (!levels.has(nodeId_0)) levels.set(nodeId_0, 0); } const byLevel = /* @__PURE__ */ new Map(); for (const [nodeId_1, level_0] of levels) { if (!byLevel.has(level_0)) byLevel.set(level_0, []); byLevel.get(level_0).push(nodeId_1); } const targets = /* @__PURE__ */ new Map(); const maxLevel = Math.max(...byLevel.keys()); for (const [level_1, nodeIdsAtLevel] of byLevel) { const count = nodeIdsAtLevel.length; let maxNodeSize = 200; for (const nid of nodeIdsAtLevel) { if (graph.hasNode(nid)) { const attrs = graph.getNodeAttributes(nid); maxNodeSize = Math.max(maxNodeSize, attrs.width || 200); } } const totalWidth = (count - 1) * (maxNodeSize + nodeGap); const startX = -totalWidth / 2; for (let i = 0; i < count; i++) { const primary = level_1 * levelGap; const secondary = startX + i * (maxNodeSize + nodeGap); if (direction === "top-down") { targets.set(nodeIdsAtLevel[i], { x: secondary, y: primary }); } else { targets.set(nodeIdsAtLevel[i], { x: primary, y: secondary }); } } } await animate(targets, direction === "top-down" ? "Tree layout" : "Horizontal layout"); isRunningRef.current = false; }; return { applyLayout, isRunning: isRunningRef.current || isAnimating }; } // src/hooks/useGridLayout.ts init_graph_store(); init_graph_derived(); import { useAtomValue as useAtomValue20 } from "jotai"; import { useRef as useRef9 } from "react"; function useGridLayout(options = {}) { const { columns, gap = 80, ...animateOptions } = options; const graph = useAtomValue20(graphAtom); const nodes = useAtomValue20(uiNodesAtom); const { animate, isAnimating } = useAnimatedLayout(animateOptions); const isRunningRef = useRef9(false); const applyLayout = async () => { if (isRunningRef.current || isAnimating) return; if (nodes.length === 0) return; isRunningRef.current = true; const sorted = [...nodes].sort((a, b) => { const ay = a.position?.y ?? 0; const by = b.position?.y ?? 0; if (Math.abs(ay - by) > 50) return ay - by; return (a.position?.x ?? 0) - (b.position?.x ?? 0); }); const cols = columns ?? Math.ceil(Math.sqrt(sorted.length)); let maxW = 200; let maxH = 100; for (const node of sorted) { if (graph.hasNode(node.id)) { const attrs = graph.getNodeAttributes(node.id); maxW = Math.max(maxW, attrs.width || 200); maxH = Math.max(maxH, attrs.height || 100); } } const cellW = maxW + gap; const cellH = maxH + gap; const rows = Math.ceil(sorted.length / cols); const totalW = (cols - 1) * cellW; const totalH = (rows - 1) * cellH; const offsetX = -totalW / 2; const offsetY = -totalH / 2; const targets = /* @__PURE__ */ new Map(); for (let i = 0; i < sorted.length; i++) { const col = i % cols; const row = Math.floor(i / cols); targets.set(sorted[i].id, { x: Math.round(offsetX + col * cellW), y: Math.round(offsetY + row * cellH) }); } await animate(targets, "Grid layout"); isRunningRef.current = false; }; return { applyLayout, isRunning: isRunningRef.current || isAnimating }; } // src/hooks/usePlugin.ts init_plugin_registry(); import { c as _c20 } from "react/compiler-runtime"; import { useEffect as useEffect3, useRef as useRef10 } from "react"; function usePlugin(plugin) { const $ = _c20(4); const registeredRef = useRef10(false); let t0; if ($[0] !== plugin) { t0 = () => { if (!hasPlugin(plugin.id)) { registerPlugin(plugin); registeredRef.current = true; } return () => { if (registeredRef.current && hasPlugin(plugin.id)) { try { unregisterPlugin(plugin.id); } catch { } registeredRef.current = false; } }; }; $[0] = plugin; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== plugin.id) { t1 = [plugin.id]; $[2] = plugin.id; $[3] = t1; } else { t1 = $[3]; } useEffect3(t0, t1); } function usePlugins(plugins2) { const $ = _c20(6); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = []; $[0] = t0; } else { t0 = $[0]; } const registeredRef = useRef10(t0); let t1; let t2; if ($[1] !== plugins2) { t1 = () => { const registered = []; for (const plugin of plugins2) { if (!hasPlugin(plugin.id)) { registerPlugin(plugin); registered.push(plugin.id); } } registeredRef.current = registered; return () => { for (const id of registeredRef.current.reverse()) { if (hasPlugin(id)) { try { unregisterPlugin(id); } catch { } } } registeredRef.current = []; }; }; t2 = plugins2.map(_temp7).join(","); $[1] = plugins2; $[2] = t1; $[3] = t2; } else { t1 = $[2]; t2 = $[3]; } let t3; if ($[4] !== t2) { t3 = [t2]; $[4] = t2; $[5] = t3; } else { t3 = $[5]; } useEffect3(t1, t3); } function _temp7(p) { return p.id; } // src/utils/index.ts init_debug(); init_mutation_queue(); // src/utils/component-registry.tsx import { c as _c21 } from "react/compiler-runtime"; import React2 from "react"; import { jsx as _jsx2 } from "react/jsx-runtime"; function createComponentRegistry({ registry, fallback, getDisplayName = (type) => `Memoized${type}Component` }) { const memoizedCache = /* @__PURE__ */ new Map(); const getComponent = (type) => { if (memoizedCache.has(type)) { return memoizedCache.get(type); } const LazyComponent = registry[type]; if (!LazyComponent) { return fallback || null; } function RegistryComponent(props) { const $ = _c21(2); let t0; if ($[0] !== props) { t0 = /* @__PURE__ */ _jsx2(LazyComponent, { ...props }); $[0] = props; $[1] = t0; } else { t0 = $[1]; } return t0; } RegistryComponent.displayName = getDisplayName(type); memoizedCache.set(type, RegistryComponent); return RegistryComponent; }; const getTypes = () => Object.keys(registry); const hasType = (type) => type in registry; return { getComponent, getTypes, hasType, Fallback: fallback || null }; } function createFallbackComponent(getMessage) { return function FallbackComponent(props) { const message = getMessage ? getMessage(props) : `Unsupported type: "${props.nodeData?.dbData?.node_type || "Unknown"}"`; return /* @__PURE__ */ _jsx2("div", { style: { padding: "10px", color: "orange" }, children: /* @__PURE__ */ _jsx2("p", { children: message }) }); }; } // src/utils/index.ts init_edge_path_calculators(); init_layout(); init_edge_path_registry(); // src/db/adapter.ts import { atom as atom29 } from "jotai"; var InMemoryStorageAdapter = class { constructor() { __publicField(this, "nodes", /* @__PURE__ */ new Map()); __publicField(this, "edges", /* @__PURE__ */ new Map()); } async fetchNodes(graphId) { return Array.from(this.nodes.values()).filter((n) => n.graph_id === graphId); } async createNode(graphId, node) { const now = (/* @__PURE__ */ new Date()).toISOString(); const fullNode = { id: node.id ?? crypto.randomUUID(), graph_id: graphId, label: node.label ?? null, node_type: node.node_type ?? null, configuration: node.configuration ?? null, ui_properties: node.ui_properties ?? null, data: node.data ?? null, created_at: now, updated_at: now }; this.nodes.set(fullNode.id, fullNode); return fullNode; } async updateNode(nodeId, updates) { const existing = this.nodes.get(nodeId); if (!existing) throw new Error(`Node ${nodeId} not found`); const updated = { ...existing, ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }; this.nodes.set(nodeId, updated); return updated; } async deleteNode(nodeId) { this.nodes.delete(nodeId); for (const [edgeId, edge] of this.edges) { if (edge.source_node_id === nodeId || edge.target_node_id === nodeId) { this.edges.delete(edgeId); } } } async fetchEdges(graphId) { return Array.from(this.edges.values()).filter((e) => e.graph_id === graphId); } async createEdge(graphId, edge) { const now = (/* @__PURE__ */ new Date()).toISOString(); const fullEdge = { id: edge.id ?? crypto.randomUUID(), graph_id: graphId, source_node_id: edge.source_node_id ?? "", target_node_id: edge.target_node_id ?? "", edge_type: edge.edge_type ?? null, filter_condition: edge.filter_condition ?? null, ui_properties: edge.ui_properties ?? null, data: edge.data ?? null, created_at: now, updated_at: now }; this.edges.set(fullEdge.id, fullEdge); return fullEdge; } async updateEdge(edgeId, updates) { const existing = this.edges.get(edgeId); if (!existing) throw new Error(`Edge ${edgeId} not found`); const updated = { ...existing, ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }; this.edges.set(edgeId, updated); return updated; } async deleteEdge(edgeId) { this.edges.delete(edgeId); } // --- Batch Operations --- async createNodes(graphId, nodes) { return Promise.all(nodes.map((n) => this.createNode(graphId, n))); } async deleteNodes(nodeIds) { for (const id of nodeIds) { await this.deleteNode(id); } } async createEdges(graphId, edges) { return Promise.all(edges.map((e) => this.createEdge(graphId, e))); } async deleteEdges(edgeIds) { for (const id of edgeIds) { await this.deleteEdge(id); } } }; var storageAdapterAtom = atom29(null); // src/db/supabase-adapter.ts var SupabaseStorageAdapter = class { constructor(client) { this.client = client; } /** Get the underlying Supabase client (for advanced use or backward compat) */ getClient() { return this.client; } // --- Node Operations --- async fetchNodes(graphId) { const { data, error } = await this.client.from("nodes").select("*").eq("graph_id", graphId).order("created_at", { ascending: true }); if (error) throw error; return data || []; } async createNode(graphId, node) { const { data, error } = await this.client.from("nodes").insert({ graph_id: graphId, label: node.label ?? null, node_type: node.node_type ?? null, configuration: node.configuration ?? null, ui_properties: node.ui_properties ?? null, data: node.data ?? null }).select().single(); if (error) throw error; return data; } async updateNode(nodeId, updates) { const { data, error } = await this.client.from("nodes").update({ ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", nodeId).select().single(); if (error) throw error; return data; } async deleteNode(nodeId) { const { error } = await this.client.from("nodes").delete().eq("id", nodeId); if (error) throw error; } // --- Edge Operations --- async fetchEdges(graphId) { const { data, error } = await this.client.from("edges").select("*").eq("graph_id", graphId).order("created_at", { ascending: true }); if (error) throw error; return data || []; } async createEdge(graphId, edge) { const { data, error } = await this.client.from("edges").insert({ graph_id: graphId, source_node_id: edge.source_node_id ?? "", target_node_id: edge.target_node_id ?? "", edge_type: edge.edge_type ?? null, filter_condition: edge.filter_condition ?? null, ui_properties: edge.ui_properties ?? null, data: edge.data ?? null }).select().single(); if (error) throw error; return data; } async updateEdge(edgeId, updates) { const { data, error } = await this.client.from("edges").update({ ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", edgeId).select().single(); if (error) throw error; return data; } async deleteEdge(edgeId) { const { error } = await this.client.from("edges").delete().eq("id", edgeId); if (error) throw error; } // --- Realtime (optional) --- subscribe(graphId, onChange) { const channel = this.client.channel(`canvas-${graphId}`).on("postgres_changes", { event: "*", schema: "public", table: "nodes", filter: `graph_id=eq.${graphId}` }, (payload) => { onChange({ type: payload.eventType, table: "nodes", new: payload.new, old: payload.old }); }).on("postgres_changes", { event: "*", schema: "public", table: "edges", filter: `graph_id=eq.${graphId}` }, (payload) => { onChange({ type: payload.eventType, table: "edges", new: payload.new, old: payload.old }); }).subscribe(); return () => { channel.unsubscribe(); }; } }; // src/db/provider.tsx import React3, { createContext, useContext, useEffect as useEffect4, useRef as useRef11 } from "react"; import { useSetAtom as useSetAtom13, useAtomValue as useAtomValue21 } from "jotai"; import { atom as atom30, useAtomValue as useAtomVal } from "jotai"; import { jsx as _jsx3 } from "react/jsx-runtime"; var AdapterContext = /* @__PURE__ */ createContext(null); function CanvasDbProvider({ adapter, children }) { const setAdapter = useSetAtom13(storageAdapterAtom); const prevAdapterRef = useRef11(null); const resolved = adapter === prevAdapterRef.current ? prevAdapterRef.current : adapter; prevAdapterRef.current = resolved; useEffect4(() => { setAdapter(resolved); return () => setAdapter(null); }, [resolved, setAdapter]); return /* @__PURE__ */ _jsx3(AdapterContext, { value: resolved, children }); } function useStorageAdapter() { const contextAdapter = useContext(AdapterContext); const atomAdapter = useAtomValue21(storageAdapterAtom); const adapter = contextAdapter || atomAdapter; if (!adapter) { throw new Error("useStorageAdapter must be used within CanvasDbProvider"); } return adapter; } var supabaseClientAtom = atom30(null); function useSupabaseClient() { const client = useAtomVal(supabaseClientAtom); if (!client) { throw new Error("useSupabaseClient: no Supabase client available. Use CanvasDbProvider with a SupabaseStorageAdapter."); } return client; } // src/db/queries/nodes.ts init_debug(); var debug17 = createDebug("db:nodes"); async function fetchGraphNodes(supabase, graphId) { if (!graphId) { throw new Error("graphId is required to fetch nodes"); } const { data, error } = await supabase.from("nodes").select("*").eq("graph_id", graphId).order("created_at", { ascending: true }); if (error) { debug17.error("Error fetching nodes for graph %s: %O", graphId, error); throw error; } return data || []; } async function fetchGraphNode(supabase, nodeId) { if (!nodeId) { throw new Error("nodeId is required to fetch node"); } const { data, error } = await supabase.from("nodes").select("*").eq("id", nodeId).single(); if (error) { if (error.code === "PGRST116") { return null; } debug17.error("Error fetching node %s: %O", nodeId, error); throw error; } return data; } async function createGraphNode(supabase, node) { const { data, error } = await supabase.from("nodes").insert(node).select().single(); if (error) { debug17.error("Error creating node: %O", error); throw error; } return data; } async function updateGraphNode(supabase, nodeId, updates) { const { data, error } = await supabase.from("nodes").update({ ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", nodeId).select().single(); if (error) { debug17.error("Error updating node %s: %O", nodeId, error); throw error; } return data; } async function deleteGraphNode(supabase, nodeId) { const { error } = await supabase.from("nodes").delete().eq("id", nodeId); if (error) { debug17.error("Error deleting node %s: %O", nodeId, error); throw error; } } // src/db/queries/edges.ts init_debug(); var debug18 = createDebug("db:edges"); async function fetchGraphEdges(supabase, graphId) { if (!graphId) { throw new Error("graphId is required to fetch edges"); } const { data, error } = await supabase.from("edges").select("*").eq("graph_id", graphId).order("created_at", { ascending: true }); if (error) { debug18.error("Error fetching edges for graph %s: %O", graphId, error); throw error; } return data || []; } async function createGraphEdge(supabase, edge) { const { data, error } = await supabase.from("edges").insert(edge).select().single(); if (error) { debug18.error("Error creating edge: %O", error); throw error; } return data; } async function updateGraphEdge(supabase, edgeId, updates) { const { data, error } = await supabase.from("edges").update({ ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", edgeId).select().single(); if (error) { debug18.error("Error updating edge %s: %O", edgeId, error); throw error; } return data; } async function deleteGraphEdge(supabase, edgeId) { const { error } = await supabase.from("edges").delete().eq("id", edgeId); if (error) { debug18.error("Error deleting edge %s: %O", edgeId, error); throw error; } } // src/db/hooks/keys.ts var canvasKeys = { all: ["canvas"], graphs: () => [...canvasKeys.all, "graphs"], graph: (graphId) => [...canvasKeys.graphs(), graphId], nodes: (graphId) => [...canvasKeys.graph(graphId), "nodes"], node: (graphId, nodeId) => [...canvasKeys.nodes(graphId), nodeId], edges: (graphId) => [...canvasKeys.graph(graphId), "edges"], edge: (graphId, edgeId) => [...canvasKeys.edges(graphId), edgeId] }; // src/db/hooks/useGraphNodes.ts import { c as _c22 } from "react/compiler-runtime"; import { useQuery } from "@tanstack/react-query"; function useGraphNodes(graphId, t0) { const $ = _c22(11); let t1; if ($[0] !== t0) { t1 = t0 === void 0 ? {} : t0; $[0] = t0; $[1] = t1; } else { t1 = $[1]; } const options = t1; const { enabled: t2 } = options; const enabled = t2 === void 0 ? true : t2; const supabase = useSupabaseClient(); let t3; if ($[2] !== graphId) { t3 = canvasKeys.nodes(graphId || ""); $[2] = graphId; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== graphId || $[5] !== supabase) { t4 = () => fetchGraphNodes(supabase, graphId); $[4] = graphId; $[5] = supabase; $[6] = t4; } else { t4 = $[6]; } const t5 = enabled && !!graphId; let t6; if ($[7] !== t3 || $[8] !== t4 || $[9] !== t5) { t6 = { queryKey: t3, queryFn: t4, enabled: t5 }; $[7] = t3; $[8] = t4; $[9] = t5; $[10] = t6; } else { t6 = $[10]; } return useQuery(t6); } // src/db/hooks/useGraphEdges.ts import { c as _c23 } from "react/compiler-runtime"; import { useQuery as useQuery2 } from "@tanstack/react-query"; function useGraphEdges(graphId, t0) { const $ = _c23(11); let t1; if ($[0] !== t0) { t1 = t0 === void 0 ? {} : t0; $[0] = t0; $[1] = t1; } else { t1 = $[1]; } const options = t1; const { enabled: t2 } = options; const enabled = t2 === void 0 ? true : t2; const supabase = useSupabaseClient(); let t3; if ($[2] !== graphId) { t3 = canvasKeys.edges(graphId || ""); $[2] = graphId; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== graphId || $[5] !== supabase) { t4 = () => fetchGraphEdges(supabase, graphId); $[4] = graphId; $[5] = supabase; $[6] = t4; } else { t4 = $[6]; } const t5 = enabled && !!graphId; let t6; if ($[7] !== t3 || $[8] !== t4 || $[9] !== t5) { t6 = { queryKey: t3, queryFn: t4, enabled: t5 }; $[7] = t3; $[8] = t4; $[9] = t5; $[10] = t6; } else { t6 = $[10]; } return useQuery2(t6); } // src/db/hooks/useUpdateNode.ts init_sync_store(); import { c as _c24 } from "react/compiler-runtime"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useSetAtom as useSetAtom14 } from "jotai"; function useUpdateNode() { const $ = _c24(18); const queryClient = useQueryClient(); const supabase = useSupabaseClient(); const startMutation = useSetAtom14(startMutationAtom); const completeMutation = useSetAtom14(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (t12) => { const { nodeId, updates } = t12; return updateGraphNode(supabase, nodeId, updates); }; $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (t22) => { const { nodeId: nodeId_0, graphId, updates: updates_0 } = t22; startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.nodes(graphId) }); const previousNodes = queryClient.getQueryData(canvasKeys.nodes(graphId)); if (previousNodes) { queryClient.setQueryData(canvasKeys.nodes(graphId), (old) => old?.map((node) => node.id === nodeId_0 ? { ...node, ...updates_0, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : node)); } return { previousNodes, graphId }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousNodes) { queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes); } completeMutation(false); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[8] !== completeMutation) { t3 = () => { completeMutation(true); }; $[8] = completeMutation; $[9] = t3; } else { t3 = $[9]; } let t4; if ($[10] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.nodes(variables.graphId) }); }; $[10] = queryClient; $[11] = t4; } else { t4 = $[11]; } let t5; if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[12] = t0; $[13] = t1; $[14] = t2; $[15] = t3; $[16] = t4; $[17] = t5; } else { t5 = $[17]; } return useMutation(t5); } // src/db/hooks/useCreateNode.ts init_sync_store(); import { c as _c25 } from "react/compiler-runtime"; import { useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query"; import { useSetAtom as useSetAtom15 } from "jotai"; function useCreateNode() { const $ = _c25(17); const queryClient = useQueryClient2(); const supabase = useSupabaseClient(); const startMutation = useSetAtom15(startMutationAtom); const completeMutation = useSetAtom15(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (node) => createGraphNode(supabase, node); $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (node_0) => { startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.nodes(node_0.graph_id) }); const previousNodes = queryClient.getQueryData(canvasKeys.nodes(node_0.graph_id)); const optimisticNode = { ...node_0, id: `temp-${Date.now()}`, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }; queryClient.setQueryData(canvasKeys.nodes(node_0.graph_id), (old) => [...old || [], optimisticNode]); return { previousNodes, graphId: node_0.graph_id, optimisticId: optimisticNode.id }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; let t3; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousNodes) { queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes); } completeMutation(false); }; t3 = (newNode, _variables_0, context_0) => { if (context_0) { queryClient.setQueryData(canvasKeys.nodes(context_0.graphId), (old_0) => old_0?.map((node_1) => node_1.id === context_0.optimisticId ? newNode : node_1)); } completeMutation(true); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; $[8] = t3; } else { t2 = $[7]; t3 = $[8]; } let t4; if ($[9] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.nodes(variables.graph_id) }); }; $[9] = queryClient; $[10] = t4; } else { t4 = $[10]; } let t5; if ($[11] !== t0 || $[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[11] = t0; $[12] = t1; $[13] = t2; $[14] = t3; $[15] = t4; $[16] = t5; } else { t5 = $[16]; } return useMutation2(t5); } // src/db/hooks/useDeleteNode.ts init_sync_store(); import { c as _c26 } from "react/compiler-runtime"; import { useMutation as useMutation3, useQueryClient as useQueryClient3 } from "@tanstack/react-query"; import { useSetAtom as useSetAtom16 } from "jotai"; function useDeleteNode() { const $ = _c26(18); const queryClient = useQueryClient3(); const supabase = useSupabaseClient(); const startMutation = useSetAtom16(startMutationAtom); const completeMutation = useSetAtom16(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (t12) => { const { nodeId } = t12; return deleteGraphNode(supabase, nodeId); }; $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (t22) => { const { nodeId: nodeId_0, graphId } = t22; startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.nodes(graphId) }); const previousNodes = queryClient.getQueryData(canvasKeys.nodes(graphId)); if (previousNodes) { queryClient.setQueryData(canvasKeys.nodes(graphId), (old) => old?.filter((node) => node.id !== nodeId_0)); } return { previousNodes, graphId }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousNodes) { queryClient.setQueryData(canvasKeys.nodes(context.graphId), context.previousNodes); } completeMutation(false); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[8] !== completeMutation) { t3 = () => { completeMutation(true); }; $[8] = completeMutation; $[9] = t3; } else { t3 = $[9]; } let t4; if ($[10] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.nodes(variables.graphId) }); }; $[10] = queryClient; $[11] = t4; } else { t4 = $[11]; } let t5; if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[12] = t0; $[13] = t1; $[14] = t2; $[15] = t3; $[16] = t4; $[17] = t5; } else { t5 = $[17]; } return useMutation3(t5); } // src/db/hooks/useUpdateEdge.ts init_sync_store(); import { c as _c27 } from "react/compiler-runtime"; import { useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query"; import { useSetAtom as useSetAtom17 } from "jotai"; function useUpdateEdge() { const $ = _c27(18); const queryClient = useQueryClient4(); const supabase = useSupabaseClient(); const startMutation = useSetAtom17(startMutationAtom); const completeMutation = useSetAtom17(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (t12) => { const { edgeId, updates } = t12; return updateGraphEdge(supabase, edgeId, updates); }; $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (t22) => { const { edgeId: edgeId_0, graphId, updates: updates_0 } = t22; startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.edges(graphId) }); const previousEdges = queryClient.getQueryData(canvasKeys.edges(graphId)); if (previousEdges) { queryClient.setQueryData(canvasKeys.edges(graphId), (old) => old?.map((edge) => edge.id === edgeId_0 ? { ...edge, ...updates_0, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : edge)); } return { previousEdges, graphId }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousEdges) { queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges); } completeMutation(false); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[8] !== completeMutation) { t3 = () => { completeMutation(true); }; $[8] = completeMutation; $[9] = t3; } else { t3 = $[9]; } let t4; if ($[10] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.edges(variables.graphId) }); }; $[10] = queryClient; $[11] = t4; } else { t4 = $[11]; } let t5; if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[12] = t0; $[13] = t1; $[14] = t2; $[15] = t3; $[16] = t4; $[17] = t5; } else { t5 = $[17]; } return useMutation4(t5); } // src/db/hooks/useCreateEdge.ts init_sync_store(); import { c as _c28 } from "react/compiler-runtime"; import { useMutation as useMutation5, useQueryClient as useQueryClient5 } from "@tanstack/react-query"; import { useSetAtom as useSetAtom18 } from "jotai"; function useCreateEdge() { const $ = _c28(17); const queryClient = useQueryClient5(); const supabase = useSupabaseClient(); const startMutation = useSetAtom18(startMutationAtom); const completeMutation = useSetAtom18(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (edge) => createGraphEdge(supabase, edge); $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (edge_0) => { startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.edges(edge_0.graph_id) }); const previousEdges = queryClient.getQueryData(canvasKeys.edges(edge_0.graph_id)); const optimisticEdge = { ...edge_0, id: `temp-${Date.now()}`, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }; queryClient.setQueryData(canvasKeys.edges(edge_0.graph_id), (old) => [...old || [], optimisticEdge]); return { previousEdges, graphId: edge_0.graph_id, optimisticId: optimisticEdge.id }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; let t3; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousEdges) { queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges); } completeMutation(false); }; t3 = (newEdge, _variables_0, context_0) => { if (context_0) { queryClient.setQueryData(canvasKeys.edges(context_0.graphId), (old_0) => old_0?.map((edge_1) => edge_1.id === context_0.optimisticId ? newEdge : edge_1)); } completeMutation(true); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; $[8] = t3; } else { t2 = $[7]; t3 = $[8]; } let t4; if ($[9] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.edges(variables.graph_id) }); }; $[9] = queryClient; $[10] = t4; } else { t4 = $[10]; } let t5; if ($[11] !== t0 || $[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[11] = t0; $[12] = t1; $[13] = t2; $[14] = t3; $[15] = t4; $[16] = t5; } else { t5 = $[16]; } return useMutation5(t5); } // src/db/hooks/useDeleteEdge.ts init_sync_store(); import { c as _c29 } from "react/compiler-runtime"; import { useMutation as useMutation6, useQueryClient as useQueryClient6 } from "@tanstack/react-query"; import { useSetAtom as useSetAtom19 } from "jotai"; function useDeleteEdge() { const $ = _c29(18); const queryClient = useQueryClient6(); const supabase = useSupabaseClient(); const startMutation = useSetAtom19(startMutationAtom); const completeMutation = useSetAtom19(completeMutationAtom); let t0; if ($[0] !== supabase) { t0 = async (t12) => { const { edgeId } = t12; return deleteGraphEdge(supabase, edgeId); }; $[0] = supabase; $[1] = t0; } else { t0 = $[1]; } let t1; if ($[2] !== queryClient || $[3] !== startMutation) { t1 = async (t22) => { const { edgeId: edgeId_0, graphId } = t22; startMutation(); await queryClient.cancelQueries({ queryKey: canvasKeys.edges(graphId) }); const previousEdges = queryClient.getQueryData(canvasKeys.edges(graphId)); if (previousEdges) { queryClient.setQueryData(canvasKeys.edges(graphId), (old) => old?.filter((edge) => edge.id !== edgeId_0)); } return { previousEdges, graphId }; }; $[2] = queryClient; $[3] = startMutation; $[4] = t1; } else { t1 = $[4]; } let t2; if ($[5] !== completeMutation || $[6] !== queryClient) { t2 = (_err, _variables, context) => { if (context?.previousEdges) { queryClient.setQueryData(canvasKeys.edges(context.graphId), context.previousEdges); } completeMutation(false); }; $[5] = completeMutation; $[6] = queryClient; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[8] !== completeMutation) { t3 = () => { completeMutation(true); }; $[8] = completeMutation; $[9] = t3; } else { t3 = $[9]; } let t4; if ($[10] !== queryClient) { t4 = (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: canvasKeys.edges(variables.graphId) }); }; $[10] = queryClient; $[11] = t4; } else { t4 = $[11]; } let t5; if ($[12] !== t0 || $[13] !== t1 || $[14] !== t2 || $[15] !== t3 || $[16] !== t4) { t5 = { mutationFn: t0, onMutate: t1, onError: t2, onSuccess: t3, onSettled: t4 }; $[12] = t0; $[13] = t1; $[14] = t2; $[15] = t3; $[16] = t4; $[17] = t5; } else { t5 = $[17]; } return useMutation6(t5); } // src/styles/canvas-styles.ts var defaultLightStyles = { background: { color: "#f0f0f0", gradient: { from: "#f0f0f0", to: "#e0e0e0", angle: 180 }, pattern: "none" }, grid: { visible: true, minorLineColor: "rgba(0, 0, 0, 0.05)", majorLineColor: "rgba(0, 0, 0, 0.1)", opacity: 1 }, axes: { visible: true, color: "rgba(0, 0, 0, 0.3)", labelColor: "rgba(0, 0, 0, 0.7)", tickColor: "rgba(0, 0, 0, 0.3)" }, nodes: { defaultBackground: "#FAFAFA", defaultBorderColor: "rgba(0, 0, 0, 0.06)", defaultBorderRadius: 12, selectedBorderColor: "#FF9800", selectedGlowColor: "rgba(255, 152, 0, 0.5)", shadowColor: "rgba(0, 0, 0, 0.1)", shadowOpacity: 1, draggingBackgroundColor: "#FAFAFAd9", draggingBorderColor: "#CCCCCC", resizeHandleColor: "rgba(0, 0, 0, 0.2)", resizeHandleActiveColor: "rgba(59, 130, 246, 0.8)" }, edges: { defaultColor: "#cccccc", defaultWeight: 1.5, labelColor: "#FFFFFF", labelStrokeColor: "#000000", pathType: "bezier" } }; var defaultDarkStyles = { background: { color: "#0f172a", gradient: { from: "#0f172a", to: "#1e293b", angle: 180 }, pattern: "none" }, grid: { visible: true, minorLineColor: "rgba(255, 255, 255, 0.06)", majorLineColor: "rgba(255, 255, 255, 0.12)", opacity: 1 }, axes: { visible: true, color: "rgba(255, 255, 255, 0.35)", labelColor: "rgba(255, 255, 255, 0.7)", tickColor: "rgba(255, 255, 255, 0.35)" }, nodes: { defaultBackground: "#FAFAFA", defaultBorderColor: "rgba(0, 0, 0, 0.06)", defaultBorderRadius: 12, selectedBorderColor: "#FF9800", selectedGlowColor: "rgba(255, 152, 0, 0.5)", shadowColor: "rgba(0, 0, 0, 0.1)", shadowOpacity: 1, draggingBackgroundColor: "#FAFAFAd9", draggingBorderColor: "#666666", resizeHandleColor: "rgba(255, 255, 255, 0.2)", resizeHandleActiveColor: "rgba(96, 165, 250, 0.8)" }, edges: { defaultColor: "#cccccc", defaultWeight: 1.5, labelColor: "#FFFFFF", labelStrokeColor: "#000000", pathType: "bezier" } }; function mergeWithDefaults(userStyles, isDark) { const defaults = isDark ? defaultDarkStyles : defaultLightStyles; if (!userStyles) return defaults; return { background: { ...defaults.background, ...userStyles.background }, grid: { ...defaults.grid, ...userStyles.grid }, axes: { ...defaults.axes, ...userStyles.axes }, nodes: { ...defaults.nodes, ...userStyles.nodes }, edges: { ...defaults.edges, ...userStyles.edges } }; } // src/providers/CanvasProvider.tsx import { c as _c31 } from "react/compiler-runtime"; import React5, { useEffect as useEffect5 } from "react"; import { Provider as JotaiProvider, useSetAtom as useSetAtom21 } from "jotai"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // src/providers/CanvasStyleProvider.tsx import { c as _c30 } from "react/compiler-runtime"; import React4, { createContext as createContext2, useContext as useContext2, useRef as useRef12, useLayoutEffect } from "react"; import { atom as atom31, useAtom as useAtom3, useSetAtom as useSetAtom20 } from "jotai"; import { jsx as _jsx4 } from "react/jsx-runtime"; var canvasStylesAtom = atom31(defaultLightStyles); var canvasIsDarkAtom = atom31(false); var canvasStyleOverridesAtom = atom31(void 0); var CanvasStyleContext = /* @__PURE__ */ createContext2(null); var CSS_VARIABLE_NAMES = ["--canvas-bg", "--node-bg", "--node-border", "--node-radius", "--node-selected-border", "--node-selected-glow", "--node-shadow", "--node-shadow-opacity", "--node-dragging-bg", "--node-dragging-border", "--node-resize-handle", "--node-resize-handle-active", "--edge-color", "--edge-weight", "--edge-label-color", "--edge-label-stroke"]; function applyStylesToElement(element, styles4) { const { nodes, edges, background } = styles4; element.style.setProperty("--canvas-bg", background.color || ""); element.style.setProperty("--node-bg", nodes.defaultBackground || ""); element.style.setProperty("--node-border", nodes.defaultBorderColor || ""); element.style.setProperty("--node-radius", nodes.defaultBorderRadius ? `${nodes.defaultBorderRadius}px` : "8px"); element.style.setProperty("--node-selected-border", nodes.selectedBorderColor || ""); element.style.setProperty("--node-selected-glow", nodes.selectedGlowColor || ""); element.style.setProperty("--node-shadow", nodes.shadowColor || ""); element.style.setProperty("--node-shadow-opacity", nodes.shadowOpacity?.toString() || "0.2"); element.style.setProperty("--node-dragging-bg", nodes.draggingBackgroundColor || ""); element.style.setProperty("--node-dragging-border", nodes.draggingBorderColor || ""); element.style.setProperty("--node-resize-handle", nodes.resizeHandleColor || ""); element.style.setProperty("--node-resize-handle-active", nodes.resizeHandleActiveColor || ""); element.style.setProperty("--edge-color", edges.defaultColor || ""); element.style.setProperty("--edge-weight", edges.defaultWeight?.toString() || "1.5"); element.style.setProperty("--edge-label-color", edges.labelColor || ""); element.style.setProperty("--edge-label-stroke", edges.labelStrokeColor || ""); } function removeStylesFromElement(element) { CSS_VARIABLE_NAMES.forEach((name) => { element.style.removeProperty(name); }); } function CanvasStyleProvider(t0) { const $ = _c30(24); const { children, styles: propStyles, isDark: t1 } = t0; const propIsDark = t1 === void 0 ? false : t1; const containerRef = useRef12(null); const [styleOverrides, setStyleOverrides] = useAtom3(canvasStyleOverridesAtom); const [isDarkAtom, setIsDarkAtom] = useAtom3(canvasIsDarkAtom); const setCanvasStyles = useSetAtom20(canvasStylesAtom); const effectiveStyles = styleOverrides ?? propStyles; const effectiveIsDark = isDarkAtom || propIsDark; let t2; if ($[0] !== effectiveIsDark || $[1] !== effectiveStyles) { t2 = mergeWithDefaults(effectiveStyles, effectiveIsDark); $[0] = effectiveIsDark; $[1] = effectiveStyles; $[2] = t2; } else { t2 = $[2]; } const mergedStyles = t2; let t3; let t4; if ($[3] !== mergedStyles || $[4] !== setCanvasStyles) { t3 = () => { setCanvasStyles(mergedStyles); }; t4 = [mergedStyles, setCanvasStyles]; $[3] = mergedStyles; $[4] = setCanvasStyles; $[5] = t3; $[6] = t4; } else { t3 = $[5]; t4 = $[6]; } useLayoutEffect(t3, t4); let t5; if ($[7] !== mergedStyles) { t5 = (node) => { containerRef.current = node; if (!node) { return; } applyStylesToElement(node, mergedStyles); return () => removeStylesFromElement(node); }; $[7] = mergedStyles; $[8] = t5; } else { t5 = $[8]; } const containerRefCallback = t5; let t6; if ($[9] !== setStyleOverrides) { t6 = (newStyles) => { setStyleOverrides(newStyles); }; $[9] = setStyleOverrides; $[10] = t6; } else { t6 = $[10]; } const setStyles = t6; let t7; if ($[11] !== setIsDarkAtom) { t7 = (isDark) => { setIsDarkAtom(isDark); }; $[11] = setIsDarkAtom; $[12] = t7; } else { t7 = $[12]; } const setDarkMode = t7; let t8; if ($[13] !== mergedStyles || $[14] !== setDarkMode || $[15] !== setStyles) { t8 = { styles: mergedStyles, containerRef, setStyles, setDarkMode }; $[13] = mergedStyles; $[14] = setDarkMode; $[15] = setStyles; $[16] = t8; } else { t8 = $[16]; } const contextValue = t8; let t9; if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = { display: "contents" }; $[17] = t9; } else { t9 = $[17]; } let t10; if ($[18] !== children || $[19] !== containerRefCallback) { t10 = /* @__PURE__ */ _jsx4("div", { ref: containerRefCallback, className: "canvas-style-scope", style: t9, children }); $[18] = children; $[19] = containerRefCallback; $[20] = t10; } else { t10 = $[20]; } let t11; if ($[21] !== contextValue || $[22] !== t10) { t11 = /* @__PURE__ */ _jsx4(CanvasStyleContext, { value: contextValue, children: t10 }); $[21] = contextValue; $[22] = t10; $[23] = t11; } else { t11 = $[23]; } return t11; } function useCanvasStyle() { const context = useContext2(CanvasStyleContext); if (!context) { throw new Error("useCanvasStyle must be used within a CanvasStyleProvider"); } return context.styles; } function useCanvasTheme() { const $ = _c30(10); const context = useContext2(CanvasStyleContext); const [isDark, setIsDarkAtom] = useAtom3(canvasIsDarkAtom); const [styleOverrides, setStyleOverrides] = useAtom3(canvasStyleOverridesAtom); if (!context) { throw new Error("useCanvasTheme must be used within a CanvasStyleProvider"); } let t0; if ($[0] !== setIsDarkAtom || $[1] !== setStyleOverrides) { t0 = () => { setStyleOverrides(void 0); setIsDarkAtom(false); }; $[0] = setIsDarkAtom; $[1] = setStyleOverrides; $[2] = t0; } else { t0 = $[2]; } let t1; if ($[3] !== context.setDarkMode || $[4] !== context.setStyles || $[5] !== context.styles || $[6] !== isDark || $[7] !== styleOverrides || $[8] !== t0) { t1 = { styles: context.styles, isDark, styleOverrides, setDarkMode: context.setDarkMode, setStyles: context.setStyles, resetStyles: t0 }; $[3] = context.setDarkMode; $[4] = context.setStyles; $[5] = context.styles; $[6] = isDark; $[7] = styleOverrides; $[8] = t0; $[9] = t1; } else { t1 = $[9]; } return t1; } // src/providers/CanvasProvider.tsx init_graph_store(); init_graph_mutations(); import { Fragment as _Fragment, jsx as _jsx5 } from "react/jsx-runtime"; var defaultQueryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1e3 * 60, // 1 minute refetchOnWindowFocus: false } } }); function GraphLoader(t0) { const $ = _c31(19); const { graphId, onGraphLoad, onGraphError, children } = t0; const setCurrentGraphId = useSetAtom21(currentGraphIdAtom); const loadGraphFromDb = useSetAtom21(loadGraphFromDbAtom); const { data: nodes, error: nodesError, isLoading: nodesLoading } = useGraphNodes(graphId); const { data: edges, error: edgesError, isLoading: edgesLoading } = useGraphEdges(graphId); let t1; let t2; if ($[0] !== graphId || $[1] !== setCurrentGraphId) { t1 = () => { setCurrentGraphId(graphId); }; t2 = [graphId, setCurrentGraphId]; $[0] = graphId; $[1] = setCurrentGraphId; $[2] = t1; $[3] = t2; } else { t1 = $[2]; t2 = $[3]; } useEffect5(t1, t2); let t3; let t4; if ($[4] !== edges || $[5] !== edgesLoading || $[6] !== loadGraphFromDb || $[7] !== nodes || $[8] !== nodesLoading || $[9] !== onGraphLoad) { t3 = () => { if (nodes && edges && !nodesLoading && !edgesLoading) { loadGraphFromDb(nodes, edges); onGraphLoad?.(nodes, edges); } }; t4 = [nodes, edges, nodesLoading, edgesLoading, loadGraphFromDb, onGraphLoad]; $[4] = edges; $[5] = edgesLoading; $[6] = loadGraphFromDb; $[7] = nodes; $[8] = nodesLoading; $[9] = onGraphLoad; $[10] = t3; $[11] = t4; } else { t3 = $[10]; t4 = $[11]; } useEffect5(t3, t4); let t5; let t6; if ($[12] !== edgesError || $[13] !== nodesError || $[14] !== onGraphError) { t5 = () => { const error = nodesError || edgesError; if (error) { onGraphError?.(error); } }; t6 = [nodesError, edgesError, onGraphError]; $[12] = edgesError; $[13] = nodesError; $[14] = onGraphError; $[15] = t5; $[16] = t6; } else { t5 = $[15]; t6 = $[16]; } useEffect5(t5, t6); let t7; if ($[17] !== children) { t7 = /* @__PURE__ */ _jsx5(_Fragment, { children }); $[17] = children; $[18] = t7; } else { t7 = $[18]; } return t7; } function CanvasProvider(t0) { const $ = _c31(15); const { adapter, graphId, styles: styles4, isDark: t1, queryClient: t2, children, onGraphLoad, onGraphError } = t0; const isDark = t1 === void 0 ? false : t1; const queryClient = t2 === void 0 ? defaultQueryClient : t2; let t3; if ($[0] !== children || $[1] !== graphId || $[2] !== onGraphError || $[3] !== onGraphLoad) { t3 = /* @__PURE__ */ _jsx5(GraphLoader, { graphId, onGraphLoad, onGraphError, children }); $[0] = children; $[1] = graphId; $[2] = onGraphError; $[3] = onGraphLoad; $[4] = t3; } else { t3 = $[4]; } let t4; if ($[5] !== isDark || $[6] !== styles4 || $[7] !== t3) { t4 = /* @__PURE__ */ _jsx5(CanvasStyleProvider, { styles: styles4, isDark, children: t3 }); $[5] = isDark; $[6] = styles4; $[7] = t3; $[8] = t4; } else { t4 = $[8]; } let t5; if ($[9] !== adapter || $[10] !== t4) { t5 = /* @__PURE__ */ _jsx5(CanvasDbProvider, { adapter, children: t4 }); $[9] = adapter; $[10] = t4; $[11] = t5; } else { t5 = $[11]; } let t6; if ($[12] !== queryClient || $[13] !== t5) { t6 = /* @__PURE__ */ _jsx5(JotaiProvider, { children: /* @__PURE__ */ _jsx5(QueryClientProvider, { client: queryClient, children: t5 }) }); $[12] = queryClient; $[13] = t5; $[14] = t6; } else { t6 = $[14]; } return t6; } // src/components/Canvas.tsx import { c as _c46 } from "react/compiler-runtime"; import React16 from "react"; // src/components/Viewport.tsx init_viewport_store(); import { c as _c36 } from "react/compiler-runtime"; import React8, { useEffect as useEffect11, useRef as useRef17 } from "react"; import { useAtomValue as useAtomValue25 } from "jotai"; // src/gestures/useCanvasGestures.ts init_viewport_store(); init_selection_store(); init_input_classifier(); init_input_store(); init_reduced_motion_store(); init_selection_path_store(); import { useRef as useRef15, useEffect as useEffect7, useMemo } from "react"; import { useAtom as useAtom4, useSetAtom as useSetAtom22, useAtomValue as useAtomValue23 } from "jotai"; import { useGesture as useGesture2 } from "@use-gesture/react"; // src/gestures/normalize.ts init_input_classifier(); init_types2(); function extractModifiers(e) { if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { return NO_MODIFIERS; } return { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey, meta: e.metaKey }; } function clampButton(raw) { if (raw === 1) return 1; if (raw === 2) return 2; return 0; } function lifecycleFromEventType(type) { switch (type) { case "pointerdown": return "down"; case "pointerup": return "up"; case "pointercancel": return "cancel"; default: return "move"; } } function normalizePointer(e) { const classified = classifyPointer(e); return { pointerId: e.pointerId, lifecycle: lifecycleFromEventType(e.type), source: classified.source, button: clampButton(e.button), modifiers: extractModifiers(e), screenX: e.clientX, screenY: e.clientY, pressure: e.pressure, timestamp: e.timeStamp }; } // src/gestures/timed-state.ts var LONG_PRESS_TIMER = "long-press"; var SETTLE_TIMER = "settle"; var DEFAULT_LONG_PRESS_MS = 600; var DEFAULT_MULTI_TAP_WINDOW_MS = 300; var IDLE = { tag: "idle", tapCount: 0 }; var DEFAULT_CONFIG = { longPressMs: DEFAULT_LONG_PRESS_MS, multiTapWindowMs: DEFAULT_MULTI_TAP_WINDOW_MS }; function transition(state, event, config = DEFAULT_CONFIG) { switch (event) { case "down": return onDown(state, config); case "up": return onUp(state, config); case "move-beyond-threshold": return onMoveBeyond(state); case "cancel": return onCancel(); case "timer:long-press": return onLongPressTimer(state); case "timer:settle": return onSettleTimer(); default: return { state }; } } function onDown(state, config) { switch (state.tag) { case "idle": return { state: { tag: "pressed", tapCount: 0 }, cancelTimer: SETTLE_TIMER, scheduleTimer: { id: LONG_PRESS_TIMER, delayMs: config.longPressMs } }; case "released": return { state: { tag: "pressed", tapCount: state.tapCount }, cancelTimer: SETTLE_TIMER, scheduleTimer: { id: LONG_PRESS_TIMER, delayMs: config.longPressMs } }; default: return { state }; } } function onUp(state, config) { if (state.tag !== "pressed") { if (state.tag === "long-pressed") { return { state: IDLE, cancelTimer: LONG_PRESS_TIMER }; } return { state }; } const newCount = state.tapCount + 1; const emit = tapTypeForCount(newCount); return { state: { tag: "released", tapCount: newCount }, emit, cancelTimer: LONG_PRESS_TIMER, scheduleTimer: { id: SETTLE_TIMER, delayMs: config.multiTapWindowMs } }; } function onMoveBeyond(state) { if (state.tag === "pressed") { return { state: IDLE, cancelTimer: LONG_PRESS_TIMER }; } return { state }; } function onCancel() { return { state: IDLE, cancelTimer: LONG_PRESS_TIMER }; } function onLongPressTimer(state) { if (state.tag === "pressed") { return { state: { tag: "long-pressed", tapCount: 0 }, emit: "long-press" }; } return { state }; } function onSettleTimer() { return { state: IDLE }; } function tapTypeForCount(count) { switch (count) { case 2: return "double-tap"; case 3: return "triple-tap"; default: return "tap"; } } // src/gestures/timed-state-runner.ts var TimedStateRunner = class { constructor(config) { __publicField(this, "state", IDLE); __publicField(this, "timers", /* @__PURE__ */ new Map()); /** Called when the state machine emits a gesture (tap, double-tap, long-press, etc.) */ __publicField(this, "onEmit", null); this.config = { longPressMs: config?.longPressMs ?? DEFAULT_LONG_PRESS_MS, multiTapWindowMs: config?.multiTapWindowMs ?? DEFAULT_MULTI_TAP_WINDOW_MS }; } /** * Feed a lifecycle event into the state machine. * Returns the emitted gesture type if any (synchronous emit only). * Timer-based emits are delivered asynchronously via `onEmit`. */ feed(event) { return this.apply(event); } /** Current state tag (for debugging/testing). */ get tag() { return this.state.tag; } /** Destroy: cancel all pending timers. */ destroy() { for (const timer of this.timers.values()) { clearTimeout(timer); } this.timers.clear(); this.state = IDLE; this.onEmit = null; } apply(event) { const result = transition(this.state, event, this.config); this.state = result.state; if (result.cancelTimer) { const timer = this.timers.get(result.cancelTimer); if (timer !== void 0) { clearTimeout(timer); this.timers.delete(result.cancelTimer); } } if (result.scheduleTimer) { const { id, delayMs } = result.scheduleTimer; const existing = this.timers.get(id); if (existing !== void 0) { clearTimeout(existing); } this.timers.set(id, setTimeout(() => { this.timers.delete(id); const timerEvent = `timer:${id}`; const timerResult = transition(this.state, timerEvent, this.config); this.state = timerResult.state; if (timerResult.emit) { this.onEmit?.(timerResult.emit); } }, delayMs)); } return result.emit; } }; // src/gestures/specificity.ts init_types2(); var SCORE_TYPE = 128; var SCORE_KEY = 64; var SCORE_CODE = 64; var SCORE_SUBJECT = 32; var SCORE_MODIFIER_POSITIVE = 16; var SCORE_MODIFIER_NEGATIVE = 8; var SCORE_HELD_KEY_POSITIVE = 16; var SCORE_HELD_KEY_NEGATIVE = 8; var SCORE_SOURCE = 4; var SCORE_BUTTON = 2; function specificity(pattern, event) { let score = 0; if (pattern.kind !== void 0) { const actualKind = isKeyInputEvent(event) ? "key" : "pointer"; if (pattern.kind !== actualKind) return -1; } if (pattern.type !== void 0) { if (!isPointerGestureEvent(event) || pattern.type !== event.type) return -1; score += SCORE_TYPE; } if (pattern.subjectKind !== void 0) { if (event.subject === void 0 || pattern.subjectKind !== event.subject.kind) return -1; score += SCORE_SUBJECT; } if (pattern.phase !== void 0 && pattern.phase !== event.phase) { return -1; } if (pattern.source !== void 0) { if (!isPointerGestureEvent(event)) return -1; if (pattern.source !== event.source) return -1; score += SCORE_SOURCE; } if (pattern.button !== void 0) { if (!isPointerGestureEvent(event)) return -1; if (pattern.button !== event.button) return -1; score += SCORE_BUTTON; } if (pattern.key !== void 0) { if (!isKeyInputEvent(event) || pattern.key !== event.key) return -1; score += SCORE_KEY; } if (pattern.code !== void 0) { if (!isKeyInputEvent(event) || pattern.code !== event.code) return -1; score += SCORE_CODE; } if (pattern.modifiers !== void 0) { const ms = modifierScore(pattern.modifiers, event.modifiers); if (ms === -1) return -1; score += ms; } if (pattern.heldKeys !== void 0) { const hs = heldKeyScore(pattern.heldKeys, event.heldKeys); if (hs === -1) return -1; score += hs; } return score; } function modifierScore(pattern, actual) { let score = 0; const keys = ["shift", "ctrl", "alt", "meta"]; for (const key of keys) { const required = pattern[key]; if (required === void 0) continue; if (required !== actual[key]) return -1; score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE; } if (pattern.custom) { const actualCustom = actual.custom ?? {}; for (const [key, required] of Object.entries(pattern.custom)) { const actualVal = actualCustom[key] ?? false; if (required !== actualVal) return -1; score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE; } } return score; } function heldKeyScore(pattern, actual) { let score = 0; for (const [key, required] of Object.entries(pattern.byKey ?? {})) { const actualVal = actual.byKey[key] ?? false; if (required !== actualVal) return -1; score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE; } for (const [code, required] of Object.entries(pattern.byCode ?? {})) { const actualVal = actual.byCode[code] ?? false; if (required !== actualVal) return -1; score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE; } return score; } // src/gestures/mapper.ts init_types2(); var WILDCARD = "__wildcard__"; function getPatternBucketKey(binding) { const { pattern } = binding; if (pattern.type !== void 0) { return `pointer:${pattern.type}`; } if (pattern.kind === "key" || pattern.key !== void 0 || pattern.code !== void 0) { return pattern.phase === "down" || pattern.phase === "up" ? `key:${pattern.phase}` : null; } return null; } function getEventBucketKey(event) { return isKeyInputEvent(event) ? `key:${event.phase}` : `pointer:${event.type}`; } function indexContext(ctx) { const typed = /* @__PURE__ */ new Map(); const wildcards = []; for (const binding of ctx.bindings) { const key = getPatternBucketKey(binding); if (key === null) { wildcards.push(binding); } else { let bucket = typed.get(key); if (!bucket) { bucket = []; typed.set(key, bucket); } bucket.push(binding); } } const buckets = /* @__PURE__ */ new Map(); for (const [key, bucket] of typed) { buckets.set(key, bucket); } if (wildcards.length > 0) { buckets.set(WILDCARD, wildcards); } return { contextId: ctx.id, priority: ctx.priority, enabled: ctx.enabled, buckets }; } function buildMappingIndex(contexts) { return contexts.map(indexContext).sort((a, b) => a.priority - b.priority); } function resolve(event, index, guard) { let winner = null; for (const ctx of index) { if (!ctx.enabled) continue; const bucket = ctx.buckets.get(getEventBucketKey(event)); const wildcardBucket = ctx.buckets.get(WILDCARD); if (!bucket && !wildcardBucket) continue; let bestScore = -1; let bestBinding = null; for (const binding of [...bucket ?? [], ...wildcardBucket ?? []]) { const score = specificity(binding.pattern, event); if (score === -1) continue; if (binding.when && !binding.when(guard)) continue; if (score > bestScore) { bestScore = score; bestBinding = binding; } } if (bestBinding !== null) { const action = { actionId: bestBinding.actionId, binding: bestBinding, contextId: ctx.contextId, score: bestScore, consumed: bestBinding.consumeInput === true }; if (action.consumed) { return action; } if (winner === null) { winner = action; } } } return winner; } // src/gestures/useCanvasGestures.ts init_dispatcher(); // src/gestures/keyboard-contexts.ts var SEARCH_CONTEXT = { id: "search-active", priority: 25, enabled: true, bindings: [{ id: "search-next-enter", pattern: { kind: "key", phase: "down", key: "Enter", modifiers: { shift: false } }, actionId: "search-next-result", when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible, consumeInput: true }, { id: "search-prev-enter", pattern: { kind: "key", phase: "down", key: "Enter", modifiers: { shift: true } }, actionId: "search-prev-result", when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible, consumeInput: true }, { id: "search-next-ctrl-g", pattern: { kind: "key", phase: "down", key: "g", modifiers: { ctrl: true, shift: false } }, actionId: "search-next-result", when: (ctx) => ctx.isSearchActive, consumeInput: true }, { id: "search-prev-ctrl-g", pattern: { kind: "key", phase: "down", key: "g", modifiers: { ctrl: true, shift: true } }, actionId: "search-prev-result", when: (ctx) => ctx.isSearchActive, consumeInput: true }, { id: "search-next-meta-g", pattern: { kind: "key", phase: "down", key: "g", modifiers: { meta: true, shift: false } }, actionId: "search-next-result", when: (ctx) => ctx.isSearchActive, consumeInput: true }, { id: "search-prev-meta-g", pattern: { kind: "key", phase: "down", key: "g", modifiers: { meta: true, shift: true } }, actionId: "search-prev-result", when: (ctx) => ctx.isSearchActive, consumeInput: true }] }; var KEYBOARD_MANIPULATE_CONTEXT = { id: "keyboard:manipulate", priority: 30, enabled: true, bindings: [{ id: "manipulate-arrow-up", pattern: { kind: "key", phase: "down", key: "ArrowUp", modifiers: { shift: false } }, actionId: "nudge-selection-up", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-arrow-down", pattern: { kind: "key", phase: "down", key: "ArrowDown", modifiers: { shift: false } }, actionId: "nudge-selection-down", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-arrow-left", pattern: { kind: "key", phase: "down", key: "ArrowLeft", modifiers: { shift: false } }, actionId: "nudge-selection-left", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-arrow-right", pattern: { kind: "key", phase: "down", key: "ArrowRight", modifiers: { shift: false } }, actionId: "nudge-selection-right", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-shift-arrow-up", pattern: { kind: "key", phase: "down", key: "ArrowUp", modifiers: { shift: true } }, actionId: "nudge-selection-up-large", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-shift-arrow-down", pattern: { kind: "key", phase: "down", key: "ArrowDown", modifiers: { shift: true } }, actionId: "nudge-selection-down-large", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-shift-arrow-left", pattern: { kind: "key", phase: "down", key: "ArrowLeft", modifiers: { shift: true } }, actionId: "nudge-selection-left-large", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-shift-arrow-right", pattern: { kind: "key", phase: "down", key: "ArrowRight", modifiers: { shift: true } }, actionId: "nudge-selection-right-large", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }, { id: "manipulate-escape", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "exit-keyboard-manipulate-mode", when: (ctx) => ctx.keyboardInteractionMode === "manipulate", consumeInput: true }] }; var KEYBOARD_NAVIGATE_CONTEXT = { id: "keyboard:navigate", priority: 40, enabled: true, bindings: [{ id: "navigate-arrow-up", pattern: { kind: "key", phase: "down", key: "ArrowUp" }, actionId: "navigate-focus-up", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-arrow-down", pattern: { kind: "key", phase: "down", key: "ArrowDown" }, actionId: "navigate-focus-down", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-arrow-left", pattern: { kind: "key", phase: "down", key: "ArrowLeft" }, actionId: "navigate-focus-left", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-arrow-right", pattern: { kind: "key", phase: "down", key: "ArrowRight" }, actionId: "navigate-focus-right", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-enter", pattern: { kind: "key", phase: "down", key: "Enter" }, actionId: "enter-keyboard-manipulate-mode", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-space", pattern: { kind: "key", phase: "down", code: "Space" }, actionId: "activate-focused-node", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-tab", pattern: { kind: "key", phase: "down", key: "Tab", modifiers: { shift: false } }, actionId: "cycle-focus-forward", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }, { id: "navigate-shift-tab", pattern: { kind: "key", phase: "down", key: "Tab", modifiers: { shift: true } }, actionId: "cycle-focus-backward", when: (ctx) => ctx.keyboardInteractionMode === "navigate", consumeInput: true }] }; // src/gestures/pointer-bindings.ts var POINTER_BINDINGS = [ // --- Pointer taps --- { id: "tap-node", pattern: { type: "tap", subjectKind: "node" }, actionId: "select-node" }, { id: "tap-edge", pattern: { type: "tap", subjectKind: "edge" }, actionId: "select-edge" }, { id: "tap-bg", pattern: { type: "tap", subjectKind: "background" }, actionId: "clear-selection" }, { id: "shift-tap-node", pattern: { type: "tap", subjectKind: "node", modifiers: { shift: true } }, actionId: "toggle-selection" }, // --- Right-click --- { id: "rc-node", pattern: { type: "tap", subjectKind: "node", button: 2 }, actionId: "context-menu", consumeInput: true }, { id: "rc-bg", pattern: { type: "tap", subjectKind: "background", button: 2 }, actionId: "context-menu", consumeInput: true }, // --- Double/triple tap --- { id: "dtap-node", pattern: { type: "double-tap", subjectKind: "node" }, actionId: "fit-to-view" }, { id: "ttap-node", pattern: { type: "triple-tap", subjectKind: "node" }, actionId: "toggle-lock" }, // --- Drags --- { id: "drag-node", pattern: { type: "drag", subjectKind: "node" }, actionId: "move-node" }, { id: "drag-bg-finger", pattern: { type: "drag", subjectKind: "background", source: "finger" }, actionId: "pan" }, { id: "drag-bg-mouse", pattern: { type: "drag", subjectKind: "background", source: "mouse" }, actionId: "pan" }, { id: "drag-bg-pencil", pattern: { type: "drag", subjectKind: "background", source: "pencil" }, actionId: "lasso-select" }, { id: "space-drag-pan", pattern: { type: "drag", heldKeys: { byCode: { Space: true } } }, actionId: "pan" }, { id: "shift-drag-bg", pattern: { type: "drag", subjectKind: "background", modifiers: { shift: true } }, actionId: "rect-select" }, // Right/middle drag — no-ops by default { id: "rdrag-node", pattern: { type: "drag", subjectKind: "node", button: 2 }, actionId: "none" }, { id: "rdrag-bg", pattern: { type: "drag", subjectKind: "background", button: 2 }, actionId: "none" }, // --- Long-press --- { id: "lp-node", pattern: { type: "long-press", subjectKind: "node" }, actionId: "context-menu" }, { id: "lp-bg-finger", pattern: { type: "long-press", subjectKind: "background", source: "finger" }, actionId: "create-node" }, // --- Pinch / scroll --- { id: "pinch-bg", pattern: { type: "pinch", subjectKind: "background" }, actionId: "zoom" }, { id: "scroll-any", pattern: { type: "scroll" }, actionId: "zoom" }, { id: "pinch-node", pattern: { type: "pinch", subjectKind: "node" }, actionId: "split-node" } ]; // src/gestures/keyboard-bindings.ts var isCommandShortcut = (ctx) => !ctx.commandLineVisible; var KEYBOARD_BINDINGS = [ { id: "slash-open-command-line", pattern: { kind: "key", phase: "down", code: "Slash", modifiers: { ctrl: false, meta: false, shift: false } }, actionId: "open-command-line", when: isCommandShortcut, consumeInput: true }, { id: "escape-fallback", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "escape-input", when: isCommandShortcut, consumeInput: true }, { id: "delete-selection", pattern: { kind: "key", phase: "down", key: "Delete" }, actionId: "delete-selection", when: isCommandShortcut, consumeInput: true }, { id: "backspace-delete-selection", pattern: { kind: "key", phase: "down", key: "Backspace", modifiers: { ctrl: false, meta: false } }, actionId: "delete-selection", when: isCommandShortcut, consumeInput: true }, // Search { id: "open-search-ctrl-f", pattern: { kind: "key", phase: "down", key: "f", modifiers: { ctrl: true, shift: false } }, actionId: "open-search", consumeInput: true }, { id: "open-search-meta-f", pattern: { kind: "key", phase: "down", key: "f", modifiers: { meta: true, shift: false } }, actionId: "open-search", consumeInput: true }, // Copy / Cut / Paste { id: "copy-selection-ctrl-c", pattern: { kind: "key", phase: "down", key: "c", modifiers: { ctrl: true, shift: false } }, actionId: "copy-selection", when: isCommandShortcut, consumeInput: true }, { id: "copy-selection-meta-c", pattern: { kind: "key", phase: "down", key: "c", modifiers: { meta: true, shift: false } }, actionId: "copy-selection", when: isCommandShortcut, consumeInput: true }, { id: "cut-selection-ctrl-x", pattern: { kind: "key", phase: "down", key: "x", modifiers: { ctrl: true, shift: false } }, actionId: "cut-selection", when: isCommandShortcut, consumeInput: true }, { id: "cut-selection-meta-x", pattern: { kind: "key", phase: "down", key: "x", modifiers: { meta: true, shift: false } }, actionId: "cut-selection", when: isCommandShortcut, consumeInput: true }, { id: "paste-selection-ctrl-v", pattern: { kind: "key", phase: "down", key: "v", modifiers: { ctrl: true, shift: false } }, actionId: "paste-selection", when: isCommandShortcut, consumeInput: true }, { id: "paste-selection-meta-v", pattern: { kind: "key", phase: "down", key: "v", modifiers: { meta: true, shift: false } }, actionId: "paste-selection", when: isCommandShortcut, consumeInput: true }, // Duplicate { id: "duplicate-selection-ctrl-d", pattern: { kind: "key", phase: "down", key: "d", modifiers: { ctrl: true, shift: false } }, actionId: "duplicate-selection", when: isCommandShortcut, consumeInput: true }, { id: "duplicate-selection-meta-d", pattern: { kind: "key", phase: "down", key: "d", modifiers: { meta: true, shift: false } }, actionId: "duplicate-selection", when: isCommandShortcut, consumeInput: true }, // Select All { id: "select-all-ctrl-a", pattern: { kind: "key", phase: "down", key: "a", modifiers: { ctrl: true, shift: false } }, actionId: "select-all", when: isCommandShortcut, consumeInput: true }, { id: "select-all-meta-a", pattern: { kind: "key", phase: "down", key: "a", modifiers: { meta: true, shift: false } }, actionId: "select-all", when: isCommandShortcut, consumeInput: true }, // Merge { id: "merge-selection-ctrl-m", pattern: { kind: "key", phase: "down", key: "m", modifiers: { ctrl: true, shift: false } }, actionId: "merge-selection", when: isCommandShortcut, consumeInput: true }, { id: "merge-selection-meta-m", pattern: { kind: "key", phase: "down", key: "m", modifiers: { meta: true, shift: false } }, actionId: "merge-selection", when: isCommandShortcut, consumeInput: true }, // Undo / Redo { id: "undo-ctrl-z", pattern: { kind: "key", phase: "down", key: "z", modifiers: { ctrl: true, shift: false } }, actionId: "undo", when: isCommandShortcut, consumeInput: true }, { id: "undo-meta-z", pattern: { kind: "key", phase: "down", key: "z", modifiers: { meta: true, shift: false } }, actionId: "undo", when: isCommandShortcut, consumeInput: true }, { id: "redo-ctrl-shift-z", pattern: { kind: "key", phase: "down", key: "z", modifiers: { ctrl: true, shift: true } }, actionId: "redo", when: isCommandShortcut, consumeInput: true }, { id: "redo-meta-shift-z", pattern: { kind: "key", phase: "down", key: "z", modifiers: { meta: true, shift: true } }, actionId: "redo", when: isCommandShortcut, consumeInput: true }, { id: "redo-ctrl-y", pattern: { kind: "key", phase: "down", key: "y", modifiers: { ctrl: true } }, actionId: "redo", when: isCommandShortcut, consumeInput: true }, { id: "redo-meta-y", pattern: { kind: "key", phase: "down", key: "y", modifiers: { meta: true } }, actionId: "redo", when: isCommandShortcut, consumeInput: true } ]; // src/gestures/pointer-contexts.ts var DEFAULT_CONTEXT = { id: "default", priority: 100, enabled: true, bindings: [...POINTER_BINDINGS, ...KEYBOARD_BINDINGS] }; var PICK_NODE_CONTEXT = { id: "input-mode:pickNode", priority: 5, enabled: true, bindings: [{ id: "pick-tap-node", pattern: { type: "tap", subjectKind: "node" }, actionId: "resolve-pick-node", consumeInput: true }, { id: "pick-cancel-bg", pattern: { type: "tap", subjectKind: "background" }, actionId: "cancel-pick", consumeInput: true }, { id: "pick-cancel-key", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "cancel-pick", consumeInput: true }] }; var PICK_NODES_CONTEXT = { id: "input-mode:pickNodes", priority: 5, enabled: true, bindings: [{ id: "pick-tap-node", pattern: { type: "tap", subjectKind: "node" }, actionId: "resolve-pick-node", consumeInput: true }, { id: "pick-done", pattern: { type: "double-tap", subjectKind: "background" }, actionId: "finish-pick-nodes", consumeInput: true }, { id: "pick-cancel-bg", pattern: { type: "tap", subjectKind: "background", button: 2 }, actionId: "cancel-pick", consumeInput: true }, { id: "pick-cancel-key", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "cancel-pick", consumeInput: true }] }; var PICK_POINT_CONTEXT = { id: "input-mode:pickPoint", priority: 5, enabled: true, bindings: [{ id: "pick-tap", pattern: { type: "tap" }, actionId: "resolve-pick-point", consumeInput: true }, { id: "pick-cancel-key", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "cancel-pick", consumeInput: true }] }; var INPUT_MODE_CONTEXTS = { normal: null, pickNode: PICK_NODE_CONTEXT, pickNodes: PICK_NODES_CONTEXT, pickPoint: PICK_POINT_CONTEXT, text: null, select: null }; // src/gestures/contexts.ts var PALM_REJECTION_CONTEXT = { id: "palm-rejection", priority: 0, enabled: true, bindings: [{ id: "pr-finger-tap", pattern: { type: "tap", source: "finger" }, actionId: "none", when: (ctx) => ctx.isStylusActive, consumeInput: true }, { id: "pr-finger-dtap", pattern: { type: "double-tap", source: "finger" }, actionId: "none", when: (ctx) => ctx.isStylusActive, consumeInput: true }, { id: "pr-finger-ttap", pattern: { type: "triple-tap", source: "finger" }, actionId: "none", when: (ctx) => ctx.isStylusActive, consumeInput: true }, { id: "pr-finger-lp", pattern: { type: "long-press", source: "finger" }, actionId: "none", when: (ctx) => ctx.isStylusActive, consumeInput: true }, { id: "pr-finger-drag-node", pattern: { type: "drag", subjectKind: "node", source: "finger" }, actionId: "pan", when: (ctx) => ctx.isStylusActive, consumeInput: true }] }; var ACTIVE_INTERACTION_CONTEXT = { id: "active-interaction", priority: 15, enabled: true, bindings: [{ id: "escape-cancel-active-interaction", pattern: { kind: "key", phase: "down", key: "Escape" }, actionId: "cancel-active-input", when: (ctx) => ctx.isDragging || ctx.isSplitting || Boolean(ctx.custom.isSelecting) || Boolean(ctx.custom.isCreatingEdge), consumeInput: true }] }; // src/gestures/useGuardContext.ts init_selection_store(); init_graph_store(); init_input_store(); init_interaction_store(); init_selection_path_store(); init_search_store(); import { useRef as useRef13 } from "react"; import { useAtomValue as useAtomValue22 } from "jotai"; init_types2(); function useGuardContext(heldKeys = NO_HELD_KEYS) { const isStylusActive = useAtomValue22(isStylusActiveAtom); const fingerCount = useAtomValue22(fingerCountAtom); const draggingNodeId = useAtomValue22(draggingNodeIdAtom); const focusedNodeId = useAtomValue22(focusedNodeIdAtom); const selectedNodeIds = useAtomValue22(selectedNodeIdsAtom); const inputMode = useAtomValue22(inputModeAtom); const keyboardInteractionMode = useAtomValue22(keyboardInteractionModeAtom); const selectionPath = useAtomValue22(selectionPathAtom); const edgeCreation = useAtomValue22(edgeCreationAtom); const isSearchActive = useAtomValue22(isFilterActiveAtom); const commandLineVisible = useAtomValue22(commandLineVisibleAtom); const guardRef = useRef13({ isStylusActive: false, fingerCount: 0, isDragging: false, isResizing: false, isSplitting: false, inputMode: { type: "normal" }, keyboardInteractionMode: "navigate", selectedNodeIds: /* @__PURE__ */ new Set(), focusedNodeId: null, isSearchActive: false, commandLineVisible: false, heldKeys: NO_HELD_KEYS, custom: {} }); guardRef.current = { isStylusActive, fingerCount, isDragging: draggingNodeId !== null, isResizing: false, isSplitting: false, inputMode, keyboardInteractionMode, selectedNodeIds, focusedNodeId, isSearchActive, commandLineVisible, heldKeys, custom: { isSelecting: selectionPath !== null, isCreatingEdge: edgeCreation.isCreating } }; return guardRef; } // src/gestures/useInertia.ts import { useRef as useRef14, useEffect as useEffect6 } from "react"; // src/gestures/inertia.ts var PAN_FRICTION = 0.92; var ZOOM_FRICTION = 0.88; var MIN_VELOCITY = 0.5; var ZOOM_SNAP_THRESHOLD = 0.03; var VELOCITY_SAMPLE_COUNT = 5; var VelocitySampler = class { constructor(maxSamples = VELOCITY_SAMPLE_COUNT) { __publicField(this, "samples", []); this.maxSamples = maxSamples; } sample(vx, vy, t) { if (this.samples.length >= this.maxSamples) { this.samples.shift(); } this.samples.push({ vx, vy, t }); } average() { const n = this.samples.length; if (n === 0) return { x: 0, y: 0 }; let sx = 0; let sy = 0; for (const s of this.samples) { sx += s.vx; sy += s.vy; } return { x: sx / n, y: sy / n }; } reset() { this.samples.length = 0; } }; var PanInertia = class { constructor(velocity, friction = PAN_FRICTION, minVelocity = MIN_VELOCITY) { this.friction = friction; this.minVelocity = minVelocity; this.vx = velocity.x; this.vy = velocity.y; } /** * Advance one frame. Returns the velocity delta to apply, * or null if below threshold (animation complete). */ tick() { this.vx *= this.friction; this.vy *= this.friction; if (Math.abs(this.vx) < this.minVelocity && Math.abs(this.vy) < this.minVelocity) { return null; } return { x: this.vx, y: this.vy }; } }; var ZoomInertia = class { constructor(velocity, origin, friction = ZOOM_FRICTION, minVelocity = MIN_VELOCITY, snapThreshold = ZOOM_SNAP_THRESHOLD) { this.origin = origin; this.friction = friction; this.minVelocity = minVelocity; this.snapThreshold = snapThreshold; this.v = velocity; } /** * Advance one frame. Returns zoom delta + origin, * or null if below threshold. */ tick(currentZoom) { this.v *= this.friction; if (Math.abs(this.v) < this.minVelocity) { if (Math.abs(currentZoom - 1) < this.snapThreshold) { const snapDelta = 1 - currentZoom; return Math.abs(snapDelta) > 1e-3 ? { delta: snapDelta, origin: this.origin } : null; } return null; } return { delta: this.v, origin: this.origin }; } }; // src/gestures/useInertia.ts var ZOOM_SNAP_THRESHOLD2 = 0.03; var INERTIA_MIN_SPEED = 2; var VELOCITY_SAMPLE_COUNT2 = 5; function snapZoom(z) { return Math.abs(z - 1) < ZOOM_SNAP_THRESHOLD2 ? 1 : z; } function useInertia() { const panInertiaRef = useRef14(null); const zoomInertiaRef = useRef14(null); const panSamplerRef = useRef14(new VelocitySampler(VELOCITY_SAMPLE_COUNT2)); const zoomSamplerRef = useRef14(new VelocitySampler(VELOCITY_SAMPLE_COUNT2)); const pinchPrevOrigin = useRef14(null); const lastPinchZoom = useRef14(null); const cancelPan = () => { if (panInertiaRef.current) { cancelAnimationFrame(panInertiaRef.current.anim); panInertiaRef.current = null; } }; const cancelZoom = () => { if (zoomInertiaRef.current) { cancelAnimationFrame(zoomInertiaRef.current.anim); zoomInertiaRef.current = null; } }; const cancelAll = () => { cancelPan(); cancelZoom(); }; const startPanInertia = (velocity, setPan) => { cancelPan(); const engine = new PanInertia(velocity); const animate = () => { const tick = engine.tick(); if (!tick) { panInertiaRef.current = null; return; } setPan((prev) => ({ x: prev.x + tick.x, y: prev.y + tick.y })); panInertiaRef.current.anim = requestAnimationFrame(animate); }; panInertiaRef.current = { anim: requestAnimationFrame(animate), engine }; }; const startZoomInertia = (velocity_0, origin, currentZoom, minZoom, maxZoom, setZoom, setPan_0) => { cancelZoom(); const engine_0 = new ZoomInertia(velocity_0, origin); const animate_0 = () => { const tick_0 = engine_0.tick(currentZoom); if (!tick_0) { zoomInertiaRef.current = null; return; } setZoom((prevZoom) => { const newZoom = Math.max(minZoom, Math.min(maxZoom, prevZoom + tick_0.delta)); setPan_0((prevPan) => { const worldX = (tick_0.origin.x - prevPan.x) / prevZoom; const worldY = (tick_0.origin.y - prevPan.y) / prevZoom; return { x: tick_0.origin.x - worldX * newZoom, y: tick_0.origin.y - worldY * newZoom }; }); return snapZoom(newZoom); }); zoomInertiaRef.current.anim = requestAnimationFrame(animate_0); }; zoomInertiaRef.current = { anim: requestAnimationFrame(animate_0), engine: engine_0 }; }; useEffect6(() => cancelAll, []); return { startPanInertia, startZoomInertia, cancelAll, cancelPan, cancelZoom, panSampler: panSamplerRef.current, zoomSampler: zoomSamplerRef.current, pinchPrevOrigin, lastPinchZoom }; } // src/gestures/useWheelZoom.ts function createWheelHandler(config) { return ({ event, pinching, delta: [, dy], memo }) => { if (memo === true || pinching || !config.enableZoom || !config.ref.current) return; const target = event.target; const noDrag = target.closest('[data-no-drag="true"]'); if (noDrag) { const draggableNode = target.closest('[data-draggable-node="true"]'); if (draggableNode) { const nodeId = draggableNode.getAttribute("data-node-id"); if (nodeId && config.selectedNodeIds.has(nodeId)) { let el = target; while (el && noDrag.contains(el)) { if (el.scrollHeight > el.clientHeight) { const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1; const atTop = el.scrollTop <= 1; if (!atBottom && dy > 0 || !atTop && dy < 0 || atBottom && dy < 0 || atTop && dy > 0) { return; } } el = el.parentElement; } } } } event.preventDefault(); const rect = config.ref.current.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; const worldX = (mouseX - config.pan.x) / config.zoom; const worldY = (mouseY - config.pan.y) / config.zoom; const zoomDelta = -dy * config.zoomSensitivity * config.zoom; const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, config.zoom + zoomDelta))); config.setZoom(newZoom); config.setPan({ x: mouseX - worldX * newZoom, y: mouseY - worldY * newZoom }); }; } // src/gestures/usePinchZoom.ts function createPinchHandlers(config) { const onPinchStart = ({ origin: [ox, oy] }) => { if (!config.enableZoom || !config.ref.current) return true; config.inertia.cancelAll(); config.inertia.pinchPrevOrigin.current = { x: ox, y: oy }; config.inertia.zoomSampler.reset(); config.inertia.lastPinchZoom.current = { zoom: config.zoom, t: performance.now() }; return false; }; const onPinch = ({ offset: [d], origin: [ox, oy], event, memo }) => { if (memo === true || !config.enableZoom || !config.ref.current) return; event.preventDefault(); const rect = config.ref.current.getBoundingClientRect(); const pinchX = ox - rect.left; const pinchY = oy - rect.top; let pdx = 0; let pdy = 0; if (config.inertia.pinchPrevOrigin.current) { pdx = ox - config.inertia.pinchPrevOrigin.current.x; pdy = oy - config.inertia.pinchPrevOrigin.current.y; } config.inertia.pinchPrevOrigin.current = { x: ox, y: oy }; const worldX = (pinchX - config.pan.x) / config.zoom; const worldY = (pinchY - config.pan.y) / config.zoom; const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, d))); const now = performance.now(); if (config.inertia.lastPinchZoom.current) { const dt = (now - config.inertia.lastPinchZoom.current.t) / 1e3; if (dt > 0) { const v = (newZoom - config.inertia.lastPinchZoom.current.zoom) / dt; config.inertia.zoomSampler.sample(v, 0, now); } } config.inertia.lastPinchZoom.current = { zoom: newZoom, t: now }; config.setZoom(newZoom); config.setPan({ x: pinchX - worldX * newZoom + pdx, y: pinchY - worldY * newZoom + pdy }); }; const onPinchEnd = () => { const avg = config.inertia.zoomSampler.average(); const perFrameV = avg.x / 60; if (!config.reducedMotion && Math.abs(perFrameV) > 1e-3 && config.ref.current) { const rect = config.ref.current.getBoundingClientRect(); const origin = config.inertia.pinchPrevOrigin.current ?? { x: rect.width / 2, y: rect.height / 2 }; config.inertia.startZoomInertia(perFrameV, { x: origin.x - rect.left, y: origin.y - rect.top }, config.zoom, config.minZoom, config.maxZoom, config.setZoom, config.setPan); } config.inertia.pinchPrevOrigin.current = null; config.inertia.zoomSampler.reset(); config.inertia.lastPinchZoom.current = null; }; return { onPinchStart, onPinch, onPinchEnd }; } // src/gestures/useCanvasGestures.ts init_types2(); function useCanvasGestures({ ref, minZoom = 0.1, maxZoom = 5, zoomSensitivity = 15e-4, enablePan = true, enableZoom = true, mappingIndex: externalIndex, contexts: extraContexts, palmRejection = true, onAction, heldKeys = NO_HELD_KEYS }) { const [panVals, setPan] = useAtom4(panAtom); const [zoomVal, setZoom] = useAtom4(zoomAtom); const setViewportRect = useSetAtom22(viewportRectAtom); const selectedNodeIds = useAtomValue23(selectedNodeIdsAtom); const clearSelection = useSetAtom22(clearSelectionAtom); const screenToWorld = useAtomValue23(screenToWorldAtom); const setPointerDown = useSetAtom22(pointerDownAtom); const setPointerUp = useSetAtom22(pointerUpAtom); const clearPointers = useSetAtom22(clearPointersAtom); const primaryInputSource = useAtomValue23(primaryInputSourceAtom); const reducedMotion = useAtomValue23(prefersReducedMotionAtom); const startSelection = useSetAtom22(startSelectionAtom); const updateSelection = useSetAtom22(updateSelectionAtom); const endSelection = useSetAtom22(endSelectionAtom); const guardRef = useGuardContext(heldKeys); const inertia = useInertia(); const panStartRef = useRef15({ x: 0, y: 0 }); const dragOriginatedOnBg = useRef15(false); const dragSourceRef = useRef15("mouse"); const buttonRef = useRef15(0); const dragIntentRef = useRef15("none"); const timedRunner = useRef15(new TimedStateRunner()); const internalIndex = useMemo(() => { const ctxList = []; if (palmRejection) ctxList.push(PALM_REJECTION_CONTEXT); if (extraContexts) ctxList.push(...extraContexts); ctxList.push(DEFAULT_CONTEXT); return buildMappingIndex(ctxList); }, [palmRejection, extraContexts]); const mappingIndex = externalIndex ?? internalIndex; const isBackgroundTarget = (target) => { if (!ref.current || !target) return false; const child = ref.current.firstChild; return target === ref.current || target === child; }; const makeGestureEvent = (type, phase, subject, screenX, screenY, extra) => { const worldPos = screenToWorld(screenX, screenY); return { kind: "pointer", type, phase, subject, source: dragSourceRef.current, button: buttonRef.current, modifiers: NO_MODIFIERS, heldKeys, screenPosition: { x: screenX, y: screenY }, worldPosition: worldPos, ...extra }; }; const resolveAndDispatch = (event) => { const resolution = resolve(event, mappingIndex, guardRef.current); if (resolution) { dispatch(event, resolution); onAction?.(event, resolution); } return resolution; }; useEffect7(() => { const runner = timedRunner.current; runner.onEmit = (_gestureType) => { }; return () => { runner.destroy(); }; }, []); useEffect7(() => { const el = ref.current; if (!el) { setViewportRect(null); return; } setViewportRect(el.getBoundingClientRect()); const observer = new ResizeObserver((entries) => { for (const entry of entries) { setViewportRect(entry.contentRect); } }); observer.observe(el); return () => { observer.unobserve(el); observer.disconnect(); }; }, [setViewportRect]); useEffect7(() => { return () => { inertia.cancelAll(); timedRunner.current.destroy(); }; }, []); useEffect7(() => { const handleVisChange = () => { if (document.hidden) { inertia.cancelAll(); clearPointers(); timedRunner.current.feed("cancel"); } }; document.addEventListener("visibilitychange", handleVisChange); return () => document.removeEventListener("visibilitychange", handleVisChange); }, [clearPointers]); useGesture2({ onPointerDown: (state) => { if (!ref.current) { dragOriginatedOnBg.current = false; return; } const pe = state.event; const classified = classifyPointer(pe); setPointerDown(classified); dragSourceRef.current = classified.source; buttonRef.current = pe.button ?? 0; inertia.cancelAll(); if (isBackgroundTarget(state.event.target)) { dragOriginatedOnBg.current = true; timedRunner.current.feed("down"); const startX = pe.clientX; const startY = pe.clientY; timedRunner.current.onEmit = (gestureType) => { if (gestureType === "long-press") { const worldPos_0 = screenToWorld(startX, startY); const event_0 = makeGestureEvent("long-press", "instant", { kind: "background" }, startX, startY, { modifiers: extractModifiers(pe), worldPosition: worldPos_0 }); resolveAndDispatch(event_0); } }; } else { dragOriginatedOnBg.current = false; } }, onPointerUp: (state_0) => { const pe_0 = state_0.event; setPointerUp(pe_0.pointerId); }, onDragStart: ({ event: event_1 }) => { if (!dragOriginatedOnBg.current) return; const pe_1 = event_1; const modifiers = pe_1 ? extractModifiers(pe_1) : NO_MODIFIERS; const gestureEvent = makeGestureEvent("drag", "start", { kind: "background" }, pe_1?.clientX ?? 0, pe_1?.clientY ?? 0, { modifiers }); const resolution_0 = resolve(gestureEvent, mappingIndex, guardRef.current); dragIntentRef.current = resolution_0?.actionId ?? "none"; if (dragIntentRef.current === "pan" && enablePan) { panStartRef.current = { ...panVals }; inertia.panSampler.reset(); } else if (dragIntentRef.current === "lasso-select" || dragIntentRef.current === "rect-select") { const worldPos_1 = screenToWorld(pe_1?.clientX ?? 0, pe_1?.clientY ?? 0); startSelection({ type: dragIntentRef.current === "lasso-select" ? "lasso" : "rect", point: worldPos_1 }); } if (resolution_0) { dispatch(gestureEvent, resolution_0); onAction?.(gestureEvent, resolution_0); } }, onDrag: ({ movement: [mx, my], tap, active, pinching, event: event_2, velocity: [vx, vy], direction: [dx, dy] }) => { if (tap && dragOriginatedOnBg.current) { const emitted = timedRunner.current.feed("up"); const pe_2 = event_2; const modifiers_0 = extractModifiers(pe_2); const tapType = emitted ?? "tap"; const gestureEvent_0 = makeGestureEvent(tapType, "instant", { kind: "background" }, pe_2.clientX, pe_2.clientY, { modifiers: modifiers_0 }); const resolution_1 = resolve(gestureEvent_0, mappingIndex, guardRef.current); if (resolution_1) { dispatch(gestureEvent_0, resolution_1); onAction?.(gestureEvent_0, resolution_1); } else { clearSelection(); } return; } if (!tap && active && !pinching && dragOriginatedOnBg.current) { timedRunner.current.feed("move-beyond-threshold"); const intent = dragIntentRef.current; if (intent === "pan" && enablePan) { setPan({ x: panStartRef.current.x + mx, y: panStartRef.current.y + my }); const now = performance.now(); inertia.panSampler.sample(vx * dx, vy * dy, now); } else if (intent === "lasso-select" || intent === "rect-select") { const pe_3 = event_2; const worldPos_2 = screenToWorld(pe_3.clientX, pe_3.clientY); updateSelection(worldPos_2); } } }, onDragEnd: () => { timedRunner.current.feed("up"); const intent_0 = dragIntentRef.current; if (intent_0 === "lasso-select" || intent_0 === "rect-select") { endSelection(); } if (!reducedMotion && dragOriginatedOnBg.current && dragSourceRef.current === "finger" && intent_0 === "pan") { const avg = inertia.panSampler.average(); const speed = Math.sqrt(avg.x ** 2 + avg.y ** 2); if (speed > INERTIA_MIN_SPEED) { inertia.startPanInertia(avg, setPan); } } dragOriginatedOnBg.current = false; dragIntentRef.current = "none"; inertia.panSampler.reset(); }, onWheel: createWheelHandler({ ref, minZoom, maxZoom, zoomSensitivity, enableZoom, zoom: zoomVal, pan: panVals, selectedNodeIds, setZoom, setPan }), ...createPinchHandlers({ ref, minZoom, maxZoom, enableZoom, reducedMotion, zoom: zoomVal, pan: panVals, setZoom, setPan, inertia }) }, { target: ref, eventOptions: { passive: false, capture: true }, drag: { filterTaps: true, tapsThreshold: primaryInputSource === "finger" ? 10 : 5, pointer: { touch: true, keys: false, capture: false, buttons: -1 } }, wheel: {}, pinch: { scaleBounds: () => ({ min: minZoom, max: maxZoom }), from: () => [zoomVal, 0] } }); } // src/gestures/GestureProvider.tsx init_dispatcher(); import React6, { createContext as createContext3, useContext as useContext3, useEffect as useEffect10, useRef as useRef16, useState as useState3 } from "react"; import { useStore as useStore4 } from "jotai"; // src/gestures/useInputModeGestureContext.ts init_interaction_store(); import { c as _c32 } from "react/compiler-runtime"; import { useEffect as useEffect8 } from "react"; import { useAtomValue as useAtomValue24 } from "jotai"; function useInputModeGestureContext(inputSystem) { const $ = _c32(4); const inputMode = useAtomValue24(inputModeAtom); let t0; let t1; if ($[0] !== inputMode.type || $[1] !== inputSystem) { t0 = () => { const modeType = inputMode.type; const ctx = INPUT_MODE_CONTEXTS[modeType] ?? null; if (ctx) { inputSystem.pushContext(ctx); } else { for (const key of Object.keys(INPUT_MODE_CONTEXTS)) { const existing = INPUT_MODE_CONTEXTS[key]; if (existing) { inputSystem.removeContext(existing.id); } } } return () => { if (ctx) { inputSystem.removeContext(ctx.id); } }; }; t1 = [inputMode.type, inputSystem]; $[0] = inputMode.type; $[1] = inputSystem; $[2] = t0; $[3] = t1; } else { t0 = $[2]; t1 = $[3]; } useEffect8(t0, t1); } // src/gestures/useRegisterInputActions.ts init_core(); import { c as _c33 } from "react/compiler-runtime"; import { useEffect as useEffect9 } from "react"; import { useStore as useStore3 } from "jotai"; init_search_store(); init_dispatcher(); // src/gestures/input-action-helpers.ts init_core(); init_search_store(); init_interaction_store(); init_selection_path_store(); // src/gestures/modifier-helpers.ts init_core(); init_types2(); function isRepeatBlocked(event) { return isKeyInputEvent(event) && event.repeat; } function getSelectedNodeIds(store) { return Array.from(store.get(selectedNodeIdsAtom)); } function clearSelectionState(store) { store.set(clearSelectionAtom); store.set(clearEdgeSelectionAtom); } function resolveFocusableNodeId(store) { const focusedNodeId = store.get(focusedNodeIdAtom); if (focusedNodeId) return focusedNodeId; const selected = getSelectedNodeIds(store); return selected[0] ?? null; } function getCurrentSubject(store) { const draggingNodeId = store.get(draggingNodeIdAtom); if (draggingNodeId) return { kind: "node", nodeId: draggingNodeId }; const edgeCreation = store.get(edgeCreationAtom); if (edgeCreation.isCreating && edgeCreation.sourceNodeId) { return { kind: "node", nodeId: edgeCreation.sourceNodeId }; } const focusedNodeId = resolveFocusableNodeId(store); if (focusedNodeId) return { kind: "node", nodeId: focusedNodeId }; const selectedEdgeId = store.get(selectedEdgeIdAtom); if (selectedEdgeId) return { kind: "edge", edgeId: selectedEdgeId }; return { kind: "background" }; } function updateKeySubject(event, store) { if (event.kind === "key" && event.subject === void 0) { event.subject = getCurrentSubject(store); } } // src/gestures/gesture-classification.ts init_core(); function findNearestNode(currentNodeId, store, direction) { const nodes = store.get(uiNodesAtom); if (nodes.length === 0) return null; if (!currentNodeId) { const sorted = [...nodes].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x); return sorted[0]?.id ?? null; } const currentNode = nodes.find((node) => node.id === currentNodeId); if (!currentNode) return nodes[0]?.id ?? null; const cx = currentNode.position.x + (currentNode.width ?? 200) / 2; const cy = currentNode.position.y + (currentNode.height ?? 100) / 2; let bestId = null; let bestScore = Number.POSITIVE_INFINITY; for (const candidate of nodes) { if (candidate.id === currentNode.id) continue; const nx = candidate.position.x + (candidate.width ?? 200) / 2; const ny = candidate.position.y + (candidate.height ?? 100) / 2; const dx = nx - cx; const dy = ny - cy; let isValid = false; switch (direction) { case "right": isValid = dx > 0; break; case "left": isValid = dx < 0; break; case "down": isValid = dy > 0; break; case "up": isValid = dy < 0; break; } if (!isValid) continue; const dist = Math.sqrt(dx * dx + dy * dy); const isHorizontal = direction === "left" || direction === "right"; const perpendicularDistance = isHorizontal ? Math.abs(dy) : Math.abs(dx); const score = dist + perpendicularDistance * 0.5; if (score < bestScore) { bestScore = score; bestId = candidate.id; } } return bestId; } function cycleFocus(store, direction) { const nodes = store.get(uiNodesAtom); if (nodes.length === 0) return; const focusedNodeId = store.get(focusedNodeIdAtom); const sorted = [...nodes].sort((a, b) => a.zIndex - b.zIndex); const currentIdx = focusedNodeId ? sorted.findIndex((node) => node.id === focusedNodeId) : -1; const nextIdx = direction === -1 ? currentIdx <= 0 ? sorted.length - 1 : currentIdx - 1 : (currentIdx + 1) % sorted.length; store.set(setFocusedNodeAtom, sorted[nextIdx].id); } function navigateFocus(store, direction) { const nextId = findNearestNode(resolveFocusableNodeId(store), store, direction); if (nextId) { store.set(setFocusedNodeAtom, nextId); } } function activateFocusedNode(store, enterManipulate) { const focusedNodeId = resolveFocusableNodeId(store); if (!focusedNodeId) return; store.set(setFocusedNodeAtom, focusedNodeId); store.set(selectSingleNodeAtom, focusedNodeId); if (enterManipulate) { store.set(setKeyboardInteractionModeAtom, "manipulate"); } } // src/gestures/input-action-helpers.ts var EMPTY_EDGE_CREATION = { isCreating: false, sourceNodeId: null, sourceNodePosition: null, targetPosition: null, hoveredTargetNodeId: null, sourceHandle: null, targetHandle: null, sourcePort: null, targetPort: null, snappedTargetPosition: null }; function nudgeSelection(store, dx, dy, label) { const selected = getSelectedNodeIds(store); if (selected.length === 0) { const focused = store.get(focusedNodeIdAtom); if (focused) { store.set(selectSingleNodeAtom, focused); } } const nodeIds = getSelectedNodeIds(store); if (nodeIds.length === 0) return; const graph = store.get(graphAtom).copy(); store.set(pushHistoryAtom, label); for (const nodeId of nodeIds) { if (!graph.hasNode(nodeId)) continue; const x = graph.getNodeAttribute(nodeId, "x"); const y = graph.getNodeAttribute(nodeId, "y"); graph.setNodeAttribute(nodeId, "x", x + dx); graph.setNodeAttribute(nodeId, "y", y + dy); } store.set(graphAtom, graph); store.set(nodePositionUpdateCounterAtom, (count) => count + 1); } function deleteSelection(store) { const selectedEdgeId = store.get(selectedEdgeIdAtom); if (selectedEdgeId) { store.set(optimisticDeleteEdgeAtom, { edgeKey: selectedEdgeId }); store.set(clearEdgeSelectionAtom); } const selectedNodeIds = getSelectedNodeIds(store); if (selectedNodeIds.length === 0) return; store.set(pushHistoryAtom, selectedNodeIds.length > 1 ? `Delete ${selectedNodeIds.length} nodes` : "Delete node"); const focusedNodeId = store.get(focusedNodeIdAtom); for (const nodeId of selectedNodeIds) { store.set(optimisticDeleteNodeAtom, { nodeId }); } if (focusedNodeId && selectedNodeIds.includes(focusedNodeId)) { store.set(setFocusedNodeAtom, null); } store.set(clearSelectionAtom); } function cutSelection(store) { const selectedNodeIds = getSelectedNodeIds(store); if (selectedNodeIds.length === 0) return; store.set(copyToClipboardAtom, selectedNodeIds); deleteSelection(store); } function cancelActiveInteraction(store) { let didCancel = false; if (store.get(draggingNodeIdAtom) !== null) { store.set(endNodeDragAtom); didCancel = true; } if (store.get(edgeCreationAtom).isCreating) { store.set(edgeCreationAtom, EMPTY_EDGE_CREATION); didCancel = true; } if (store.get(inputModeAtom).type === "pickPoint" || store.get(inputModeAtom).type === "pickNode" || store.get(inputModeAtom).type === "pickNodes") { store.set(resetInputModeAtom); didCancel = true; } const selectionPath = store.get(selectionPathAtom); if (selectionPath !== null) { store.set(cancelSelectionAtom); didCancel = true; } return didCancel; } function escapeInput(store) { if (cancelActiveInteraction(store)) return; if (store.get(keyboardInteractionModeAtom) === "manipulate") { store.set(resetKeyboardInteractionModeAtom); return; } if (store.get(isFilterActiveAtom)) { store.set(clearSearchAtom); return; } if (store.get(commandLineVisibleAtom)) { store.set(closeCommandLineAtom); return; } if (store.get(selectedNodeIdsAtom).size > 0 || store.get(selectedEdgeIdAtom) !== null) { clearSelectionState(store); } } function resolvePickNode(event, store) { updateKeySubject(event, store); if (event.subject?.kind !== "node") return; if (store.get(inputModeAtom).type === "pickNodes") { store.set(toggleNodeInSelectionAtom, event.subject.nodeId); store.set(setFocusedNodeAtom, event.subject.nodeId); return; } store.set(provideInputAtom, event.subject.nodeId); store.set(resetInputModeAtom); } function finishPickNodes(store) { store.set(provideInputAtom, getSelectedNodeIds(store)); store.set(resetInputModeAtom); } function resolvePickPoint(event, store) { if (event.kind !== "pointer") return; store.set(provideInputAtom, event.worldPosition); store.set(resetInputModeAtom); } function selectAll(store) { clearSelectionState(store); store.set(addNodesToSelectionAtom, store.get(nodeKeysAtom)); } // src/gestures/useRegisterInputActions.ts function register(actionId, handler) { registerAction2(actionId, handler); return () => unregisterAction2(actionId); } function useRegisterInputActions() { const $ = _c33(3); const store = useStore3(); let t0; let t1; if ($[0] !== store) { t0 = () => { const unregister = [register("select-node", (event) => { updateKeySubject(event, store); if (event.subject?.kind !== "node") { return; } store.set(setFocusedNodeAtom, event.subject.nodeId); if (event.kind === "key") { store.set(selectSingleNodeAtom, event.subject.nodeId); } }), register("select-edge", (event_0) => { updateKeySubject(event_0, store); if (event_0.subject?.kind !== "edge") { return; } store.set(selectEdgeAtom, event_0.subject.edgeId); }), register("toggle-selection", (event_1) => { updateKeySubject(event_1, store); if (event_1.subject?.kind !== "node") { return; } store.set(setFocusedNodeAtom, event_1.subject.nodeId); if (event_1.kind === "key") { store.set(toggleNodeInSelectionAtom, event_1.subject.nodeId); } }), register("clear-selection", () => { clearSelectionState(store); }), register("select-all", (event_2) => { if (isRepeatBlocked(event_2)) { return; } selectAll(store); }), register("fit-to-view", (event_3) => { updateKeySubject(event_3, store); if (event_3.subject?.kind === "node") { store.set(centerOnNodeAtom, event_3.subject.nodeId); } }), register("fit-all-to-view", () => { store.set(fitToBoundsAtom, { mode: "graph" }); }), register("toggle-lock", (event_4) => { updateKeySubject(event_4, store); if (event_4.subject?.kind !== "node") { return; } if (store.get(lockedNodeIdAtom) === event_4.subject.nodeId) { store.set(unlockNodeAtom); } else { store.set(lockNodeAtom, { nodeId: event_4.subject.nodeId }); } }), register("open-command-line", (event_5) => { if (isRepeatBlocked(event_5)) { return; } store.set(openCommandLineAtom); }), register("open-search", (event_6) => { if (isRepeatBlocked(event_6)) { return; } store.set(openCommandLineAtom); }), register("search-next-result", () => { store.set(nextSearchResultAtom); }), register("search-prev-result", () => { store.set(prevSearchResultAtom); }), register("copy-selection", (event_7) => { if (isRepeatBlocked(event_7)) { return; } const selectedNodeIds = getSelectedNodeIds(store); if (selectedNodeIds.length > 0) { store.set(copyToClipboardAtom, selectedNodeIds); } }), register("cut-selection", (event_8) => { if (isRepeatBlocked(event_8)) { return; } cutSelection(store); }), register("paste-selection", (event_9) => { if (isRepeatBlocked(event_9)) { return; } store.set(pasteFromClipboardAtom); }), register("duplicate-selection", (event_10) => { if (isRepeatBlocked(event_10)) { return; } store.set(duplicateSelectionAtom); }), register("merge-selection", (event_11) => { if (isRepeatBlocked(event_11)) { return; } const nodeIds = getSelectedNodeIds(store); if (nodeIds.length >= 2) { store.set(mergeNodesAtom, { nodeIds }); } }), register("delete-selection", (event_12) => { if (isRepeatBlocked(event_12)) { return; } deleteSelection(store); }), register("undo", (event_13) => { if (isRepeatBlocked(event_13)) { return; } store.set(undoAtom); }), register("redo", (event_14) => { if (isRepeatBlocked(event_14)) { return; } store.set(redoAtom); }), register("cancel-active-input", () => { cancelActiveInteraction(store); }), register("escape-input", () => { escapeInput(store); }), register("navigate-focus-up", () => { navigateFocus(store, "up"); }), register("navigate-focus-down", () => { navigateFocus(store, "down"); }), register("navigate-focus-left", () => { navigateFocus(store, "left"); }), register("navigate-focus-right", () => { navigateFocus(store, "right"); }), register("cycle-focus-forward", (event_15) => { if (isRepeatBlocked(event_15)) { return; } cycleFocus(store, 1); }), register("cycle-focus-backward", (event_16) => { if (isRepeatBlocked(event_16)) { return; } cycleFocus(store, -1); }), register("activate-focused-node", (event_17) => { if (isRepeatBlocked(event_17)) { return; } activateFocusedNode(store, false); }), register("enter-keyboard-manipulate-mode", (event_18) => { if (isRepeatBlocked(event_18)) { return; } activateFocusedNode(store, true); }), register("exit-keyboard-manipulate-mode", () => { store.set(resetKeyboardInteractionModeAtom); }), register("nudge-selection-up", () => { nudgeSelection(store, 0, -10, "Nudge selection"); }), register("nudge-selection-down", () => { nudgeSelection(store, 0, 10, "Nudge selection"); }), register("nudge-selection-left", () => { nudgeSelection(store, -10, 0, "Nudge selection"); }), register("nudge-selection-right", () => { nudgeSelection(store, 10, 0, "Nudge selection"); }), register("nudge-selection-up-large", () => { nudgeSelection(store, 0, -50, "Nudge selection"); }), register("nudge-selection-down-large", () => { nudgeSelection(store, 0, 50, "Nudge selection"); }), register("nudge-selection-left-large", () => { nudgeSelection(store, -50, 0, "Nudge selection"); }), register("nudge-selection-right-large", () => { nudgeSelection(store, 50, 0, "Nudge selection"); }), register("resolve-pick-node", (event_19) => { resolvePickNode(event_19, store); }), register("finish-pick-nodes", () => { finishPickNodes(store); }), register("resolve-pick-point", (event_20) => { resolvePickPoint(event_20, store); }), register("cancel-pick", () => { store.set(resetInputModeAtom); })]; return () => { for (const cleanup of unregister) { cleanup(); } }; }; t1 = [store]; $[0] = store; $[1] = t0; $[2] = t1; } else { t0 = $[1]; t1 = $[2]; } useEffect9(t0, t1); } // src/gestures/useGestureSystem.ts import { c as _c34 } from "react/compiler-runtime"; import { useState as useState2 } from "react"; init_types2(); function useInputSystem(t0) { const $ = _c34(19); const config = t0 === void 0 ? {} : t0; const { contexts: staticContexts, palmRejection: t1 } = config; const initialPR = t1 === void 0 ? true : t1; let t2; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = []; $[0] = t2; } else { t2 = $[0]; } const [dynamicContexts, setDynamicContexts] = useState2(t2); const [palmRejection, setPalmRejection] = useState2(initialPR); const [heldKeys, setHeldKeys] = useState2(NO_HELD_KEYS); const all = []; if (palmRejection) { all.push(PALM_REJECTION_CONTEXT); } all.push(ACTIVE_INTERACTION_CONTEXT, SEARCH_CONTEXT, KEYBOARD_MANIPULATE_CONTEXT, KEYBOARD_NAVIGATE_CONTEXT); if (staticContexts) { all.push(...staticContexts); } all.push(...dynamicContexts); all.push(DEFAULT_CONTEXT); const mappingIndex = buildMappingIndex(all); let t3; if ($[1] !== setDynamicContexts) { t3 = (ctx) => { setDynamicContexts((prev) => { const filtered = prev.filter((c) => c.id !== ctx.id); return [...filtered, ctx]; }); }; $[1] = setDynamicContexts; $[2] = t3; } else { t3 = $[2]; } const pushContext = t3; let t4; if ($[3] !== setDynamicContexts) { t4 = (id) => { setDynamicContexts((prev_0) => prev_0.filter((c_0) => c_0.id !== id)); }; $[3] = setDynamicContexts; $[4] = t4; } else { t4 = $[4]; } const removeContext = t4; let t5; if ($[5] !== setDynamicContexts) { t5 = (id_0, enabled) => { setDynamicContexts((prev_1) => prev_1.map((c_1) => c_1.id === id_0 ? { ...c_1, enabled } : c_1)); }; $[5] = setDynamicContexts; $[6] = t5; } else { t5 = $[6]; } const setContextEnabled = t5; let t6; if ($[7] !== setHeldKeys) { t6 = () => { setHeldKeys(NO_HELD_KEYS); }; $[7] = setHeldKeys; $[8] = t6; } else { t6 = $[8]; } const clearHeldKeys = t6; let t7; if ($[9] !== clearHeldKeys || $[10] !== heldKeys || $[11] !== mappingIndex || $[12] !== palmRejection || $[13] !== pushContext || $[14] !== removeContext || $[15] !== setContextEnabled || $[16] !== setHeldKeys || $[17] !== setPalmRejection) { t7 = { mappingIndex, pushContext, removeContext, setContextEnabled, palmRejection, setPalmRejection, heldKeys, setHeldKeys, clearHeldKeys }; $[9] = clearHeldKeys; $[10] = heldKeys; $[11] = mappingIndex; $[12] = palmRejection; $[13] = pushContext; $[14] = removeContext; $[15] = setContextEnabled; $[16] = setHeldKeys; $[17] = setPalmRejection; $[18] = t7; } else { t7 = $[18]; } return t7; } var useGestureSystem = useInputSystem; // src/gestures/gesture-provider-utils.ts init_core(); init_search_store(); init_selection_path_store(); function isEditableTarget(target) { if (!(target instanceof HTMLElement)) { return false; } if (target.isContentEditable) { return true; } const editable = target.closest('input, textarea, [contenteditable="true"], [data-no-canvas-keyboard="true"]'); return editable !== null; } function setHeldKeyValue(current, key, isHeld) { const previous = current[key] ?? false; if (previous === isHeld) { return current; } const next = { ...current }; if (isHeld) { next[key] = true; } else { delete next[key]; } return next; } function applyHeldKeyDelta(event, current) { const isHeld = event.type === "keydown"; const nextByKey = setHeldKeyValue(current.byKey, event.key, isHeld); const nextByCode = setHeldKeyValue(current.byCode, event.code, isHeld); if (nextByKey === current.byKey && nextByCode === current.byCode) { return current; } return { byKey: nextByKey, byCode: nextByCode }; } function getCurrentSubject2(store) { const draggingNodeId = store.get(draggingNodeIdAtom); if (draggingNodeId) { return { kind: "node", nodeId: draggingNodeId }; } const edgeCreation = store.get(edgeCreationAtom); if (edgeCreation.isCreating && edgeCreation.sourceNodeId) { return { kind: "node", nodeId: edgeCreation.sourceNodeId }; } const focusedNodeId = store.get(focusedNodeIdAtom); if (focusedNodeId) { return { kind: "node", nodeId: focusedNodeId }; } return { kind: "background" }; } function getSubjectPosition(store, root, subject) { const worldToScreen = store.get(worldToScreenAtom); if (subject.kind === "node") { const node = store.get(uiNodesAtom).find((entry) => entry.id === subject.nodeId); if (!node) { return {}; } const worldPosition = { x: node.position.x + (node.width ?? 200) / 2, y: node.position.y + (node.height ?? 100) / 2 }; return { worldPosition, screenPosition: worldToScreen(worldPosition.x, worldPosition.y) }; } if (subject.kind === "background" && root) { const rect = root.getBoundingClientRect(); const screenPosition = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; return { screenPosition, worldPosition: store.get(screenToWorldAtom)(screenPosition.x, screenPosition.y) }; } return {}; } function buildGuardContext(store, system) { const heldKeys = "heldKeys" in system ? system.heldKeys : system; const edgeCreation = store.get(edgeCreationAtom); return { isStylusActive: store.get(isStylusActiveAtom), fingerCount: store.get(fingerCountAtom), isDragging: store.get(draggingNodeIdAtom) !== null, isResizing: false, isSplitting: false, inputMode: store.get(inputModeAtom), keyboardInteractionMode: store.get(keyboardInteractionModeAtom), selectedNodeIds: store.get(selectedNodeIdsAtom), focusedNodeId: store.get(focusedNodeIdAtom), isSearchActive: store.get(isFilterActiveAtom), commandLineVisible: store.get(commandLineVisibleAtom), heldKeys, custom: { isSelecting: store.get(selectionPathAtom) !== null, isCreatingEdge: edgeCreation.isCreating } }; } // src/gestures/GestureProvider.tsx import { jsx as _jsx6 } from "react/jsx-runtime"; var nextOwnerId = 1; var activeOwnerId = null; var InputContext = /* @__PURE__ */ createContext3(null); function InputProvider({ children, gestureConfig, onAction }) { const store = useStore4(); const system = useInputSystem({ contexts: gestureConfig?.contexts, palmRejection: gestureConfig?.palmRejection ?? true }); useInputModeGestureContext(system); useRegisterInputActions(); const [ownerId] = useState3(() => nextOwnerId++); const [canvasRoot, setCanvasRoot] = useState3(null); const rootRef = useRef16(null); const systemRef = useRef16(system); systemRef.current = system; const registerCanvasRoot = (node) => { rootRef.current = node; setCanvasRoot(node); }; useEffect10(() => { const root = canvasRoot; if (!root) { return; } const activate = (event) => { activeOwnerId = ownerId; if (!isEditableTarget(event.target)) { root.focus({ preventScroll: true }); } }; root.addEventListener("pointerdown", activate, true); root.addEventListener("focusin", activate, true); return () => { root.removeEventListener("pointerdown", activate, true); root.removeEventListener("focusin", activate, true); if (activeOwnerId === ownerId) { activeOwnerId = null; } }; }, [canvasRoot, ownerId]); useEffect10(() => { const handleKeyboard = (nativeEvent) => { if (activeOwnerId !== ownerId) { return; } const root_0 = rootRef.current; if (!root_0 || !root_0.isConnected) { return; } const currentSystem = systemRef.current; const editableTarget = isEditableTarget(nativeEvent.target); const shouldTrackHeldKey = nativeEvent.type === "keyup" || !nativeEvent.isComposing && !editableTarget; const nextHeldKeys = shouldTrackHeldKey ? applyHeldKeyDelta(nativeEvent, currentSystem.heldKeys) : currentSystem.heldKeys; if (nextHeldKeys !== currentSystem.heldKeys) { currentSystem.setHeldKeys(nextHeldKeys); } if (nativeEvent.isComposing || editableTarget) { return; } const subject = getCurrentSubject2(store); const { screenPosition, worldPosition } = getSubjectPosition(store, root_0, subject); const inputEvent = { kind: "key", phase: nativeEvent.type === "keydown" ? "down" : "up", key: nativeEvent.key, code: nativeEvent.code, repeat: nativeEvent.repeat, modifiers: { shift: nativeEvent.shiftKey, ctrl: nativeEvent.ctrlKey, alt: nativeEvent.altKey, meta: nativeEvent.metaKey }, heldKeys: nextHeldKeys, subject, screenPosition, worldPosition, originalEvent: nativeEvent }; const resolution = resolve(inputEvent, currentSystem.mappingIndex, buildGuardContext(store, { heldKeys: nextHeldKeys })); if (!resolution) { return; } nativeEvent.preventDefault(); dispatch(inputEvent, resolution); onAction?.(inputEvent, resolution); }; const clearHeldKeys = () => { systemRef.current.clearHeldKeys(); }; window.addEventListener("keydown", handleKeyboard, true); window.addEventListener("keyup", handleKeyboard, true); window.addEventListener("blur", clearHeldKeys); document.addEventListener("visibilitychange", clearHeldKeys); return () => { window.removeEventListener("keydown", handleKeyboard, true); window.removeEventListener("keyup", handleKeyboard, true); window.removeEventListener("blur", clearHeldKeys); document.removeEventListener("visibilitychange", clearHeldKeys); }; }, [onAction, ownerId, store]); return /* @__PURE__ */ _jsx6(InputContext.Provider, { value: { system, onAction, registerCanvasRoot }, children }); } function useInputContext() { const ctx = useContext3(InputContext); if (!ctx) { throw new Error("useInputContext must be used within an InputProvider"); } return ctx; } var GestureProvider = InputProvider; var useGestureContext = useInputContext; // src/components/Grid.tsx import { c as _c35 } from "react/compiler-runtime"; import React7 from "react"; import { jsx as _jsx7 } from "react/jsx-runtime"; function Grid(t0) { const $ = _c35(19); const { pan, zoom, viewportWidth, viewportHeight, gridSize: t1, majorGridSize: t2 } = t0; const gridSize = t1 === void 0 ? 50 : t1; const majorGridSize = t2 === void 0 ? 250 : t2; const styles4 = useCanvasStyle(); if (!styles4.grid.visible || !viewportWidth || !viewportHeight || zoom <= 0) { return null; } let gridOpacity; let lines; if ($[0] !== gridSize || $[1] !== majorGridSize || $[2] !== pan.x || $[3] !== pan.y || $[4] !== styles4.grid.majorLineColor || $[5] !== styles4.grid.minorLineColor || $[6] !== styles4.grid.opacity || $[7] !== viewportHeight || $[8] !== viewportWidth || $[9] !== zoom) { lines = []; const minorLineColor = styles4.grid.minorLineColor; const majorLineColor = styles4.grid.majorLineColor; gridOpacity = styles4.grid.opacity; const worldLeft = -pan.x / zoom; const worldTop = -pan.y / zoom; const worldRight = (-pan.x + viewportWidth) / zoom; const worldBottom = (-pan.y + viewportHeight) / zoom; const firstVerticalLine = Math.ceil(worldLeft / gridSize) * gridSize; for (let x = firstVerticalLine; x <= worldRight; x = x + gridSize, x) { if (x % majorGridSize !== 0) { lines.push(/* @__PURE__ */ _jsx7("line", { x1: x, y1: worldTop, x2: x, y2: worldBottom, stroke: minorLineColor, strokeWidth: 1 / zoom }, `v-${x}`)); } } const firstHorizontalLine = Math.ceil(worldTop / gridSize) * gridSize; for (let y = firstHorizontalLine; y <= worldBottom; y = y + gridSize, y) { if (y % majorGridSize !== 0) { lines.push(/* @__PURE__ */ _jsx7("line", { x1: worldLeft, y1: y, x2: worldRight, y2: y, stroke: minorLineColor, strokeWidth: 1 / zoom }, `h-${y}`)); } } const firstMajorVerticalLine = Math.ceil(worldLeft / majorGridSize) * majorGridSize; for (let x_0 = firstMajorVerticalLine; x_0 <= worldRight; x_0 = x_0 + majorGridSize, x_0) { lines.push(/* @__PURE__ */ _jsx7("line", { x1: x_0, y1: worldTop, x2: x_0, y2: worldBottom, stroke: majorLineColor, strokeWidth: 1.5 / zoom }, `major-v-${x_0}`)); } const firstMajorHorizontalLine = Math.ceil(worldTop / majorGridSize) * majorGridSize; for (let y_0 = firstMajorHorizontalLine; y_0 <= worldBottom; y_0 = y_0 + majorGridSize, y_0) { lines.push(/* @__PURE__ */ _jsx7("line", { x1: worldLeft, y1: y_0, x2: worldRight, y2: y_0, stroke: majorLineColor, strokeWidth: 1.5 / zoom }, `major-h-${y_0}`)); } $[0] = gridSize; $[1] = majorGridSize; $[2] = pan.x; $[3] = pan.y; $[4] = styles4.grid.majorLineColor; $[5] = styles4.grid.minorLineColor; $[6] = styles4.grid.opacity; $[7] = viewportHeight; $[8] = viewportWidth; $[9] = zoom; $[10] = gridOpacity; $[11] = lines; } else { gridOpacity = $[10]; lines = $[11]; } let t3; if ($[12] !== gridOpacity) { t3 = { position: "absolute", top: 0, left: 0, overflow: "visible", pointerEvents: "none", opacity: gridOpacity }; $[12] = gridOpacity; $[13] = t3; } else { t3 = $[13]; } let t4; if ($[14] !== lines) { t4 = /* @__PURE__ */ _jsx7("g", { children: lines }); $[14] = lines; $[15] = t4; } else { t4 = $[15]; } let t5; if ($[16] !== t3 || $[17] !== t4) { t5 = /* @__PURE__ */ _jsx7("svg", { width: "100%", height: "100%", style: t3, children: t4 }); $[16] = t3; $[17] = t4; $[18] = t5; } else { t5 = $[18]; } return t5; } function Crosshairs(t0) { const $ = _c35(31); const { pan, zoom, viewportWidth, viewportHeight, tickSize: t1, labelSize: t2 } = t0; const tickSize = t1 === void 0 ? 100 : t1; const labelSize = t2 === void 0 ? 12 : t2; const styles4 = useCanvasStyle(); if (!styles4.axes.visible || !viewportWidth || !viewportHeight || zoom <= 0) { return null; } let t3; if ($[0] !== tickSize || $[1] !== zoom) { t3 = () => { const minWorldSpacing = 80 / zoom; const niceNumbers = [50, 100, 250, 500, 1e3, 2500, 5e3, 1e4]; for (const n of niceNumbers) { if (n >= minWorldSpacing) { return Math.max(n, tickSize); } } return 1e4; }; $[0] = tickSize; $[1] = zoom; $[2] = t3; } else { t3 = $[2]; } const getAdjustedTickSize = t3; const adjustedTickSize = getAdjustedTickSize(); let elements; if ($[3] !== adjustedTickSize || $[4] !== labelSize || $[5] !== pan.x || $[6] !== pan.y || $[7] !== styles4.axes.color || $[8] !== styles4.axes.labelColor || $[9] !== viewportHeight || $[10] !== viewportWidth || $[11] !== zoom) { elements = []; const strokeWidth = 1.5 / zoom; const tickLength = 5 / zoom; const labelOffset = 8 / zoom; const finalLabelSize = labelSize / zoom; const axisColor = styles4.axes.color; const labelColor = styles4.axes.labelColor; const worldLeft = -pan.x / zoom; const worldTop = -pan.y / zoom; const worldRight = (-pan.x + viewportWidth) / zoom; const worldBottom = (-pan.y + viewportHeight) / zoom; if (worldTop <= 0 && worldBottom >= 0) { let t42; if ($[13] !== axisColor || $[14] !== strokeWidth || $[15] !== worldLeft || $[16] !== worldRight) { t42 = /* @__PURE__ */ _jsx7("line", { x1: worldLeft, y1: 0, x2: worldRight, y2: 0, stroke: axisColor, strokeWidth }, "x-axis"); $[13] = axisColor; $[14] = strokeWidth; $[15] = worldLeft; $[16] = worldRight; $[17] = t42; } else { t42 = $[17]; } elements.push(t42); const firstXTick = Math.ceil(worldLeft / adjustedTickSize) * adjustedTickSize; for (let x = firstXTick; x <= worldRight; x = x + adjustedTickSize, x) { if (x === 0 && worldLeft <= 0 && worldRight >= 0) { continue; } elements.push(/* @__PURE__ */ _jsx7("line", { x1: x, y1: -tickLength, x2: x, y2: tickLength, stroke: axisColor, strokeWidth: strokeWidth / 1.5 }, `x-tick-${x}`)); elements.push(/* @__PURE__ */ _jsx7("text", { x, y: tickLength + labelOffset, fill: labelColor, fontSize: finalLabelSize, textAnchor: "middle", dominantBaseline: "hanging", children: x }, `x-label-${x}`)); } } if (worldLeft <= 0 && worldRight >= 0) { let t42; if ($[18] !== axisColor || $[19] !== strokeWidth || $[20] !== worldBottom || $[21] !== worldTop) { t42 = /* @__PURE__ */ _jsx7("line", { x1: 0, y1: worldTop, x2: 0, y2: worldBottom, stroke: axisColor, strokeWidth }, "y-axis"); $[18] = axisColor; $[19] = strokeWidth; $[20] = worldBottom; $[21] = worldTop; $[22] = t42; } else { t42 = $[22]; } elements.push(t42); const firstYTick = Math.ceil(worldTop / adjustedTickSize) * adjustedTickSize; for (let y = firstYTick; y <= worldBottom; y = y + adjustedTickSize, y) { if (y === 0 && worldTop <= 0 && worldBottom >= 0) { continue; } elements.push(/* @__PURE__ */ _jsx7("line", { x1: -tickLength, y1: y, x2: tickLength, y2: y, stroke: axisColor, strokeWidth: strokeWidth / 1.5 }, `y-tick-${y}`)); elements.push(/* @__PURE__ */ _jsx7("text", { x: tickLength + labelOffset, y, fill: labelColor, fontSize: finalLabelSize, textAnchor: "start", dominantBaseline: "middle", children: y }, `y-label-${y}`)); } } if (worldLeft <= 0 && worldRight >= 0 && worldTop <= 0 && worldBottom >= 0) { const t42 = -labelOffset; const t52 = finalLabelSize * 1.1; let t6; if ($[23] !== labelColor || $[24] !== labelOffset || $[25] !== t42 || $[26] !== t52) { t6 = /* @__PURE__ */ _jsx7("text", { x: labelOffset, y: t42, fill: labelColor, fontSize: t52, textAnchor: "start", dominantBaseline: "alphabetic", children: "(0,0)" }, "origin-label"); $[23] = labelColor; $[24] = labelOffset; $[25] = t42; $[26] = t52; $[27] = t6; } else { t6 = $[27]; } elements.push(t6); } $[3] = adjustedTickSize; $[4] = labelSize; $[5] = pan.x; $[6] = pan.y; $[7] = styles4.axes.color; $[8] = styles4.axes.labelColor; $[9] = viewportHeight; $[10] = viewportWidth; $[11] = zoom; $[12] = elements; } else { elements = $[12]; } let t4; if ($[28] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { position: "absolute", top: 0, left: 0, overflow: "visible", pointerEvents: "none" }; $[28] = t4; } else { t4 = $[28]; } let t5; if ($[29] !== elements) { t5 = /* @__PURE__ */ _jsx7("svg", { width: "100%", height: "100%", style: t4, children: /* @__PURE__ */ _jsx7("g", { children: elements }) }); $[29] = elements; $[30] = t5; } else { t5 = $[30]; } return t5; } // src/components/Viewport.tsx import { jsx as _jsx8, Fragment as _Fragment2, jsxs as _jsxs2 } from "react/jsx-runtime"; function Viewport(t0) { const $ = _c36(28); const { children, minZoom: t1, maxZoom: t2, zoomSensitivity: t3, className, style, enablePan: t4, enableZoom: t5 } = t0; const minZoom = t1 === void 0 ? 0.1 : t1; const maxZoom = t2 === void 0 ? 5 : t2; const zoomSensitivity = t3 === void 0 ? 15e-4 : t3; const enablePan = t4 === void 0 ? true : t4; const enableZoom = t5 === void 0 ? true : t5; const panVals = useAtomValue25(panAtom); const zoomVal = useAtomValue25(zoomAtom); const viewportRect = useAtomValue25(viewportRectAtom); const viewportRef = useRef17(null); const { system, onAction, registerCanvasRoot } = useInputContext(); let t6; let t7; if ($[0] !== registerCanvasRoot) { t6 = () => { registerCanvasRoot(viewportRef.current); return () => registerCanvasRoot(null); }; t7 = [registerCanvasRoot]; $[0] = registerCanvasRoot; $[1] = t6; $[2] = t7; } else { t6 = $[1]; t7 = $[2]; } useEffect11(t6, t7); let t8; if ($[3] !== enablePan || $[4] !== enableZoom || $[5] !== maxZoom || $[6] !== minZoom || $[7] !== onAction || $[8] !== system.heldKeys || $[9] !== system.mappingIndex || $[10] !== zoomSensitivity) { t8 = { ref: viewportRef, minZoom, maxZoom, zoomSensitivity, enablePan, enableZoom, mappingIndex: system.mappingIndex, onAction, heldKeys: system.heldKeys }; $[3] = enablePan; $[4] = enableZoom; $[5] = maxZoom; $[6] = minZoom; $[7] = onAction; $[8] = system.heldKeys; $[9] = system.mappingIndex; $[10] = zoomSensitivity; $[11] = t8; } else { t8 = $[11]; } useCanvasGestures(t8); let t9; if ($[12] !== style) { t9 = { width: "100%", height: "100%", overflow: "hidden", position: "relative", touchAction: "none", userSelect: "none", WebkitUserSelect: "none", WebkitTouchCallout: "none", background: "var(--canvas-bg, #23272a)", ...style }; $[12] = style; $[13] = t9; } else { t9 = $[13]; } const t10 = `translate(${panVals.x}px, ${panVals.y}px) scale(${zoomVal})`; let t11; if ($[14] !== t10) { t11 = { transform: t10, transformOrigin: "0 0", width: "100%", height: "100%" }; $[14] = t10; $[15] = t11; } else { t11 = $[15]; } let t12; if ($[16] !== panVals || $[17] !== viewportRect || $[18] !== zoomVal) { t12 = viewportRect && viewportRect.width > 0 && viewportRect.height > 0 && /* @__PURE__ */ _jsxs2(_Fragment2, { children: [/* @__PURE__ */ _jsx8(Grid, { pan: panVals, zoom: zoomVal, viewportWidth: viewportRect.width, viewportHeight: viewportRect.height }), /* @__PURE__ */ _jsx8(Crosshairs, { pan: panVals, zoom: zoomVal, viewportWidth: viewportRect.width, viewportHeight: viewportRect.height })] }); $[16] = panVals; $[17] = viewportRect; $[18] = zoomVal; $[19] = t12; } else { t12 = $[19]; } let t13; if ($[20] !== children || $[21] !== t11 || $[22] !== t12) { t13 = /* @__PURE__ */ _jsxs2("div", { style: t11, children: [t12, children] }); $[20] = children; $[21] = t11; $[22] = t12; $[23] = t13; } else { t13 = $[23]; } let t14; if ($[24] !== className || $[25] !== t13 || $[26] !== t9) { t14 = /* @__PURE__ */ _jsx8("div", { ref: viewportRef, id: "viewport-canvas", className, "data-viewport": "true", "data-canvas-root": "true", tabIndex: -1, style: t9, children: t13 }); $[24] = className; $[25] = t13; $[26] = t9; $[27] = t14; } else { t14 = $[27]; } return t14; } // src/components/NodeRenderer.tsx init_graph_position(); init_graph_derived(); init_virtualization_store(); init_node_type_registry(); import { c as _c39 } from "react/compiler-runtime"; import React12 from "react"; import { useAtomValue as useAtomValue28 } from "jotai"; // src/components/Node.tsx init_graph_store(); init_graph_position(); init_graph_mutations(); init_selection_store(); init_search_store(); import React10 from "react"; import { useAtomValue as useAtomValue27, useSetAtom as useSetAtom23 } from "jotai"; // src/components/ResizeHandle.tsx import { c as _c37 } from "react/compiler-runtime"; import React9 from "react"; import { jsx as _jsx9 } from "react/jsx-runtime"; var HIT_AREA_SIZE = 44; var VISUAL_SIZE = 12; var VISUAL_RADIUS = "8px"; function getHitAreaStyles(direction) { const base = { position: "absolute", width: `${HIT_AREA_SIZE}px`, height: `${HIT_AREA_SIZE}px`, zIndex: 10, touchAction: "none", // Prevent iOS callout/magnifier on long-press WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }; const halfHit = HIT_AREA_SIZE / 2; switch (direction) { case "nw": return { ...base, top: -halfHit, left: -halfHit, cursor: "nwse-resize" }; case "ne": return { ...base, top: -halfHit, right: -halfHit, cursor: "nesw-resize" }; case "sw": return { ...base, bottom: -halfHit, left: -halfHit, cursor: "nesw-resize" }; case "se": return { ...base, bottom: -halfHit, right: -halfHit, cursor: "nwse-resize" }; case "n": return { ...base, top: -halfHit, left: "50%", transform: "translateX(-50%)", cursor: "ns-resize" }; case "s": return { ...base, bottom: -halfHit, left: "50%", transform: "translateX(-50%)", cursor: "ns-resize" }; case "e": return { ...base, right: -halfHit, top: "50%", transform: "translateY(-50%)", cursor: "ew-resize" }; case "w": return { ...base, left: -halfHit, top: "50%", transform: "translateY(-50%)", cursor: "ew-resize" }; } } function getVisualStyles(direction, isResizing) { const activeColor = "var(--node-resize-handle-active, rgba(59, 130, 246, 0.8))"; const inactiveColor = "var(--node-resize-handle, rgba(0, 0, 0, 0.2))"; const borderWidth = isResizing ? "4px" : "3px"; const borderColor = isResizing ? activeColor : inactiveColor; const borderStyle = `${borderWidth} solid ${borderColor}`; const base = { position: "absolute", pointerEvents: "none", // Center the visual indicator within the hit area top: "50%", left: "50%", transform: "translate(-50%, -50%)", width: `${VISUAL_SIZE}px`, height: `${VISUAL_SIZE}px` }; switch (direction) { case "nw": return { ...base, borderTop: borderStyle, borderLeft: borderStyle, borderTopLeftRadius: VISUAL_RADIUS }; case "ne": return { ...base, borderTop: borderStyle, borderRight: borderStyle, borderTopRightRadius: VISUAL_RADIUS }; case "sw": return { ...base, borderBottom: borderStyle, borderLeft: borderStyle, borderBottomLeftRadius: VISUAL_RADIUS }; case "se": return { ...base, borderBottom: borderStyle, borderRight: borderStyle, borderBottomRightRadius: VISUAL_RADIUS }; case "n": return { ...base, borderTop: borderStyle, width: `${VISUAL_SIZE * 2}px` }; case "s": return { ...base, borderBottom: borderStyle, width: `${VISUAL_SIZE * 2}px` }; case "e": return { ...base, borderRight: borderStyle, height: `${VISUAL_SIZE * 2}px` }; case "w": return { ...base, borderLeft: borderStyle, height: `${VISUAL_SIZE * 2}px` }; } } function ResizeHandle(t0) { const $ = _c37(26); const { direction, createResizeStart, handleResizeMove, handleResizeEnd, isResizing, alwaysVisible: t1 } = t0; const alwaysVisible = t1 === void 0 ? false : t1; let t2; if ($[0] !== createResizeStart || $[1] !== direction) { t2 = createResizeStart(direction); $[0] = createResizeStart; $[1] = direction; $[2] = t2; } else { t2 = $[2]; } let t3; if ($[3] !== direction) { t3 = getHitAreaStyles(direction); $[3] = direction; $[4] = t3; } else { t3 = $[4]; } const t4 = alwaysVisible || isResizing ? 1 : 0; let t5; if ($[5] !== t3 || $[6] !== t4) { t5 = { ...t3, opacity: t4, transition: "opacity 200ms" }; $[5] = t3; $[6] = t4; $[7] = t5; } else { t5 = $[7]; } let t6; if ($[8] !== alwaysVisible) { t6 = (e) => { if (!alwaysVisible) { e.currentTarget.style.opacity = "1"; } }; $[8] = alwaysVisible; $[9] = t6; } else { t6 = $[9]; } let t7; if ($[10] !== alwaysVisible || $[11] !== isResizing) { t7 = (e_0) => { if (!alwaysVisible && !isResizing) { e_0.currentTarget.style.opacity = "0"; } }; $[10] = alwaysVisible; $[11] = isResizing; $[12] = t7; } else { t7 = $[12]; } let t8; if ($[13] !== direction || $[14] !== isResizing) { t8 = getVisualStyles(direction, isResizing); $[13] = direction; $[14] = isResizing; $[15] = t8; } else { t8 = $[15]; } let t9; if ($[16] !== t8) { t9 = /* @__PURE__ */ _jsx9("div", { style: t8 }); $[16] = t8; $[17] = t9; } else { t9 = $[17]; } let t10; if ($[18] !== handleResizeEnd || $[19] !== handleResizeMove || $[20] !== t2 || $[21] !== t5 || $[22] !== t6 || $[23] !== t7 || $[24] !== t9) { t10 = /* @__PURE__ */ _jsx9("div", { "data-no-drag": "true", onPointerDown: t2, onPointerMove: handleResizeMove, onPointerUp: handleResizeEnd, onPointerCancel: handleResizeEnd, style: t5, onPointerEnter: t6, onPointerLeave: t7, children: t9 }); $[18] = handleResizeEnd; $[19] = handleResizeMove; $[20] = t2; $[21] = t5; $[22] = t6; $[23] = t7; $[24] = t9; $[25] = t10; } else { t10 = $[25]; } return t10; } // src/gestures/useNodeGestures.ts init_viewport_store(); init_input_store(); init_interaction_store(); init_selection_store(); init_graph_store(); init_search_store(); init_selection_path_store(); import { useRef as useRef18, useEffect as useEffect12 } from "react"; import { useAtomValue as useAtomValue26 } from "jotai"; init_input_classifier(); init_dispatcher(); init_types2(); function useNodeGestures({ nodeId, mappingIndex, onAction, heldKeys = NO_HELD_KEYS }) { const screenToWorld = useAtomValue26(screenToWorldAtom); const isStylusActive = useAtomValue26(isStylusActiveAtom); const fingerCount = useAtomValue26(fingerCountAtom); const inputMode = useAtomValue26(inputModeAtom); const keyboardInteractionMode = useAtomValue26(keyboardInteractionModeAtom); const selectedNodeIds = useAtomValue26(selectedNodeIdsAtom); const focusedNodeId = useAtomValue26(focusedNodeIdAtom); const draggingNodeId = useAtomValue26(draggingNodeIdAtom); const edgeCreation = useAtomValue26(edgeCreationAtom); const selectionPath = useAtomValue26(selectionPathAtom); const isSearchActive = useAtomValue26(isFilterActiveAtom); const commandLineVisible = useAtomValue26(commandLineVisibleAtom); const runner = useRef18(new TimedStateRunner()); const sourceRef = useRef18("mouse"); const buttonRef = useRef18(0); const posRef = useRef18({ x: 0, y: 0 }); const modifiersRef = useRef18(NO_MODIFIERS); const rightDragSeen = useRef18(false); const guardRef = useRef18({ isStylusActive: false, fingerCount: 0, isDragging: false, isResizing: false, isSplitting: false, inputMode: { type: "normal" }, keyboardInteractionMode: "navigate", selectedNodeIds: /* @__PURE__ */ new Set(), focusedNodeId: null, isSearchActive: false, commandLineVisible: false, heldKeys: NO_HELD_KEYS, custom: {} }); guardRef.current = { isStylusActive, fingerCount, isDragging: draggingNodeId !== null, isResizing: false, isSplitting: false, inputMode, keyboardInteractionMode, selectedNodeIds, focusedNodeId, isSearchActive, commandLineVisible, heldKeys, custom: {} }; guardRef.current.custom = { isSelecting: selectionPath !== null, isCreatingEdge: edgeCreation.isCreating }; useEffect12(() => { return () => runner.current.destroy(); }, []); const resolveAndDispatch = (event) => { const resolution = resolve(event, mappingIndex, guardRef.current); if (resolution) { dispatch(event, resolution); onAction?.(event, resolution); } return resolution; }; const makeEvent = (type, phase, screenX, screenY) => { const worldPos = screenToWorld(screenX, screenY); return { kind: "pointer", type, phase, subject: { kind: "node", nodeId }, source: sourceRef.current, button: buttonRef.current, modifiers: modifiersRef.current, heldKeys, screenPosition: { x: screenX, y: screenY }, worldPosition: worldPos }; }; useEffect12(() => { runner.current.onEmit = (gestureType) => { if (gestureType === "long-press") { const event_0 = makeEvent("long-press", "instant", posRef.current.x, posRef.current.y); resolveAndDispatch(event_0); } }; }); const onPointerDown = (e) => { const classified = classifyPointer(e.nativeEvent); sourceRef.current = classified.source; buttonRef.current = e.button ?? 0; posRef.current = { x: e.clientX, y: e.clientY }; modifiersRef.current = extractModifiers(e); rightDragSeen.current = false; runner.current.feed("down"); }; const onPointerUp = (e_0) => { const emitted = runner.current.feed("up"); if (emitted) { const event_1 = makeEvent(emitted, "instant", e_0.clientX, e_0.clientY); resolveAndDispatch(event_1); } }; const onPointerCancel = (_e) => { runner.current.feed("cancel"); }; const onContextMenu = (e_1) => { e_1.preventDefault(); e_1.stopPropagation(); if (rightDragSeen.current) { rightDragSeen.current = false; return; } const event_2 = makeEvent("tap", "instant", e_1.clientX, e_1.clientY); event_2.button = 2; event_2.modifiers = extractModifiers(e_1); resolveAndDispatch(event_2); }; return { onPointerDown, onPointerUp, onPointerCancel, onContextMenu }; } // src/components/Node.tsx import { jsx as _jsx10, jsxs as _jsxs3 } from "react/jsx-runtime"; function Node({ nodeData, renderContent, onPersist, onPersistError, wrapper: Wrapper }) { const { id, zIndex } = nodeData; const graph = useAtomValue27(graphAtom); const setHandleNodePointerDown = useSetAtom23(handleNodePointerDownSelectionAtom); const { isSelected } = useNodeSelection(id); const selectedNodeIds = useAtomValue27(selectedNodeIdsAtom); const activeDraggingNodeId = useAtomValue27(draggingNodeIdAtom); const focusedNodeId = useAtomValue27(focusedNodeIdAtom); const isFocused = focusedNodeId === id && !isSelected; const position = useAtomValue27(nodePositionAtomFamily(id)); const canvasStyles = useCanvasStyle(); const isFilterActive = useAtomValue27(isFilterActiveAtom); const searchResults = useAtomValue27(searchResultsAtom); const highlightedSearchNodeId = useAtomValue27(highlightedSearchNodeIdAtom); const isDimmedByFilter = isFilterActive && !searchResults.has(id); const isSearchHighlighted = highlightedSearchNodeId === id; const dropTargetId = useAtomValue27(dropTargetNodeIdAtom); const isDropTarget = dropTargetId === id; const { system, onAction } = useInputContext(); const gestureHandlers = useNodeGestures({ nodeId: id, mappingIndex: system.mappingIndex, onAction, heldKeys: system.heldKeys }); const { bind, updateNodePositions } = useNodeDrag(id, { heldKeys: system.heldKeys, onPersist, onPersistError }); const resizeOptions = { onPersist, onPersistError }; const { localWidth, localHeight, isResizing, createResizeStart, handleResizeMove, handleResizeEnd } = useNodeResize({ id, nodeData, updateNodePositions, options: resizeOptions }); const nodeWidth = localWidth; const nodeHeight = localHeight; const splitGesture = useSplitGesture(id); const isVisuallyDragging = activeDraggingNodeId !== null && selectedNodeIds.has(id); const bindGestures = bind(); const handlePointerDown = (e) => { const target = e.target; if (target.closest('[data-no-drag="true"]') || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT") { return; } gestureHandlers.onPointerDown(e); if (bindGestures.onPointerDown) { bindGestures.onPointerDown(e); } e.stopPropagation(); setHandleNodePointerDown({ nodeId: id, isShiftPressed: e.shiftKey }); }; const handlePointerUp = (e_0) => { gestureHandlers.onPointerUp(e_0); }; const currentZIndex = graph.hasNode(id) ? graph.getNodeAttribute(id, "zIndex") : zIndex; const borderColor = isSelected ? canvasStyles.nodes.selectedBorderColor : isFocused ? canvasStyles.nodes.selectedBorderColor : isVisuallyDragging ? canvasStyles.nodes.draggingBorderColor : canvasStyles.nodes.defaultBorderColor; const borderWidth = isSelected || isVisuallyDragging ? "1.5px" : isFocused ? "2px" : "1px"; const borderRadius = `${canvasStyles.nodes.defaultBorderRadius}px`; const defaultBoxShadow = `0px 8px 24px ${canvasStyles.nodes.shadowColor}`; const draggingBoxShadow = `0px 12px 32px ${canvasStyles.nodes.shadowColor}`; const selectedNodeBoxShadow = `0 0 10px 2px ${canvasStyles.nodes.selectedGlowColor}, ${defaultBoxShadow}`; const focusedBoxShadow = `0 0 0 2px ${canvasStyles.nodes.selectedBorderColor}, ${defaultBoxShadow}`; const currentBoxShadow = isSelected ? selectedNodeBoxShadow : isFocused ? focusedBoxShadow : isVisuallyDragging ? draggingBoxShadow : defaultBoxShadow; const nodeContent = /* @__PURE__ */ _jsxs3("div", { "data-draggable-node": "true", "data-node-id": id, "data-focused": isFocused || void 0, className: [isSearchHighlighted && "canvas-search-highlight", isDropTarget && "canvas-drop-target"].filter(Boolean).join(" ") || void 0, ...bindGestures, onPointerDown: (e_1) => { splitGesture.onPointerDown(e_1); handlePointerDown(e_1); }, onPointerMove: splitGesture.onPointerMove, onPointerUp: (e_2) => { splitGesture.onPointerUp(e_2); handlePointerUp(e_2); }, onPointerCancel: gestureHandlers.onPointerCancel, onContextMenu: gestureHandlers.onContextMenu, style: { width: `${nodeWidth}px`, height: `${nodeHeight}px`, backgroundColor: isVisuallyDragging ? canvasStyles.nodes.draggingBackgroundColor : canvasStyles.nodes.defaultBackground, border: `${borderWidth} solid ${borderColor}`, borderRadius, boxShadow: currentBoxShadow, cursor: isVisuallyDragging ? "grabbing" : "grab", opacity: isDimmedByFilter ? 0.2 : isVisuallyDragging ? 0.9 : 1, pointerEvents: isDimmedByFilter ? "none" : "auto", transition: isVisuallyDragging ? "opacity 0.1s, transform 0.05s, background-color 0.1s, box-shadow 0.1s, border-color 0.1s" : "opacity 0.4s, border-color 0.4s, box-shadow 0.4s, background-color 0.4s, transform 0.4s ease-in-out", transform: isVisuallyDragging ? "scale(1.005)" : "scale(1)", zIndex: currentZIndex, touchAction: "manipulation", userSelect: "auto", position: "relative", overflow: "hidden" }, children: [renderContent({ node: nodeData, isSelected, isResizing, isDragging: isVisuallyDragging }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "nw", createResizeStart, handleResizeMove, handleResizeEnd, isResizing }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "ne", createResizeStart, handleResizeMove, handleResizeEnd, isResizing }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "sw", createResizeStart, handleResizeMove, handleResizeEnd, isResizing }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "se", createResizeStart, handleResizeMove, handleResizeEnd, isResizing, alwaysVisible: true }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "n", createResizeStart, handleResizeMove, handleResizeEnd, isResizing }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "s", createResizeStart, handleResizeMove, handleResizeEnd, isResizing }), /* @__PURE__ */ _jsx10(ResizeHandle, { direction: "w", createResizeStart, handleResizeMove, handleResizeEnd, isResizing })] }); if (Wrapper) { return /* @__PURE__ */ _jsx10(Wrapper, { nodeData, children: nodeContent }); } return nodeContent; } // src/components/NodeErrorBoundary.tsx init_debug(); import { c as _c38 } from "react/compiler-runtime"; import React11, { Component } from "react"; import { jsx as _jsx11, jsxs as _jsxs4 } from "react/jsx-runtime"; var debug19 = createDebug("error-boundary"); function DefaultFallback(t0) { const $ = _c38(11); const { error, reset } = t0; let t1; let t2; let t3; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { padding: "16px", backgroundColor: "rgba(239, 68, 68, 0.1)", border: "1px solid rgba(239, 68, 68, 0.3)", borderRadius: "8px", color: "#ef4444", fontSize: "12px", minWidth: "150px" }; t2 = /* @__PURE__ */ _jsx11("div", { style: { fontWeight: 600, marginBottom: "8px" }, children: "\u26A0\uFE0F Node Error" }); t3 = { color: "#888", marginBottom: "8px", wordBreak: "break-word" }; $[0] = t1; $[1] = t2; $[2] = t3; } else { t1 = $[0]; t2 = $[1]; t3 = $[2]; } const t4 = error.message || "Unknown error"; let t5; if ($[3] !== t4) { t5 = /* @__PURE__ */ _jsx11("div", { style: t3, children: t4 }); $[3] = t4; $[4] = t5; } else { t5 = $[4]; } let t6; if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t6 = { padding: "4px 8px", fontSize: "11px", backgroundColor: "rgba(239, 68, 68, 0.2)", border: "1px solid rgba(239, 68, 68, 0.3)", borderRadius: "4px", color: "#ef4444", cursor: "pointer" }; $[5] = t6; } else { t6 = $[5]; } let t7; if ($[6] !== reset) { t7 = /* @__PURE__ */ _jsx11("button", { onClick: reset, style: t6, children: "Retry" }); $[6] = reset; $[7] = t7; } else { t7 = $[7]; } let t8; if ($[8] !== t5 || $[9] !== t7) { t8 = /* @__PURE__ */ _jsxs4("div", { style: t1, children: [t2, t5, t7] }); $[8] = t5; $[9] = t7; $[10] = t8; } else { t8 = $[10]; } return t8; } var NodeErrorBoundary = class extends Component { constructor(props) { super(props); __publicField(this, "reset", () => { this.setState({ hasError: false, error: null }); }); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { const { nodeId, onError } = this.props; debug19.error("Error in node %s: %O %O", nodeId, error, errorInfo); onError?.(nodeId, error, errorInfo); } render() { const { hasError, error } = this.state; const { nodeId, children, renderFallback } = this.props; if (hasError && error) { if (renderFallback) { return renderFallback({ nodeId, error, reset: this.reset }); } return /* @__PURE__ */ _jsx11(DefaultFallback, { nodeId, error, reset: this.reset }); } return children; } }; // src/components/NodeRenderer.tsx import { jsx as _jsx12, Fragment as _Fragment3 } from "react/jsx-runtime"; function NodeRenderer(t0) { const $ = _c39(17); const { renderNode, onNodePersist, onNodePersistError, onNodeRenderError, renderErrorFallback, nodeWrapper } = t0; const nodeKeys = useAtomValue28(visibleNodeKeysAtom); let t1; if ($[0] !== nodeKeys || $[1] !== nodeWrapper || $[2] !== onNodePersist || $[3] !== onNodePersistError || $[4] !== onNodeRenderError || $[5] !== renderErrorFallback || $[6] !== renderNode) { let t22; if ($[8] !== nodeWrapper || $[9] !== onNodePersist || $[10] !== onNodePersistError || $[11] !== onNodeRenderError || $[12] !== renderErrorFallback || $[13] !== renderNode) { t22 = (key) => /* @__PURE__ */ _jsx12(NodeItem, { nodeKey: key, renderNode, onNodePersist, onNodePersistError, onNodeRenderError, renderErrorFallback, nodeWrapper }, key); $[8] = nodeWrapper; $[9] = onNodePersist; $[10] = onNodePersistError; $[11] = onNodeRenderError; $[12] = renderErrorFallback; $[13] = renderNode; $[14] = t22; } else { t22 = $[14]; } t1 = nodeKeys.map(t22); $[0] = nodeKeys; $[1] = nodeWrapper; $[2] = onNodePersist; $[3] = onNodePersistError; $[4] = onNodeRenderError; $[5] = renderErrorFallback; $[6] = renderNode; $[7] = t1; } else { t1 = $[7]; } let t2; if ($[15] !== t1) { t2 = /* @__PURE__ */ _jsx12(_Fragment3, { children: t1 }); $[15] = t1; $[16] = t2; } else { t2 = $[16]; } return t2; } function resolveRenderNode(nodeData) { const Component2 = getNodeTypeComponent(nodeData.dbData.node_type) ?? FallbackNodeTypeComponent; return (props) => /* @__PURE__ */ _jsx12(Component2, { nodeData: props.node, isResizing: props.isResizing }); } function NodeItem(t0) { const $ = _c39(24); const { nodeKey, renderNode, onNodePersist, onNodePersistError, onNodeRenderError, renderErrorFallback, nodeWrapper } = t0; let t1; if ($[0] !== nodeKey) { t1 = nodeFamilyAtom(nodeKey); $[0] = nodeKey; $[1] = t1; } else { t1 = $[1]; } const nodeData = useAtomValue28(t1); let t2; if ($[2] !== nodeKey) { t2 = nodePositionAtomFamily(nodeKey); $[2] = nodeKey; $[3] = t2; } else { t2 = $[3]; } const position = useAtomValue28(t2); if (!nodeData) { return null; } let t3; if ($[4] !== nodeData || $[5] !== renderNode) { t3 = renderNode ?? resolveRenderNode(nodeData); $[4] = nodeData; $[5] = renderNode; $[6] = t3; } else { t3 = $[6]; } const effectiveRenderNode = t3; let t4; if ($[7] !== position.x || $[8] !== position.y) { t4 = { position: "absolute", left: position.x, top: position.y, transform: "translate(0, 0)" }; $[7] = position.x; $[8] = position.y; $[9] = t4; } else { t4 = $[9]; } let t5; if ($[10] !== effectiveRenderNode || $[11] !== nodeData || $[12] !== nodeWrapper || $[13] !== onNodePersist || $[14] !== onNodePersistError) { t5 = /* @__PURE__ */ _jsx12(Node, { nodeData, renderContent: effectiveRenderNode, onPersist: onNodePersist, onPersistError: onNodePersistError, wrapper: nodeWrapper }); $[10] = effectiveRenderNode; $[11] = nodeData; $[12] = nodeWrapper; $[13] = onNodePersist; $[14] = onNodePersistError; $[15] = t5; } else { t5 = $[15]; } let t6; if ($[16] !== nodeKey || $[17] !== onNodeRenderError || $[18] !== renderErrorFallback || $[19] !== t5) { t6 = /* @__PURE__ */ _jsx12(NodeErrorBoundary, { nodeId: nodeKey, onError: onNodeRenderError, renderFallback: renderErrorFallback, children: t5 }); $[16] = nodeKey; $[17] = onNodeRenderError; $[18] = renderErrorFallback; $[19] = t5; $[20] = t6; } else { t6 = $[20]; } let t7; if ($[21] !== t4 || $[22] !== t6) { t7 = /* @__PURE__ */ _jsx12("div", { style: t4, children: t6 }); $[21] = t4; $[22] = t6; $[23] = t7; } else { t7 = $[23]; } return t7; } // src/components/EdgeRenderer.tsx init_graph_derived(); init_graph_mutations(); init_virtualization_store(); init_selection_store(); init_viewport_store(); init_search_store(); import { c as _c40 } from "react/compiler-runtime"; import React13 from "react"; import { useAtomValue as useAtomValue29, useSetAtom as useSetAtom24 } from "jotai"; init_edge_path_calculators(); import { jsx as _jsx13, jsxs as _jsxs5 } from "react/jsx-runtime"; var SELECTED_EDGE_COLOR = "#3b82f6"; function EdgeRenderer(t0) { const $ = _c40(19); const { onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave } = t0; const edgeKeys = useAtomValue29(visibleEdgeKeysAtom); const departingEdges = useAtomValue29(departingEdgesAtom); let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none", zIndex: 0, overflow: "visible" }; $[0] = t1; } else { t1 = $[0]; } let t2; if ($[1] !== edgeKeys || $[2] !== onEdgeClick || $[3] !== onEdgeDoubleClick || $[4] !== onEdgeHover || $[5] !== onEdgeLeave || $[6] !== onEdgeRightClick) { let t32; if ($[8] !== onEdgeClick || $[9] !== onEdgeDoubleClick || $[10] !== onEdgeHover || $[11] !== onEdgeLeave || $[12] !== onEdgeRightClick) { t32 = (key) => /* @__PURE__ */ _jsx13(EdgeItem, { edgeKey: key, onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave }, key); $[8] = onEdgeClick; $[9] = onEdgeDoubleClick; $[10] = onEdgeHover; $[11] = onEdgeLeave; $[12] = onEdgeRightClick; $[13] = t32; } else { t32 = $[13]; } t2 = edgeKeys.map(t32); $[1] = edgeKeys; $[2] = onEdgeClick; $[3] = onEdgeDoubleClick; $[4] = onEdgeHover; $[5] = onEdgeLeave; $[6] = onEdgeRightClick; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[14] !== departingEdges) { t3 = Array.from(departingEdges.entries()).map(_temp8); $[14] = departingEdges; $[15] = t3; } else { t3 = $[15]; } let t4; if ($[16] !== t2 || $[17] !== t3) { t4 = /* @__PURE__ */ _jsxs5("svg", { style: t1, children: [t2, t3] }); $[16] = t2; $[17] = t3; $[18] = t4; } else { t4 = $[18]; } return t4; } function _temp8(t0) { const [key_0, edgeData] = t0; return /* @__PURE__ */ _jsx13(DepartingEdgeItem, { edgeData }, `departing-${key_0}`); } function EdgeItem(t0) { const $ = _c40(77); const { edgeKey, onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave } = t0; let t1; if ($[0] !== edgeKey) { t1 = edgeFamilyAtom(edgeKey); $[0] = edgeKey; $[1] = t1; } else { t1 = $[1]; } const edgeData = useAtomValue29(t1); const styles4 = useCanvasStyle(); const selectedEdgeId = useAtomValue29(selectedEdgeIdAtom); const selectEdge = useSetAtom24(selectEdgeAtom); const zoom = useAtomValue29(zoomAtom); const setEditingEdgeLabel = useSetAtom24(editingEdgeLabelAtom); const isFilterActive = useAtomValue29(isFilterActiveAtom); const searchResults = useAtomValue29(searchResultsAtom); const searchEdgeResults = useAtomValue29(searchEdgeResultsAtom); const isSelected = selectedEdgeId === edgeKey; let color; let handleClick; let handleContextMenu; let handleDoubleClick; let handlePointerEnter; let handlePointerLeave; let label; let t2; let t3; let type; let weight; if ($[2] !== edgeData || $[3] !== edgeKey || $[4] !== onEdgeClick || $[5] !== onEdgeDoubleClick || $[6] !== onEdgeHover || $[7] !== onEdgeLeave || $[8] !== onEdgeRightClick || $[9] !== selectEdge || $[10] !== styles4.edges.pathType || $[11] !== zoom) { t3 = /* @__PURE__ */ Symbol.for("react.early_return_sentinel"); bb0: { const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier"); let t42; if ($[23] !== edgeData || $[24] !== edgeKey || $[25] !== onEdgeClick || $[26] !== selectEdge) { t42 = (e) => { e.stopPropagation(); if (onEdgeClick && edgeData) { onEdgeClick(edgeKey, edgeData, e); } else { selectEdge(edgeKey); } }; $[23] = edgeData; $[24] = edgeKey; $[25] = onEdgeClick; $[26] = selectEdge; $[27] = t42; } else { t42 = $[27]; } handleClick = t42; let t52; if ($[28] !== edgeData || $[29] !== edgeKey || $[30] !== onEdgeDoubleClick) { t52 = (e_0) => { if (onEdgeDoubleClick && edgeData) { e_0.stopPropagation(); e_0.preventDefault(); onEdgeDoubleClick(edgeKey, edgeData, e_0); } }; $[28] = edgeData; $[29] = edgeKey; $[30] = onEdgeDoubleClick; $[31] = t52; } else { t52 = $[31]; } handleDoubleClick = t52; let t62; if ($[32] !== edgeData || $[33] !== edgeKey || $[34] !== onEdgeRightClick) { t62 = (e_1) => { if (onEdgeRightClick && edgeData) { e_1.preventDefault(); e_1.stopPropagation(); onEdgeRightClick(edgeKey, edgeData, e_1); } }; $[32] = edgeData; $[33] = edgeKey; $[34] = onEdgeRightClick; $[35] = t62; } else { t62 = $[35]; } handleContextMenu = t62; let t72; if ($[36] !== edgeData || $[37] !== edgeKey || $[38] !== onEdgeHover) { t72 = () => { if (onEdgeHover && edgeData) { onEdgeHover(edgeKey, edgeData); } }; $[36] = edgeData; $[37] = edgeKey; $[38] = onEdgeHover; $[39] = t72; } else { t72 = $[39]; } handlePointerEnter = t72; let t82; if ($[40] !== edgeKey || $[41] !== onEdgeLeave) { t82 = () => { if (onEdgeLeave) { onEdgeLeave(edgeKey); } }; $[40] = edgeKey; $[41] = onEdgeLeave; $[42] = t82; } else { t82 = $[42]; } handlePointerLeave = t82; if (!edgeData) { t3 = null; break bb0; } const { sourcePosition, targetPosition, color: t92, weight: t102, type: t112, label: t122, sourceEndpoint, targetEndpoint } = edgeData; color = t92; weight = t102; type = t112; label = t122; const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2; const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2; const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2; const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2; if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) { t3 = null; break bb0; } t2 = pathCalculator({ x1, y1, x2, y2, sourceWidth: edgeData.sourceNodeWidth, sourceHeight: edgeData.sourceNodeHeight, targetWidth: edgeData.targetNodeWidth, targetHeight: edgeData.targetNodeHeight }); } $[2] = edgeData; $[3] = edgeKey; $[4] = onEdgeClick; $[5] = onEdgeDoubleClick; $[6] = onEdgeHover; $[7] = onEdgeLeave; $[8] = onEdgeRightClick; $[9] = selectEdge; $[10] = styles4.edges.pathType; $[11] = zoom; $[12] = color; $[13] = handleClick; $[14] = handleContextMenu; $[15] = handleDoubleClick; $[16] = handlePointerEnter; $[17] = handlePointerLeave; $[18] = label; $[19] = t2; $[20] = t3; $[21] = type; $[22] = weight; } else { color = $[12]; handleClick = $[13]; handleContextMenu = $[14]; handleDoubleClick = $[15]; handlePointerEnter = $[16]; handlePointerLeave = $[17]; label = $[18]; t2 = $[19]; t3 = $[20]; type = $[21]; weight = $[22]; } if (t3 !== /* @__PURE__ */ Symbol.for("react.early_return_sentinel")) { return t3; } const { path: pathD, labelX, labelY } = t2; const edgeColor = isSelected ? SELECTED_EDGE_COLOR : color || styles4.edges.defaultColor; const baseStrokeWidth = weight || styles4.edges.defaultWeight; const effectiveStrokeWidth = (isSelected ? baseStrokeWidth * 1.5 : baseStrokeWidth) / zoom; const effectiveFontSize = 10 / zoom; const hitboxStrokeWidth = Math.max(10, baseStrokeWidth * 3) / zoom; const isDimmedByFilter = isFilterActive && !searchResults.has(edgeData.sourceId) && !searchResults.has(edgeData.targetId) && !searchEdgeResults.has(edgeKey); const edgeOpacity = isDimmedByFilter ? 0.2 : 1; let t4; if ($[43] !== edgeOpacity) { t4 = { cursor: "pointer", opacity: edgeOpacity, transition: "opacity 150ms ease" }; $[43] = edgeOpacity; $[44] = t4; } else { t4 = $[44]; } const t5 = isDimmedByFilter ? "none" : "stroke"; let t6; if ($[45] !== t5) { t6 = { pointerEvents: t5 }; $[45] = t5; $[46] = t6; } else { t6 = $[46]; } let t7; if ($[47] !== handleClick || $[48] !== handleContextMenu || $[49] !== handleDoubleClick || $[50] !== handlePointerEnter || $[51] !== handlePointerLeave || $[52] !== hitboxStrokeWidth || $[53] !== pathD || $[54] !== t6) { t7 = /* @__PURE__ */ _jsx13("path", { d: pathD, stroke: "transparent", strokeWidth: hitboxStrokeWidth, fill: "none", style: t6, onClick: handleClick, onDoubleClick: handleDoubleClick, onContextMenu: handleContextMenu, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave }); $[47] = handleClick; $[48] = handleContextMenu; $[49] = handleDoubleClick; $[50] = handlePointerEnter; $[51] = handlePointerLeave; $[52] = hitboxStrokeWidth; $[53] = pathD; $[54] = t6; $[55] = t7; } else { t7 = $[55]; } const t8 = type === "dashed" ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : void 0; let t9; if ($[56] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = { pointerEvents: "none" }; $[56] = t9; } else { t9 = $[56]; } let t10; if ($[57] !== edgeColor || $[58] !== effectiveStrokeWidth || $[59] !== pathD || $[60] !== t8) { t10 = /* @__PURE__ */ _jsx13("path", { d: pathD, stroke: edgeColor, strokeWidth: effectiveStrokeWidth, fill: "none", strokeDasharray: t8, style: t9 }); $[57] = edgeColor; $[58] = effectiveStrokeWidth; $[59] = pathD; $[60] = t8; $[61] = t10; } else { t10 = $[61]; } let t11; if ($[62] !== edgeKey || $[63] !== effectiveFontSize || $[64] !== label || $[65] !== labelX || $[66] !== labelY || $[67] !== setEditingEdgeLabel || $[68] !== styles4.edges.labelColor || $[69] !== styles4.edges.labelStrokeColor || $[70] !== zoom) { t11 = label && /* @__PURE__ */ _jsx13("text", { x: labelX, y: labelY, fontSize: `${effectiveFontSize}px`, fill: styles4.edges.labelColor, stroke: styles4.edges.labelStrokeColor, strokeWidth: `${0.2 / zoom}px`, textAnchor: "middle", dy: `${-5 / zoom}px`, style: { pointerEvents: "visiblePainted", cursor: "text" }, onDoubleClick: (e_2) => { e_2.stopPropagation(); e_2.preventDefault(); setEditingEdgeLabel(edgeKey); }, children: label }); $[62] = edgeKey; $[63] = effectiveFontSize; $[64] = label; $[65] = labelX; $[66] = labelY; $[67] = setEditingEdgeLabel; $[68] = styles4.edges.labelColor; $[69] = styles4.edges.labelStrokeColor; $[70] = zoom; $[71] = t11; } else { t11 = $[71]; } let t12; if ($[72] !== t10 || $[73] !== t11 || $[74] !== t4 || $[75] !== t7) { t12 = /* @__PURE__ */ _jsxs5("g", { style: t4, children: [t7, t10, t11] }); $[72] = t10; $[73] = t11; $[74] = t4; $[75] = t7; $[76] = t12; } else { t12 = $[76]; } return t12; } function DepartingEdgeItem(t0) { const $ = _c40(14); const { edgeData } = t0; const styles4 = useCanvasStyle(); const zoom = useAtomValue29(zoomAtom); let color; let t1; let t2; let type; let weight; if ($[0] !== edgeData || $[1] !== styles4.edges.pathType || $[2] !== zoom) { t2 = /* @__PURE__ */ Symbol.for("react.early_return_sentinel"); bb0: { const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier"); const { sourcePosition, targetPosition, color: t32, weight: t42, type: t52, sourceEndpoint, targetEndpoint } = edgeData; color = t32; weight = t42; type = t52; const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2; const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2; const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2; const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2; if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) { t2 = null; break bb0; } t1 = pathCalculator({ x1, y1, x2, y2, sourceWidth: edgeData.sourceNodeWidth, sourceHeight: edgeData.sourceNodeHeight, targetWidth: edgeData.targetNodeWidth, targetHeight: edgeData.targetNodeHeight }); } $[0] = edgeData; $[1] = styles4.edges.pathType; $[2] = zoom; $[3] = color; $[4] = t1; $[5] = t2; $[6] = type; $[7] = weight; } else { color = $[3]; t1 = $[4]; t2 = $[5]; type = $[6]; weight = $[7]; } if (t2 !== /* @__PURE__ */ Symbol.for("react.early_return_sentinel")) { return t2; } const { path: pathD } = t1; const edgeColor = color || styles4.edges.defaultColor; const baseStrokeWidth = weight || styles4.edges.defaultWeight; const effectiveStrokeWidth = baseStrokeWidth / zoom; const t3 = type === "dashed" ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : void 0; let t4; if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { pointerEvents: "none" }; $[8] = t4; } else { t4 = $[8]; } let t5; if ($[9] !== edgeColor || $[10] !== effectiveStrokeWidth || $[11] !== pathD || $[12] !== t3) { t5 = /* @__PURE__ */ _jsx13("g", { className: "canvas-edge-exit", children: /* @__PURE__ */ _jsx13("path", { d: pathD, stroke: edgeColor, strokeWidth: effectiveStrokeWidth, fill: "none", strokeDasharray: t3, style: t4 }) }); $[9] = edgeColor; $[10] = effectiveStrokeWidth; $[11] = pathD; $[12] = t3; $[13] = t5; } else { t5 = $[13]; } return t5; } // src/components/EdgeLabelEditor.tsx init_graph_derived(); init_graph_mutations(); init_viewport_store(); init_edge_path_calculators(); import { c as _c41 } from "react/compiler-runtime"; import React14, { useRef as useRef19, useEffect as useEffect13 } from "react"; import { useAtomValue as useAtomValue30, useSetAtom as useSetAtom25 } from "jotai"; import { jsx as _jsx14 } from "react/jsx-runtime"; function EdgeLabelEditor(t0) { const $ = _c41(3); const { className } = t0; const editingEdgeKey = useAtomValue30(editingEdgeLabelAtom); if (!editingEdgeKey) { return null; } let t1; if ($[0] !== className || $[1] !== editingEdgeKey) { t1 = /* @__PURE__ */ _jsx14(EdgeLabelInput, { edgeKey: editingEdgeKey, className }); $[0] = className; $[1] = editingEdgeKey; $[2] = t1; } else { t1 = $[2]; } return t1; } function EdgeLabelInput(t0) { const $ = _c41(29); const { edgeKey, className } = t0; let t1; if ($[0] !== edgeKey) { t1 = edgeFamilyAtom(edgeKey); $[0] = edgeKey; $[1] = t1; } else { t1 = $[1]; } const edgeData = useAtomValue30(t1); const setEditingEdge = useSetAtom25(editingEdgeLabelAtom); const updateLabel = useSetAtom25(updateEdgeLabelAtom); const pan = useAtomValue30(panAtom); const zoom = useAtomValue30(zoomAtom); const styles4 = useCanvasStyle(); const inputRef = useRef19(null); let t2; let t3; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = () => { if (inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }; t3 = []; $[2] = t2; $[3] = t3; } else { t2 = $[2]; t3 = $[3]; } useEffect13(t2, t3); if (!edgeData) { setEditingEdge(null); return null; } const { sourcePosition, targetPosition, sourceEndpoint, targetEndpoint } = edgeData; const x1 = sourceEndpoint?.x ?? sourcePosition.x + edgeData.sourceNodeWidth / 2; const y1 = sourceEndpoint?.y ?? sourcePosition.y + edgeData.sourceNodeHeight / 2; const x2 = targetEndpoint?.x ?? targetPosition.x + edgeData.targetNodeWidth / 2; const y2 = targetEndpoint?.y ?? targetPosition.y + edgeData.targetNodeHeight / 2; let t4; if ($[4] !== edgeData.sourceNodeHeight || $[5] !== edgeData.sourceNodeWidth || $[6] !== edgeData.targetNodeHeight || $[7] !== edgeData.targetNodeWidth || $[8] !== styles4.edges.pathType || $[9] !== x1 || $[10] !== x2 || $[11] !== y1 || $[12] !== y2) { const pathCalculator = getEdgePathCalculator(styles4.edges.pathType || "bezier"); t4 = pathCalculator({ x1, y1, x2, y2, sourceWidth: edgeData.sourceNodeWidth, sourceHeight: edgeData.sourceNodeHeight, targetWidth: edgeData.targetNodeWidth, targetHeight: edgeData.targetNodeHeight }); $[4] = edgeData.sourceNodeHeight; $[5] = edgeData.sourceNodeWidth; $[6] = edgeData.targetNodeHeight; $[7] = edgeData.targetNodeWidth; $[8] = styles4.edges.pathType; $[9] = x1; $[10] = x2; $[11] = y1; $[12] = y2; $[13] = t4; } else { t4 = $[13]; } const { labelX, labelY } = t4; const screenX = labelX * zoom + pan.x; const screenY = labelY * zoom + pan.y; let t5; if ($[14] !== edgeKey || $[15] !== setEditingEdge || $[16] !== updateLabel) { t5 = (e) => { updateLabel({ edgeKey, label: e.target.value }); setEditingEdge(null); }; $[14] = edgeKey; $[15] = setEditingEdge; $[16] = updateLabel; $[17] = t5; } else { t5 = $[17]; } const handleBlur = t5; let t6; if ($[18] !== setEditingEdge) { t6 = (e_0) => { if (e_0.key === "Enter") { e_0.currentTarget.blur(); } else { if (e_0.key === "Escape") { setEditingEdge(null); } } }; $[18] = setEditingEdge; $[19] = t6; } else { t6 = $[19]; } const handleKeyDown = t6; const t7 = edgeData.label || ""; let t8; if ($[20] !== screenX || $[21] !== screenY) { t8 = { position: "absolute", left: screenX, top: screenY, transform: "translate(-50%, -50%)", zIndex: 1e4, fontSize: "10px", padding: "2px 6px", border: "1px solid #3b82f6", borderRadius: "3px", outline: "none", background: "white", textAlign: "center", minWidth: "60px" }; $[20] = screenX; $[21] = screenY; $[22] = t8; } else { t8 = $[22]; } let t9; if ($[23] !== className || $[24] !== handleBlur || $[25] !== handleKeyDown || $[26] !== t7 || $[27] !== t8) { t9 = /* @__PURE__ */ _jsx14("input", { ref: inputRef, "data-no-drag": "true", className, defaultValue: t7, onBlur: handleBlur, onKeyDown: handleKeyDown, style: t8 }); $[23] = className; $[24] = handleBlur; $[25] = handleKeyDown; $[26] = t7; $[27] = t8; $[28] = t9; } else { t9 = $[28]; } return t9; } // src/components/EdgePreviewLine.tsx init_graph_store(); init_edge_path_calculators(); import { c as _c42 } from "react/compiler-runtime"; import React15 from "react"; import { useAtomValue as useAtomValue31 } from "jotai"; import { jsx as _jsx15, jsxs as _jsxs6 } from "react/jsx-runtime"; var DASH_ARRAY = "8 4"; var IDLE_COLOR = "rgba(150, 150, 150, 0.6)"; var VALID_TARGET_COLOR = "var(--canvas-edge-preview-valid, #4ade80)"; var SNAP_COLOR = "var(--canvas-edge-preview-snap, #60a5fa)"; function EdgePreviewLine() { const $ = _c42(28); const edgeCreation = useAtomValue31(edgeCreationAtom); if (!edgeCreation.isCreating || !edgeCreation.sourceNodePosition) { return null; } const source = edgeCreation.sourceNodePosition; const target = edgeCreation.snappedTargetPosition ?? edgeCreation.targetPosition; if (!target) { return null; } const hasValidTarget = edgeCreation.hoveredTargetNodeId !== null; const isSnapped = edgeCreation.snappedTargetPosition !== null; let strokeColor = IDLE_COLOR; if (isSnapped) { strokeColor = SNAP_COLOR; } else { if (hasValidTarget) { strokeColor = VALID_TARGET_COLOR; } } let t0; if ($[0] !== source.x || $[1] !== source.y || $[2] !== target.x || $[3] !== target.y) { const pathInput = { x1: source.x, y1: source.y, x2: target.x, y2: target.y, sourceWidth: 0, sourceHeight: 0, targetWidth: 0, targetHeight: 0 }; t0 = bezierSmart(pathInput); $[0] = source.x; $[1] = source.y; $[2] = target.x; $[3] = target.y; $[4] = t0; } else { t0 = $[4]; } const { path } = t0; const dotRadius = isSnapped ? 6 : 4; let t1; if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none", overflow: "visible", zIndex: 5 }; $[5] = t1; } else { t1 = $[5]; } const t2 = isSnapped ? 2.5 : 2; let t3; let t4; if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t3 = { transition: "stroke 150ms, stroke-width 150ms" }; t4 = /* @__PURE__ */ _jsx15("animate", { attributeName: "stroke-dashoffset", from: "24", to: "0", dur: "0.8s", repeatCount: "indefinite" }); $[6] = t3; $[7] = t4; } else { t3 = $[6]; t4 = $[7]; } let t5; if ($[8] !== path || $[9] !== strokeColor || $[10] !== t2) { t5 = /* @__PURE__ */ _jsx15("path", { d: path, fill: "none", stroke: strokeColor, strokeWidth: t2, strokeDasharray: DASH_ARRAY, strokeLinecap: "round", style: t3, children: t4 }); $[8] = path; $[9] = strokeColor; $[10] = t2; $[11] = t5; } else { t5 = $[11]; } let t6; if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t6 = { transition: "fill 150ms" }; $[12] = t6; } else { t6 = $[12]; } let t7; if ($[13] !== source.x || $[14] !== source.y || $[15] !== strokeColor) { t7 = /* @__PURE__ */ _jsx15("circle", { cx: source.x, cy: source.y, r: 4, fill: strokeColor, style: t6 }); $[13] = source.x; $[14] = source.y; $[15] = strokeColor; $[16] = t7; } else { t7 = $[16]; } const t8 = isSnapped ? SNAP_COLOR : "transparent"; let t9; if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = { transition: "r 150ms, fill 150ms" }; $[17] = t9; } else { t9 = $[17]; } let t10; if ($[18] !== dotRadius || $[19] !== strokeColor || $[20] !== t8 || $[21] !== target.x || $[22] !== target.y) { t10 = /* @__PURE__ */ _jsx15("circle", { cx: target.x, cy: target.y, r: dotRadius, fill: t8, stroke: strokeColor, strokeWidth: 1.5, style: t9 }); $[18] = dotRadius; $[19] = strokeColor; $[20] = t8; $[21] = target.x; $[22] = target.y; $[23] = t10; } else { t10 = $[23]; } let t11; if ($[24] !== t10 || $[25] !== t5 || $[26] !== t7) { t11 = /* @__PURE__ */ _jsxs6("svg", { style: t1, children: [t5, t7, t10] }); $[24] = t10; $[25] = t5; $[26] = t7; $[27] = t11; } else { t11 = $[27]; } return t11; } // src/hooks/useSelectionChangeEffect.ts init_selection_store(); import { c as _c43 } from "react/compiler-runtime"; import { useEffect as useEffect14, useRef as useRef20 } from "react"; import { useAtomValue as useAtomValue32 } from "jotai"; function useSelectionChangeEffect(onSelectionChange) { const $ = _c43(5); const selectedNodeIds = useAtomValue32(selectedNodeIdsAtom); const selectedEdgeId = useAtomValue32(selectedEdgeIdAtom); const isFirstRender = useRef20(true); let t0; let t1; if ($[0] !== onSelectionChange || $[1] !== selectedEdgeId || $[2] !== selectedNodeIds) { t0 = () => { if (isFirstRender.current) { isFirstRender.current = false; return; } onSelectionChange?.(selectedNodeIds, selectedEdgeId); }; t1 = [selectedNodeIds, selectedEdgeId, onSelectionChange]; $[0] = onSelectionChange; $[1] = selectedEdgeId; $[2] = selectedNodeIds; $[3] = t0; $[4] = t1; } else { t0 = $[3]; t1 = $[4]; } useEffect14(t0, t1); } // src/hooks/useViewportChangeEffect.ts init_viewport_store(); import { c as _c44 } from "react/compiler-runtime"; import { useEffect as useEffect15, useRef as useRef21 } from "react"; import { useAtomValue as useAtomValue33 } from "jotai"; function useViewportChangeEffect(onViewportChange) { const $ = _c44(5); const zoom = useAtomValue33(zoomAtom); const pan = useAtomValue33(panAtom); const rafRef = useRef21(null); const isFirstRender = useRef21(true); let t0; let t1; if ($[0] !== onViewportChange || $[1] !== pan || $[2] !== zoom) { t0 = () => { if (isFirstRender.current) { isFirstRender.current = false; return; } if (!onViewportChange) { return; } if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); } rafRef.current = requestAnimationFrame(() => { onViewportChange({ zoom, pan }); rafRef.current = null; }); return () => { if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); } }; }; t1 = [zoom, pan, onViewportChange]; $[0] = onViewportChange; $[1] = pan; $[2] = zoom; $[3] = t0; $[4] = t1; } else { t0 = $[3]; t1 = $[4]; } useEffect15(t0, t1); } // src/hooks/useDragLifecycleEffect.ts init_graph_store(); init_selection_store(); import { c as _c45 } from "react/compiler-runtime"; import { useEffect as useEffect16, useRef as useRef22 } from "react"; import { useAtomValue as useAtomValue34, useStore as useStore5 } from "jotai"; function useDragLifecycleEffect(onDragStart, onDragEnd) { const $ = _c45(7); const draggingNodeId = useAtomValue34(draggingNodeIdAtom); const store = useStore5(); const prevDraggingRef = useRef22(null); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = []; $[0] = t0; } else { t0 = $[0]; } const draggedNodeIdsRef = useRef22(t0); let t1; let t2; if ($[1] !== draggingNodeId || $[2] !== onDragEnd || $[3] !== onDragStart || $[4] !== store) { t1 = () => { const prev = prevDraggingRef.current; prevDraggingRef.current = draggingNodeId; if (prev === null && draggingNodeId !== null) { const selectedIds = Array.from(store.get(selectedNodeIdsAtom)); draggedNodeIdsRef.current = selectedIds; onDragStart?.(selectedIds); } else { if (prev !== null && draggingNodeId === null) { const nodeIds = draggedNodeIdsRef.current; if (onDragEnd && nodeIds.length > 0) { const graph = store.get(graphAtom); const positions = /* @__PURE__ */ new Map(); for (const id of nodeIds) { if (graph.hasNode(id)) { const attrs = graph.getNodeAttributes(id); positions.set(id, { x: attrs.x, y: attrs.y }); } } onDragEnd(nodeIds, positions); } draggedNodeIdsRef.current = []; } } }; t2 = [draggingNodeId, store, onDragStart, onDragEnd]; $[1] = draggingNodeId; $[2] = onDragEnd; $[3] = onDragStart; $[4] = store; $[5] = t1; $[6] = t2; } else { t1 = $[5]; t2 = $[6]; } useEffect16(t1, t2); } // src/components/Canvas.tsx import { jsx as _jsx16, jsxs as _jsxs7 } from "react/jsx-runtime"; function Canvas(t0) { const $ = _c46(44); let children; let gestureConfig; let nodeWrapper; let onAction; let onDragEnd; let onDragStart; let onEdgeClick; let onEdgeDoubleClick; let onEdgeHover; let onEdgeLeave; let onEdgeRightClick; let onNodePersist; let onNodePersistError; let onNodeRenderError; let onSelectionChange; let onViewportChange; let renderErrorFallback; let renderNode; let viewportProps; if ($[0] !== t0) { ({ renderNode, onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave, onSelectionChange, onViewportChange, onDragStart, onDragEnd, onNodePersist, onNodePersistError, onNodeRenderError, renderErrorFallback, gestureConfig, onAction, nodeWrapper, children, ...viewportProps } = t0); $[0] = t0; $[1] = children; $[2] = gestureConfig; $[3] = nodeWrapper; $[4] = onAction; $[5] = onDragEnd; $[6] = onDragStart; $[7] = onEdgeClick; $[8] = onEdgeDoubleClick; $[9] = onEdgeHover; $[10] = onEdgeLeave; $[11] = onEdgeRightClick; $[12] = onNodePersist; $[13] = onNodePersistError; $[14] = onNodeRenderError; $[15] = onSelectionChange; $[16] = onViewportChange; $[17] = renderErrorFallback; $[18] = renderNode; $[19] = viewportProps; } else { children = $[1]; gestureConfig = $[2]; nodeWrapper = $[3]; onAction = $[4]; onDragEnd = $[5]; onDragStart = $[6]; onEdgeClick = $[7]; onEdgeDoubleClick = $[8]; onEdgeHover = $[9]; onEdgeLeave = $[10]; onEdgeRightClick = $[11]; onNodePersist = $[12]; onNodePersistError = $[13]; onNodeRenderError = $[14]; onSelectionChange = $[15]; onViewportChange = $[16]; renderErrorFallback = $[17]; renderNode = $[18]; viewportProps = $[19]; } useSelectionChangeEffect(onSelectionChange); useViewportChangeEffect(onViewportChange); useDragLifecycleEffect(onDragStart, onDragEnd); let t1; if ($[20] !== onEdgeClick || $[21] !== onEdgeDoubleClick || $[22] !== onEdgeHover || $[23] !== onEdgeLeave || $[24] !== onEdgeRightClick) { t1 = /* @__PURE__ */ _jsx16(EdgeRenderer, { onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave }); $[20] = onEdgeClick; $[21] = onEdgeDoubleClick; $[22] = onEdgeHover; $[23] = onEdgeLeave; $[24] = onEdgeRightClick; $[25] = t1; } else { t1 = $[25]; } let t2; if ($[26] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = /* @__PURE__ */ _jsx16(EdgePreviewLine, {}); $[26] = t2; } else { t2 = $[26]; } let t3; if ($[27] !== nodeWrapper || $[28] !== onNodePersist || $[29] !== onNodePersistError || $[30] !== onNodeRenderError || $[31] !== renderErrorFallback || $[32] !== renderNode) { t3 = /* @__PURE__ */ _jsx16(NodeRenderer, { renderNode, onNodePersist, onNodePersistError, onNodeRenderError, renderErrorFallback, nodeWrapper }); $[27] = nodeWrapper; $[28] = onNodePersist; $[29] = onNodePersistError; $[30] = onNodeRenderError; $[31] = renderErrorFallback; $[32] = renderNode; $[33] = t3; } else { t3 = $[33]; } let t4; if ($[34] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = /* @__PURE__ */ _jsx16(EdgeLabelEditor, {}); $[34] = t4; } else { t4 = $[34]; } let t5; if ($[35] !== children || $[36] !== t1 || $[37] !== t3 || $[38] !== viewportProps) { t5 = /* @__PURE__ */ _jsxs7(Viewport, { ...viewportProps, children: [t1, t2, t3, t4, children] }); $[35] = children; $[36] = t1; $[37] = t3; $[38] = viewportProps; $[39] = t5; } else { t5 = $[39]; } let t6; if ($[40] !== gestureConfig || $[41] !== onAction || $[42] !== t5) { t6 = /* @__PURE__ */ _jsx16(InputProvider, { gestureConfig, onAction, children: t5 }); $[40] = gestureConfig; $[41] = onAction; $[42] = t5; $[43] = t6; } else { t6 = $[43]; } return t6; } // src/components/ConnectedNode.tsx import { c as _c47 } from "react/compiler-runtime"; import React17 from "react"; init_debug(); import { jsx as _jsx17 } from "react/jsx-runtime"; var debug20 = createDebug("connected-node"); function ConnectedNode(t0) { const $ = _c47(10); const { nodeData, renderContent, onPersistError: customErrorHandler, wrapper } = t0; const { mutateAsync: updateNode } = useUpdateNode(); let t1; if ($[0] !== updateNode) { t1 = async (nodeId, graphId, uiProperties) => { debug20("Persisting node %s to graph %s", nodeId, graphId); await updateNode({ nodeId, graphId, updates: { ui_properties: uiProperties } }); }; $[0] = updateNode; $[1] = t1; } else { t1 = $[1]; } const handlePersist = t1; let t2; if ($[2] !== customErrorHandler) { t2 = (nodeId_0, error) => { debug20("Failed to persist node %s: %o", nodeId_0, error); customErrorHandler?.(nodeId_0, error); }; $[2] = customErrorHandler; $[3] = t2; } else { t2 = $[3]; } const handlePersistError = t2; let t3; if ($[4] !== handlePersist || $[5] !== handlePersistError || $[6] !== nodeData || $[7] !== renderContent || $[8] !== wrapper) { t3 = /* @__PURE__ */ _jsx17(Node, { nodeData, renderContent, onPersist: handlePersist, onPersistError: handlePersistError, wrapper }); $[4] = handlePersist; $[5] = handlePersistError; $[6] = nodeData; $[7] = renderContent; $[8] = wrapper; $[9] = t3; } else { t3 = $[9]; } return t3; } // src/components/ConnectedNodeRenderer.tsx init_graph_position(); init_graph_derived(); init_virtualization_store(); import { c as _c48 } from "react/compiler-runtime"; import React18 from "react"; import { useAtomValue as useAtomValue35 } from "jotai"; import { jsx as _jsx18, Fragment as _Fragment4 } from "react/jsx-runtime"; function ConnectedNodeRenderer(t0) { const $ = _c48(11); const { renderNode, onNodePersistError, nodeWrapper } = t0; const nodeKeys = useAtomValue35(visibleNodeKeysAtom); let t1; if ($[0] !== nodeKeys || $[1] !== nodeWrapper || $[2] !== onNodePersistError || $[3] !== renderNode) { let t22; if ($[5] !== nodeWrapper || $[6] !== onNodePersistError || $[7] !== renderNode) { t22 = (key) => /* @__PURE__ */ _jsx18(ConnectedNodeItem, { nodeKey: key, renderNode, onNodePersistError, nodeWrapper }, key); $[5] = nodeWrapper; $[6] = onNodePersistError; $[7] = renderNode; $[8] = t22; } else { t22 = $[8]; } t1 = nodeKeys.map(t22); $[0] = nodeKeys; $[1] = nodeWrapper; $[2] = onNodePersistError; $[3] = renderNode; $[4] = t1; } else { t1 = $[4]; } let t2; if ($[9] !== t1) { t2 = /* @__PURE__ */ _jsx18(_Fragment4, { children: t1 }); $[9] = t1; $[10] = t2; } else { t2 = $[10]; } return t2; } function ConnectedNodeItem(t0) { const $ = _c48(15); const { nodeKey, renderNode, onNodePersistError, nodeWrapper } = t0; let t1; if ($[0] !== nodeKey) { t1 = nodeFamilyAtom(nodeKey); $[0] = nodeKey; $[1] = t1; } else { t1 = $[1]; } const nodeData = useAtomValue35(t1); let t2; if ($[2] !== nodeKey) { t2 = nodePositionAtomFamily(nodeKey); $[2] = nodeKey; $[3] = t2; } else { t2 = $[3]; } const position = useAtomValue35(t2); if (!nodeData) { return null; } let t3; if ($[4] !== position.x || $[5] !== position.y) { t3 = { position: "absolute", left: position.x, top: position.y, transform: "translate(0, 0)" }; $[4] = position.x; $[5] = position.y; $[6] = t3; } else { t3 = $[6]; } let t4; if ($[7] !== nodeData || $[8] !== nodeWrapper || $[9] !== onNodePersistError || $[10] !== renderNode) { t4 = /* @__PURE__ */ _jsx18(ConnectedNode, { nodeData, renderContent: renderNode, onPersistError: onNodePersistError, wrapper: nodeWrapper }); $[7] = nodeData; $[8] = nodeWrapper; $[9] = onNodePersistError; $[10] = renderNode; $[11] = t4; } else { t4 = $[11]; } let t5; if ($[12] !== t3 || $[13] !== t4) { t5 = /* @__PURE__ */ _jsx18("div", { style: t3, children: t4 }); $[12] = t3; $[13] = t4; $[14] = t5; } else { t5 = $[14]; } return t5; } // src/components/NodeContextMenu.tsx init_graph_store(); init_selection_store(); init_input_store(); import { c as _c51 } from "react/compiler-runtime"; import React21, { useRef as useRef23 } from "react"; import { useAtomValue as useAtomValue36, useSetAtom as useSetAtom26 } from "jotai"; // src/components/ContextMenuAction.tsx import { c as _c49 } from "react/compiler-runtime"; import React19 from "react"; import { jsx as _jsx19, jsxs as _jsxs8 } from "react/jsx-runtime"; function ContextMenuAction(t0) { const $ = _c49(18); const { label, icon, onClick, destructive: t1, disabled: t2 } = t0; const destructive = t1 === void 0 ? false : t1; const disabled = t2 === void 0 ? false : t2; const t3 = disabled ? "not-allowed" : "pointer"; const t4 = disabled ? 0.5 : 1; const t5 = destructive ? "#ef4444" : "var(--canvas-text, #fff)"; let t6; if ($[0] !== t3 || $[1] !== t4 || $[2] !== t5) { t6 = { width: "100%", minHeight: "48px", padding: "12px 16px", display: "flex", alignItems: "center", gap: "12px", background: "none", border: "none", borderRadius: "8px", cursor: t3, opacity: t4, color: t5, fontSize: "16px", textAlign: "left", touchAction: "manipulation", WebkitTapHighlightColor: "transparent" }; $[0] = t3; $[1] = t4; $[2] = t5; $[3] = t6; } else { t6 = $[3]; } let t7; if ($[4] !== destructive || $[5] !== disabled) { t7 = (e) => { if (!disabled) { e.currentTarget.style.backgroundColor = destructive ? "rgba(239, 68, 68, 0.1)" : "rgba(255, 255, 255, 0.1)"; } }; $[4] = destructive; $[5] = disabled; $[6] = t7; } else { t7 = $[6]; } let t8; if ($[7] !== icon) { t8 = icon && /* @__PURE__ */ _jsx19("span", { style: { display: "flex" }, children: icon }); $[7] = icon; $[8] = t8; } else { t8 = $[8]; } let t9; if ($[9] !== label) { t9 = /* @__PURE__ */ _jsx19("span", { children: label }); $[9] = label; $[10] = t9; } else { t9 = $[10]; } let t10; if ($[11] !== disabled || $[12] !== onClick || $[13] !== t6 || $[14] !== t7 || $[15] !== t8 || $[16] !== t9) { t10 = /* @__PURE__ */ _jsxs8("button", { onClick, disabled, style: t6, onPointerEnter: t7, onPointerLeave: _temp9, children: [t8, t9] }); $[11] = disabled; $[12] = onClick; $[13] = t6; $[14] = t7; $[15] = t8; $[16] = t9; $[17] = t10; } else { t10 = $[17]; } return t10; } function _temp9(e_0) { e_0.currentTarget.style.backgroundColor = "transparent"; } // src/components/ContextMenuDivider.tsx import { c as _c50 } from "react/compiler-runtime"; import React20 from "react"; import { jsx as _jsx20 } from "react/jsx-runtime"; function ContextMenuDivider(t0) { const $ = _c50(7); const { label } = t0; const t1 = label ? "8px 12px" : "0"; let t2; if ($[0] !== t1) { t2 = { padding: t1, borderBottom: "1px solid var(--canvas-border, #333)", marginBottom: "4px" }; $[0] = t1; $[1] = t2; } else { t2 = $[1]; } let t3; if ($[2] !== label) { t3 = label && /* @__PURE__ */ _jsx20("div", { style: { fontSize: "14px", fontWeight: 500, color: "var(--canvas-text, #fff)" }, children: label }); $[2] = label; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== t2 || $[5] !== t3) { t4 = /* @__PURE__ */ _jsx20("div", { style: t2, children: t3 }); $[4] = t2; $[5] = t3; $[6] = t4; } else { t4 = $[6]; } return t4; } // src/components/NodeContextMenu.tsx import { jsx as _jsx21, jsxs as _jsxs9 } from "react/jsx-runtime"; var DefaultDialog = (t0) => { const $ = _c51(14); const { open, onOpenChange, children } = t0; const inputSource = useAtomValue36(primaryInputSourceAtom); const isTouchInput = inputSource === "finger" || inputSource === "pencil"; if (!open) { return null; } if (isTouchInput) { let t12; if ($[0] !== onOpenChange) { t12 = () => onOpenChange(false); $[0] = onOpenChange; $[1] = t12; } else { t12 = $[1]; } let t22; if ($[2] !== children || $[3] !== t12) { t22 = /* @__PURE__ */ _jsx21(BottomSheet, { onClose: t12, children }); $[2] = children; $[3] = t12; $[4] = t22; } else { t22 = $[4]; } return t22; } let t1; if ($[5] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { position: "fixed", inset: 0, zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(0, 0, 0, 0.5)" }; $[5] = t1; } else { t1 = $[5]; } let t2; if ($[6] !== onOpenChange) { t2 = () => onOpenChange(false); $[6] = onOpenChange; $[7] = t2; } else { t2 = $[7]; } let t3; if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t3 = { backgroundColor: "var(--canvas-bg, #1a1a1a)", border: "1px solid var(--canvas-border, #333)", borderRadius: "8px", padding: "8px 0", minWidth: "200px", maxWidth: "280px", boxShadow: "0 4px 20px rgba(0, 0, 0, 0.3)" }; $[8] = t3; } else { t3 = $[8]; } let t4; if ($[9] !== children) { t4 = /* @__PURE__ */ _jsx21("div", { style: t3, onClick: _temp10, onPointerDown: _temp23, children }); $[9] = children; $[10] = t4; } else { t4 = $[10]; } let t5; if ($[11] !== t2 || $[12] !== t4) { t5 = /* @__PURE__ */ _jsx21("div", { style: t1, onClick: t2, children: t4 }); $[11] = t2; $[12] = t4; $[13] = t5; } else { t5 = $[13]; } return t5; }; function BottomSheet(t0) { const $ = _c51(17); const { onClose, children } = t0; const sheetRef = useRef23(null); const startYRef = useRef23(null); let t1; if ($[0] !== onClose) { t1 = () => { onClose(); }; $[0] = onClose; $[1] = t1; } else { t1 = $[1]; } const handleBackdropClick = t1; let t2; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = (e) => { startYRef.current = e.clientY; }; $[2] = t2; } else { t2 = $[2]; } const handlePointerDown = t2; let t3; if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t3 = (e_0) => { if (startYRef.current === null || !sheetRef.current) { return; } const dy = e_0.clientY - startYRef.current; if (dy > 0) { sheetRef.current.style.transform = `translateY(${dy}px)`; } }; $[3] = t3; } else { t3 = $[3]; } const handlePointerMove = t3; let t4; if ($[4] !== onClose) { t4 = (e_1) => { if (startYRef.current === null) { return; } const dy_0 = e_1.clientY - startYRef.current; startYRef.current = null; if (dy_0 > 60) { onClose(); } else { if (sheetRef.current) { sheetRef.current.style.transform = "translateY(0)"; } } }; $[4] = onClose; $[5] = t4; } else { t4 = $[5]; } const handlePointerUp = t4; let t5; if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t5 = { position: "fixed", inset: 0, zIndex: 9999, display: "flex", flexDirection: "column", justifyContent: "flex-end", backgroundColor: "rgba(0, 0, 0, 0.4)" }; $[6] = t5; } else { t5 = $[6]; } let t6; if ($[7] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t6 = { backgroundColor: "var(--canvas-bg, #1a1a1a)", borderTop: "1px solid var(--canvas-border, #333)", borderRadius: "16px 16px 0 0", padding: "0 0 env(safe-area-inset-bottom, 16px)", maxHeight: "60vh", overflowY: "auto", transition: "transform 200ms ease-out", WebkitOverflowScrolling: "touch" }; $[7] = t6; } else { t6 = $[7]; } let t7; if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t7 = (e_3) => { e_3.stopPropagation(); handlePointerDown(e_3); }; $[8] = t7; } else { t7 = $[8]; } let t8; let t9; if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t8 = (e_4) => { startYRef.current = null; if (sheetRef.current) { sheetRef.current.style.transform = "translateY(0)"; } }; t9 = /* @__PURE__ */ _jsx21("div", { style: { display: "flex", justifyContent: "center", padding: "8px 0 4px", touchAction: "none" }, children: /* @__PURE__ */ _jsx21("div", { style: { width: "36px", height: "4px", borderRadius: "2px", backgroundColor: "var(--canvas-border, #666)" } }) }); $[9] = t8; $[10] = t9; } else { t8 = $[9]; t9 = $[10]; } let t10; if ($[11] !== children || $[12] !== handlePointerUp) { t10 = /* @__PURE__ */ _jsxs9("div", { ref: sheetRef, style: t6, onClick: _temp32, onPointerDown: t7, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: t8, children: [t9, children] }); $[11] = children; $[12] = handlePointerUp; $[13] = t10; } else { t10 = $[13]; } let t11; if ($[14] !== handleBackdropClick || $[15] !== t10) { t11 = /* @__PURE__ */ _jsx21("div", { style: t5, onClick: handleBackdropClick, children: t10 }); $[14] = handleBackdropClick; $[15] = t10; $[16] = t11; } else { t11 = $[16]; } return t11; } function _temp32(e_2) { return e_2.stopPropagation(); } function NodeContextMenu(t0) { const $ = _c51(13); const { open, onOpenChange, nodeId, actions: t1, renderContent, DialogComponent: t2 } = t0; const actions = t1 === void 0 ? [] : t1; const DialogComponent = t2 === void 0 ? DefaultDialog : t2; const currentGraphId = useAtomValue36(currentGraphIdAtom); useSetAtom26(clearSelectionAtom); let t3; if ($[0] !== onOpenChange) { t3 = () => { onOpenChange(false); }; $[0] = onOpenChange; $[1] = t3; } else { t3 = $[1]; } const closeMenu = t3; const ctx = { nodeId, graphId: currentGraphId, closeMenu }; if (renderContent) { return /* @__PURE__ */ _jsx21(DialogComponent, { open, onOpenChange, children: renderContent(ctx) }); } let t4; let t5; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = /* @__PURE__ */ _jsx21(ContextMenuDivider, { label: "Node Options" }); t5 = { padding: "0 4px" }; $[2] = t4; $[3] = t5; } else { t4 = $[2]; t5 = $[3]; } const t6 = actions.map((action) => /* @__PURE__ */ _jsx21(ContextMenuAction, { label: action.label, icon: action.icon, onClick: () => action.onClick(ctx), destructive: action.destructive, disabled: action.disabled }, action.key)); let t7; if ($[4] !== t5 || $[5] !== t6) { t7 = /* @__PURE__ */ _jsx21("div", { style: t5, children: t6 }); $[4] = t5; $[5] = t6; $[6] = t7; } else { t7 = $[6]; } let t8; if ($[7] !== DialogComponent || $[8] !== onOpenChange || $[9] !== open || $[10] !== t4 || $[11] !== t7) { t8 = /* @__PURE__ */ _jsxs9(DialogComponent, { open, onOpenChange, children: [t4, t7] }); $[7] = DialogComponent; $[8] = onOpenChange; $[9] = open; $[10] = t4; $[11] = t7; $[12] = t8; } else { t8 = $[12]; } return t8; } var DeleteIcon = () => { const $ = _c51(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsx21("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ _jsx21("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }); $[0] = t0; } else { t0 = $[0]; } return t0; }; var FitToViewIcon = () => { const $ = _c51(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsx21("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ _jsx21("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" }) }); $[0] = t0; } else { t0 = $[0]; } return t0; }; function _temp10(e) { return e.stopPropagation(); } function _temp23(e_0) { return e_0.stopPropagation(); } // src/components/LockedNodeOverlay.tsx init_locked_node_store(); import { c as _c52 } from "react/compiler-runtime"; import React22, { useEffect as useEffect17 } from "react"; import { useAtomValue as useAtomValue37, useSetAtom as useSetAtom27 } from "jotai"; import { jsx as _jsx22, jsxs as _jsxs10, Fragment as _Fragment5 } from "react/jsx-runtime"; var DefaultCloseButton = (t0) => { const $ = _c52(4); const { onClick } = t0; let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { color: "var(--hud-text, #fff)", opacity: 0.6 }; $[0] = t1; } else { t1 = $[0]; } let t2; if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = /* @__PURE__ */ _jsxs10("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [/* @__PURE__ */ _jsx22("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), /* @__PURE__ */ _jsx22("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }); $[1] = t2; } else { t2 = $[1]; } let t3; if ($[2] !== onClick) { t3 = /* @__PURE__ */ _jsx22("button", { onClick, className: "absolute top-4 right-4 z-10 p-2 rounded hover:bg-white/10 transition-colors", style: t1, "aria-label": "Close", children: t2 }); $[2] = onClick; $[3] = t3; } else { t3 = $[3]; } return t3; }; var DefaultFooter = (t0) => { const $ = _c52(13); const { pages, currentIndex, onPageSelect } = t0; let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { backgroundColor: "var(--hud-header-bg, rgba(20, 20, 30, 0.9))", borderColor: "var(--hud-border, rgba(255, 255, 255, 0.1))" }; $[0] = t1; } else { t1 = $[0]; } let t2; if ($[1] !== currentIndex || $[2] !== onPageSelect || $[3] !== pages) { let t32; if ($[5] !== currentIndex || $[6] !== onPageSelect) { t32 = (page, idx) => /* @__PURE__ */ _jsx22("button", { onClick: () => onPageSelect(idx), className: "w-2.5 h-2.5 rounded-full transition-all hover:scale-110", style: { backgroundColor: idx === currentIndex ? "var(--hud-accent, #3b82f6)" : "var(--hud-border, rgba(255, 255, 255, 0.3))", transform: idx === currentIndex ? "scale(1.3)" : "scale(1)" }, title: page.label }, page.key); $[5] = currentIndex; $[6] = onPageSelect; $[7] = t32; } else { t32 = $[7]; } t2 = pages.map(t32); $[1] = currentIndex; $[2] = onPageSelect; $[3] = pages; $[4] = t2; } else { t2 = $[4]; } let t3; if ($[8] !== t2) { t3 = /* @__PURE__ */ _jsx22("div", { className: "flex items-center gap-2", children: t2 }); $[8] = t2; $[9] = t3; } else { t3 = $[9]; } let t4; if ($[10] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = /* @__PURE__ */ _jsxs10("div", { className: "flex items-center gap-4 text-xs", style: { color: "var(--hud-text, #fff)", opacity: 0.5 }, children: [/* @__PURE__ */ _jsx22("span", { children: "\u2190 \u2192 Navigate" }), /* @__PURE__ */ _jsx22("span", { children: "ESC Exit" })] }); $[10] = t4; } else { t4 = $[10]; } let t5; if ($[11] !== t3) { t5 = /* @__PURE__ */ _jsxs10("footer", { className: "flex items-center justify-center gap-8 py-4 border-t", style: t1, children: [t3, t4] }); $[11] = t3; $[12] = t5; } else { t5 = $[12]; } return t5; }; var DefaultWrapper = (t0) => { const $ = _c52(2); const { children } = t0; let t1; if ($[0] !== children) { t1 = /* @__PURE__ */ _jsx22(_Fragment5, { children }); $[0] = children; $[1] = t1; } else { t1 = $[1]; } return t1; }; function LockedNodeOverlay(t0) { const $ = _c52(50); const { pages, renderContent, renderCloseButton, renderFooter, WrapperComponent: t1, className: t2, style } = t0; const WrapperComponent = t1 === void 0 ? DefaultWrapper : t1; const className = t2 === void 0 ? "" : t2; const lockedNodeId = useAtomValue37(lockedNodeIdAtom); const nodeData = useAtomValue37(lockedNodeDataAtom); const pageIndex = useAtomValue37(lockedNodePageIndexAtom); const setPageCount = useSetAtom27(lockedNodePageCountAtom); const unlock = useSetAtom27(unlockNodeAtom); let t3; let t4; if ($[0] !== pages.length || $[1] !== setPageCount) { t3 = () => { setPageCount(pages.length); }; t4 = [pages.length, setPageCount]; $[0] = pages.length; $[1] = setPageCount; $[2] = t3; $[3] = t4; } else { t3 = $[2]; t4 = $[3]; } useEffect17(t3, t4); const pageIndexSetter = useSetAtom27(lockedNodePageIndexAtom); let t5; if ($[4] !== pageIndexSetter || $[5] !== pages.length) { t5 = () => { pageIndexSetter((current) => (current + 1) % pages.length); }; $[4] = pageIndexSetter; $[5] = pages.length; $[6] = t5; } else { t5 = $[6]; } const nextPage = t5; let t6; if ($[7] !== pageIndexSetter || $[8] !== pages.length) { t6 = () => { pageIndexSetter((current_0) => (current_0 - 1 + pages.length) % pages.length); }; $[7] = pageIndexSetter; $[8] = pages.length; $[9] = t6; } else { t6 = $[9]; } const prevPage = t6; let t7; if ($[10] !== pageIndexSetter || $[11] !== pages.length) { t7 = (index_0) => { if (index_0 >= 0 && index_0 < pages.length) { pageIndexSetter(index_0); } }; $[10] = pageIndexSetter; $[11] = pages.length; $[12] = t7; } else { t7 = $[12]; } const handleGoToPage = t7; let t8; let t9; if ($[13] !== lockedNodeId || $[14] !== nextPage || $[15] !== prevPage || $[16] !== unlock) { t8 = () => { if (!lockedNodeId) { return; } const handleKeyDown = (e) => { const target = e.target; if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) { return; } bb46: switch (e.key) { case "Escape": { e.preventDefault(); unlock(); break bb46; } case "ArrowRight": case "ArrowDown": { e.preventDefault(); nextPage(); break bb46; } case "ArrowLeft": case "ArrowUp": { e.preventDefault(); prevPage(); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }; t9 = [lockedNodeId, unlock, nextPage, prevPage]; $[13] = lockedNodeId; $[14] = nextPage; $[15] = prevPage; $[16] = unlock; $[17] = t8; $[18] = t9; } else { t8 = $[17]; t9 = $[18]; } useEffect17(t8, t9); if (!lockedNodeId || !nodeData) { return null; } const currentPage = pages[pageIndex]?.key || pages[0]?.key; const totalPages = pages.length; let T0; let t10; let t11; let t12; let t13; if ($[19] !== WrapperComponent || $[20] !== className || $[21] !== currentPage || $[22] !== handleGoToPage || $[23] !== lockedNodeId || $[24] !== nextPage || $[25] !== nodeData || $[26] !== pageIndex || $[27] !== pages || $[28] !== prevPage || $[29] !== renderCloseButton || $[30] !== renderContent || $[31] !== renderFooter || $[32] !== style || $[33] !== totalPages || $[34] !== unlock) { const ctx = { nodeId: lockedNodeId, nodeData, currentPage, pageIndex, totalPages, close: unlock, nextPage, prevPage, goToPage: handleGoToPage }; T0 = WrapperComponent; t10 = `fixed inset-0 z-[9999] flex flex-col ${className}`; if ($[40] !== style) { t11 = { backgroundColor: "#0f0f14", isolation: "isolate", ...style }; $[40] = style; $[41] = t11; } else { t11 = $[41]; } t12 = /* @__PURE__ */ _jsxs10("div", { className: "flex-1 overflow-hidden relative", children: [renderCloseButton ? renderCloseButton(ctx) : /* @__PURE__ */ _jsx22(DefaultCloseButton, { onClick: unlock }), renderContent(ctx)] }); t13 = renderFooter ? renderFooter(ctx) : /* @__PURE__ */ _jsx22(DefaultFooter, { pages, currentIndex: pageIndex, onPageSelect: handleGoToPage }); $[19] = WrapperComponent; $[20] = className; $[21] = currentPage; $[22] = handleGoToPage; $[23] = lockedNodeId; $[24] = nextPage; $[25] = nodeData; $[26] = pageIndex; $[27] = pages; $[28] = prevPage; $[29] = renderCloseButton; $[30] = renderContent; $[31] = renderFooter; $[32] = style; $[33] = totalPages; $[34] = unlock; $[35] = T0; $[36] = t10; $[37] = t11; $[38] = t12; $[39] = t13; } else { T0 = $[35]; t10 = $[36]; t11 = $[37]; t12 = $[38]; t13 = $[39]; } let t14; if ($[42] !== t10 || $[43] !== t11 || $[44] !== t12 || $[45] !== t13) { t14 = /* @__PURE__ */ _jsxs10("div", { className: t10, style: t11, children: [t12, t13] }); $[42] = t10; $[43] = t11; $[44] = t12; $[45] = t13; $[46] = t14; } else { t14 = $[46]; } let t15; if ($[47] !== T0 || $[48] !== t14) { t15 = /* @__PURE__ */ _jsx22(T0, { children: t14 }); $[47] = T0; $[48] = t14; $[49] = t15; } else { t15 = $[49]; } return t15; } // src/components/EdgeOverlay.tsx init_graph_store(); init_graph_position(); init_graph_mutations(); init_graph_mutations_edges(); init_selection_store(); import React25, { useRef as useRef24 } from "react"; import { useSetAtom as useSetAtom28, useAtomValue as useAtomValue38 } from "jotai"; import { useGesture as useGesture3 } from "@use-gesture/react"; // src/components/EdgePath.tsx import { c as _c53 } from "react/compiler-runtime"; import React23 from "react"; import { jsx as _jsx23 } from "react/jsx-runtime"; function EdgePath(t0) { const $ = _c53(7); const { handleRef, className, style } = t0; let t1; if ($[0] !== style) { t1 = { position: "absolute", top: "50%", right: "-22px", height: "44px", width: "44px", transform: "translateY(-50%)", cursor: "crosshair", display: "flex", alignItems: "center", justifyContent: "center", touchAction: "none", ...style }; $[0] = style; $[1] = t1; } else { t1 = $[1]; } let t2; if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = /* @__PURE__ */ _jsx23("div", { style: { height: "32px", width: "12px", borderRadius: "3px", backgroundColor: "var(--list-item-border, #666)", transition: "background-color 0.2s", pointerEvents: "none" } }); $[2] = t2; } else { t2 = $[2]; } let t3; if ($[3] !== className || $[4] !== handleRef || $[5] !== t1) { t3 = /* @__PURE__ */ _jsx23("div", { ref: handleRef, className, style: t1, onPointerEnter: _temp11, onPointerLeave: _temp24, children: t2 }); $[3] = className; $[4] = handleRef; $[5] = t1; $[6] = t3; } else { t3 = $[6]; } return t3; } function _temp24(e_0) { const visual_0 = e_0.currentTarget.firstChild; if (visual_0) { visual_0.style.backgroundColor = "var(--list-item-border, #666)"; } } function _temp11(e) { const visual = e.currentTarget.firstChild; if (visual) { visual.style.backgroundColor = "var(--list-item-border-active, #888)"; } } // src/components/EdgeOverlay.tsx import { jsx as _jsx25, jsxs as _jsxs11 } from "react/jsx-runtime"; // src/components/EdgeLabel.tsx import { c as _c54 } from "react/compiler-runtime"; import React24 from "react"; import { jsx as _jsx24 } from "react/jsx-runtime"; function EdgeLabel(t0) { const $ = _c54(13); const { label, x, y, onClick, className, style } = t0; if (!label) { return null; } let t1; if ($[0] !== onClick) { t1 = (e) => { e.stopPropagation(); onClick?.(); }; $[0] = onClick; $[1] = t1; } else { t1 = $[1]; } const t2 = onClick ? "pointer" : "default"; const t3 = onClick ? "auto" : "none"; let t4; if ($[2] !== style || $[3] !== t2 || $[4] !== t3 || $[5] !== x || $[6] !== y) { t4 = { position: "absolute", left: x, top: y, transform: "translate(-50%, -50%)", padding: "2px 8px", fontSize: "12px", fontWeight: 500, color: "var(--canvas-text, #fff)", backgroundColor: "var(--canvas-bg, #1a1a1a)", border: "1px solid var(--canvas-border, #333)", borderRadius: "4px", whiteSpace: "nowrap", cursor: t2, userSelect: "none", pointerEvents: t3, ...style }; $[2] = style; $[3] = t2; $[4] = t3; $[5] = x; $[6] = y; $[7] = t4; } else { t4 = $[7]; } let t5; if ($[8] !== className || $[9] !== label || $[10] !== t1 || $[11] !== t4) { t5 = /* @__PURE__ */ _jsx24("div", { className, onClick: t1, style: t4, children: label }); $[8] = className; $[9] = label; $[10] = t1; $[11] = t4; $[12] = t5; } else { t5 = $[12]; } return t5; } // src/components/EdgeOverlay.tsx var defaultGetSourceHandle = (nodeId) => `widget:${nodeId}:output:data`; var defaultGetTargetHandle = (targetNodeId) => `widget:${targetNodeId}:input:default`; function EdgeOverlay({ nodeId, children, getSourceHandle = defaultGetSourceHandle, getTargetHandle = defaultGetTargetHandle, onEdgeCreate, onEdgeCreateError, onEdgeCreated, handleStyle, handleClassName, canConnect }) { const setEdgeCreationState = useSetAtom28(edgeCreationAtom); const graph = useAtomValue38(graphAtom); const addEdgeToLocalGraph = useSetAtom28(addEdgeToLocalGraphAtom); const removeEdgeFromLocalGraph = useSetAtom28(removeEdgeFromLocalGraphAtom); const swapEdgeAtomic = useSetAtom28(swapEdgeAtomicAtom); const addNodeToSelection = useSetAtom28(addNodesToSelectionAtom); const removeNodeFromSelection = useSetAtom28(removeNodesFromSelectionAtom); const currentGraphId = useAtomValue38(currentGraphIdAtom); const handleRef = useRef24(null); const wrapperRef = useRef24(null); const position = useAtomValue38(nodePositionAtomFamily(nodeId)); const nodeAttributes = graph.hasNode(nodeId) ? graph.getNodeAttributes(nodeId) : void 0; const width = nodeAttributes?.width ?? 500; const height = nodeAttributes?.height ?? 500; const handleDragStart = (x, y) => { if (!graph.hasNode(nodeId)) return; const sourceNodeAttributes = graph.getNodeAttributes(nodeId); if (!sourceNodeAttributes) return; const nodeRect = wrapperRef.current?.querySelector('[data-draggable-node="true"]')?.getBoundingClientRect(); if (!nodeRect) return; const sourceNodePosition = { x: sourceNodeAttributes.x + nodeRect.width / 2, y: sourceNodeAttributes.y + nodeRect.height / 2 }; const sourceHandle = getSourceHandle(nodeId); setEdgeCreationState({ isCreating: true, sourceNodeId: nodeId, sourceNodePosition, targetPosition: { x, y }, hoveredTargetNodeId: null, sourceHandle, targetHandle: null, sourcePort: null, targetPort: null, snappedTargetPosition: null }); }; const handleDrag = (x_0, y_0) => { setEdgeCreationState((prev) => { if (!prev.isCreating) return prev; const { nodeId: hitNodeId } = hitTestNode(x_0, y_0); let hoveredNodeId = null; let targetPort = null; const portInfo = hitTestPort(x_0, y_0); if (portInfo && portInfo.nodeId !== nodeId) { hoveredNodeId = portInfo.nodeId; targetPort = portInfo.portId; } else if (hitNodeId) { hoveredNodeId = hitNodeId; } if (hoveredNodeId && hoveredNodeId !== nodeId && hoveredNodeId !== prev.hoveredTargetNodeId) { if (prev.hoveredTargetNodeId) { removeNodeFromSelection([prev.hoveredTargetNodeId]); } addNodeToSelection([hoveredNodeId]); } return { ...prev, targetPosition: { x: x_0, y: y_0 }, hoveredTargetNodeId: hoveredNodeId && hoveredNodeId !== nodeId ? hoveredNodeId : null, targetPort }; }); }; const handleDragEnd = async (x_1, y_1) => { let targetNodeId = null; const { nodeId: hitNodeId_0 } = hitTestNode(x_1, y_1); if (hitNodeId_0) { targetNodeId = hitNodeId_0; } setEdgeCreationState((prev_0) => { if (prev_0.hoveredTargetNodeId) { removeNodeFromSelection([prev_0.hoveredTargetNodeId]); } return { isCreating: false, sourceNodeId: null, sourceNodePosition: null, targetPosition: null, hoveredTargetNodeId: null, sourceHandle: null, targetHandle: null, sourcePort: null, targetPort: null, snappedTargetPosition: null }; }); if (targetNodeId && targetNodeId !== nodeId && currentGraphId) { if (canConnect) { const allowed = canConnect({ nodeId, portId: null }, { nodeId: targetNodeId, portId: null }); if (!allowed) return; } const sourceHandle_0 = getSourceHandle(nodeId); const targetHandle = getTargetHandle(targetNodeId); const tempEdgeId = crypto.randomUUID(); const tempEdge = { id: tempEdgeId, graph_id: currentGraphId, source_node_id: nodeId, target_node_id: targetNodeId, edge_type: "data_connection", filter_condition: null, ui_properties: { style: "solid", color: "#999", sourceHandle: sourceHandle_0, targetHandle }, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }; addEdgeToLocalGraph(tempEdge); if (!onEdgeCreate) { return; } try { const newEdge = await onEdgeCreate({ graphId: currentGraphId, sourceNodeId: nodeId, targetNodeId, sourceHandle: sourceHandle_0, targetHandle, tempEdge }); swapEdgeAtomic({ tempEdgeId, newEdge }); onEdgeCreated?.({ edge: newEdge, sourceNodeId: nodeId, targetNodeId, sourceHandle: sourceHandle_0, targetHandle }); } catch (error) { removeEdgeFromLocalGraph(tempEdgeId); onEdgeCreateError?.(error, tempEdgeId); } } }; useGesture3({ onDragStart: ({ event, xy: [x_2, y_2] }) => { event.stopPropagation(); handleDragStart(x_2, y_2); }, onDrag: ({ xy: [x_3, y_3] }) => { handleDrag(x_3, y_3); }, onDragEnd: ({ xy: [x_4, y_4] }) => { handleDragEnd(x_4, y_4); } }, { target: handleRef, eventOptions: { passive: false } }); if (!position || !nodeAttributes) { return null; } return /* @__PURE__ */ _jsxs11("div", { ref: wrapperRef, style: { position: "absolute", top: position.y, left: position.x, width: `${width}px`, height: `${height}px` }, children: [children, /* @__PURE__ */ _jsx25(EdgePath, { handleRef, className: handleClassName, style: handleStyle })] }); } // src/components/NodeTypeCombobox.tsx init_node_type_registry(); import { c as _c57 } from "react/compiler-runtime"; import React28, { useState as useState4, useRef as useRef25, useEffect as useEffect18 } from "react"; import { useAtomValue as useAtomValue39 } from "jotai"; init_core(); init_debug(); // src/components/ComboboxSearch.tsx import { c as _c55 } from "react/compiler-runtime"; import React26 from "react"; import { jsx as _jsx26 } from "react/jsx-runtime"; var defaultInputStyle = { width: "100%", padding: "8px 12px", fontSize: "14px", border: "1px solid #ddd", borderRadius: "6px", outline: "none", backgroundColor: "#fff" }; function ComboboxSearch(t0) { const $ = _c55(14); const { inputRef, value, onChange, onKeyDown, onFocus, placeholder: t1, disabled: t2, isOpen, style } = t0; const placeholder = t1 === void 0 ? "Search..." : t1; const disabled = t2 === void 0 ? false : t2; let t3; if ($[0] !== onChange) { t3 = (e) => onChange(e.target.value); $[0] = onChange; $[1] = t3; } else { t3 = $[1]; } let t4; if ($[2] !== style) { t4 = { ...defaultInputStyle, ...style }; $[2] = style; $[3] = t4; } else { t4 = $[3]; } let t5; if ($[4] !== disabled || $[5] !== inputRef || $[6] !== isOpen || $[7] !== onFocus || $[8] !== onKeyDown || $[9] !== placeholder || $[10] !== t3 || $[11] !== t4 || $[12] !== value) { t5 = /* @__PURE__ */ _jsx26("input", { ref: inputRef, type: "text", value, onChange: t3, onFocus, onKeyDown, placeholder, disabled, style: t4, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-autocomplete": "list", role: "combobox" }); $[4] = disabled; $[5] = inputRef; $[6] = isOpen; $[7] = onFocus; $[8] = onKeyDown; $[9] = placeholder; $[10] = t3; $[11] = t4; $[12] = value; $[13] = t5; } else { t5 = $[13]; } return t5; } // src/components/ComboboxOption.tsx import { c as _c56 } from "react/compiler-runtime"; import React27 from "react"; import { jsx as _jsx27, jsxs as _jsxs12 } from "react/jsx-runtime"; var styles = { option: { display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", cursor: "pointer", transition: "background-color 0.1s" }, optionHighlighted: { backgroundColor: "#f0f0f0" }, icon: { fontSize: "16px", width: "20px", textAlign: "center" }, content: { display: "flex", flexDirection: "column", gap: "2px" }, label: { fontSize: "14px", fontWeight: 500 }, description: { fontSize: "12px", color: "#666" } }; function ComboboxOption(t0) { const $ = _c56(20); const { label, icon, description, highlighted: t1, onClick, onMouseEnter } = t0; const highlighted = t1 === void 0 ? false : t1; let t2; if ($[0] !== highlighted) { t2 = highlighted ? styles.optionHighlighted : {}; $[0] = highlighted; $[1] = t2; } else { t2 = $[1]; } let t3; if ($[2] !== t2) { t3 = { ...styles.option, ...t2 }; $[2] = t2; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== icon) { t4 = icon && /* @__PURE__ */ _jsx27("span", { style: styles.icon, children: icon }); $[4] = icon; $[5] = t4; } else { t4 = $[5]; } let t5; if ($[6] !== label) { t5 = /* @__PURE__ */ _jsx27("span", { style: styles.label, children: label }); $[6] = label; $[7] = t5; } else { t5 = $[7]; } let t6; if ($[8] !== description) { t6 = description && /* @__PURE__ */ _jsx27("span", { style: styles.description, children: description }); $[8] = description; $[9] = t6; } else { t6 = $[9]; } let t7; if ($[10] !== t5 || $[11] !== t6) { t7 = /* @__PURE__ */ _jsxs12("div", { style: styles.content, children: [t5, t6] }); $[10] = t5; $[11] = t6; $[12] = t7; } else { t7 = $[12]; } let t8; if ($[13] !== highlighted || $[14] !== onClick || $[15] !== onMouseEnter || $[16] !== t3 || $[17] !== t4 || $[18] !== t7) { t8 = /* @__PURE__ */ _jsxs12("li", { onClick, onMouseEnter, style: t3, role: "option", "aria-selected": highlighted, children: [t4, t7] }); $[13] = highlighted; $[14] = onClick; $[15] = onMouseEnter; $[16] = t3; $[17] = t4; $[18] = t7; $[19] = t8; } else { t8 = $[19]; } return t8; } // src/components/NodeTypeCombobox.tsx import { jsx as _jsx28, jsxs as _jsxs13 } from "react/jsx-runtime"; var debug21 = createDebug("node-type-combobox"); function NodeTypeCombobox(t0) { const $ = _c57(53); const { nodeTypes: customNodeTypes, onNodeCreated, createPosition: t1, placeholder: t2, clearOnCreate: t3, className, style, disabled: t4 } = t0; const createPosition = t1 === void 0 ? "center" : t1; const placeholder = t2 === void 0 ? "Add node..." : t2; const clearOnCreate = t3 === void 0 ? true : t3; const disabled = t4 === void 0 ? false : t4; const [isOpen, setIsOpen] = useState4(false); const [searchQuery, setSearchQuery] = useState4(""); const [highlightedIndex, setHighlightedIndex] = useState4(0); const inputRef = useRef25(null); const listRef = useRef25(null); const createNode = useCreateNode(); const graphId = useAtomValue39(currentGraphIdAtom); const viewportRect = useAtomValue39(viewportRectAtom); const pan = useAtomValue39(panAtom); const zoom = useAtomValue39(zoomAtom); let t5; if ($[0] !== customNodeTypes || $[1] !== searchQuery) { const nodeTypes = customNodeTypes ?? getRegisteredNodeTypes().map(_temp12); bb0: { if (!searchQuery.trim()) { t5 = nodeTypes; break bb0; } const query = searchQuery.toLowerCase(); t5 = nodeTypes.filter((opt) => opt.type.toLowerCase().includes(query) || opt.label?.toLowerCase().includes(query) || opt.description?.toLowerCase().includes(query)); } $[0] = customNodeTypes; $[1] = searchQuery; $[2] = t5; } else { t5 = $[2]; } const filteredTypes = t5; let t6; if ($[3] !== createPosition || $[4] !== pan || $[5] !== viewportRect?.height || $[6] !== viewportRect?.width || $[7] !== zoom) { t6 = () => { if (typeof createPosition === "object") { return createPosition; } const vpWidth = viewportRect?.width ?? 800; const vpHeight = viewportRect?.height ?? 600; if (createPosition === "random") { const centerX = (vpWidth / 2 - pan.x) / zoom; const centerY = (vpHeight / 2 - pan.y) / zoom; const offsetX = (Math.random() - 0.5) * 400; const offsetY = (Math.random() - 0.5) * 400; return { x: centerX + offsetX, y: centerY + offsetY }; } const centerX_0 = (vpWidth / 2 - pan.x) / zoom; const centerY_0 = (vpHeight / 2 - pan.y) / zoom; return { x: centerX_0, y: centerY_0 }; }; $[3] = createPosition; $[4] = pan; $[5] = viewportRect?.height; $[6] = viewportRect?.width; $[7] = zoom; $[8] = t6; } else { t6 = $[8]; } const getCreatePosition = t6; let t7; if ($[9] !== clearOnCreate || $[10] !== createNode || $[11] !== getCreatePosition || $[12] !== graphId || $[13] !== onNodeCreated) { t7 = (option) => { if (!graphId) { debug21.warn("No graphId available"); return; } const position = getCreatePosition(); const width = option.defaultWidth ?? 200; const height = option.defaultHeight ?? 150; createNode.mutate({ graph_id: graphId, label: option.label || option.type, node_type: option.type, configuration: null, ui_properties: { x: position.x - width / 2, y: position.y - height / 2, width, height } }, { onSuccess: (data) => { onNodeCreated?.(data.id, option.type); } }); if (clearOnCreate) { setSearchQuery(""); } setIsOpen(false); }; $[9] = clearOnCreate; $[10] = createNode; $[11] = getCreatePosition; $[12] = graphId; $[13] = onNodeCreated; $[14] = t7; } else { t7 = $[14]; } const handleSelect = t7; let t8; if ($[15] !== filteredTypes || $[16] !== handleSelect || $[17] !== highlightedIndex || $[18] !== isOpen) { t8 = (e) => { if (!isOpen) { if (e.key === "ArrowDown" || e.key === "Enter") { setIsOpen(true); e.preventDefault(); } return; } bb121: switch (e.key) { case "ArrowDown": { e.preventDefault(); setHighlightedIndex((prev_0) => Math.min(prev_0 + 1, filteredTypes.length - 1)); break bb121; } case "ArrowUp": { e.preventDefault(); setHighlightedIndex(_temp25); break bb121; } case "Enter": { e.preventDefault(); if (filteredTypes[highlightedIndex]) { handleSelect(filteredTypes[highlightedIndex]); } break bb121; } case "Escape": { e.preventDefault(); setIsOpen(false); } } }; $[15] = filteredTypes; $[16] = handleSelect; $[17] = highlightedIndex; $[18] = isOpen; $[19] = t8; } else { t8 = $[19]; } const handleKeyDown = t8; let t9; if ($[20] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = () => { setHighlightedIndex(0); }; $[20] = t9; } else { t9 = $[20]; } let t10; if ($[21] !== filteredTypes.length) { t10 = [filteredTypes.length]; $[21] = filteredTypes.length; $[22] = t10; } else { t10 = $[22]; } useEffect18(t9, t10); let t11; let t12; if ($[23] !== highlightedIndex || $[24] !== isOpen) { t11 = () => { if (isOpen && listRef.current) { const highlightedElement = listRef.current.children[highlightedIndex]; highlightedElement?.scrollIntoView({ block: "nearest" }); } }; t12 = [highlightedIndex, isOpen]; $[23] = highlightedIndex; $[24] = isOpen; $[25] = t11; $[26] = t12; } else { t11 = $[25]; t12 = $[26]; } useEffect18(t11, t12); let t13; let t14; if ($[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t13 = () => { const handleClickOutside = (e_0) => { if (inputRef.current && !inputRef.current.parentElement?.contains(e_0.target)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }; t14 = []; $[27] = t13; $[28] = t14; } else { t13 = $[27]; t14 = $[28]; } useEffect18(t13, t14); let t15; if ($[29] !== style) { t15 = { ...styles2.container, ...style }; $[29] = style; $[30] = t15; } else { t15 = $[30]; } let t16; let t17; if ($[31] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t16 = (v) => { setSearchQuery(v); setIsOpen(true); }; t17 = () => setIsOpen(true); $[31] = t16; $[32] = t17; } else { t16 = $[31]; t17 = $[32]; } let t18; if ($[33] !== disabled || $[34] !== handleKeyDown || $[35] !== isOpen || $[36] !== placeholder || $[37] !== searchQuery) { t18 = /* @__PURE__ */ _jsx28(ComboboxSearch, { inputRef, value: searchQuery, onChange: t16, onFocus: t17, onKeyDown: handleKeyDown, placeholder, disabled, isOpen }); $[33] = disabled; $[34] = handleKeyDown; $[35] = isOpen; $[36] = placeholder; $[37] = searchQuery; $[38] = t18; } else { t18 = $[38]; } let t19; if ($[39] !== filteredTypes || $[40] !== handleSelect || $[41] !== highlightedIndex || $[42] !== isOpen) { t19 = isOpen && filteredTypes.length > 0 && /* @__PURE__ */ _jsx28("ul", { ref: listRef, style: styles2.dropdown, role: "listbox", children: filteredTypes.map((option_0, index) => /* @__PURE__ */ _jsx28(ComboboxOption, { type: option_0.type, label: option_0.label || option_0.type, icon: option_0.icon, description: option_0.description, highlighted: index === highlightedIndex, onClick: () => handleSelect(option_0), onMouseEnter: () => setHighlightedIndex(index) }, option_0.type)) }); $[39] = filteredTypes; $[40] = handleSelect; $[41] = highlightedIndex; $[42] = isOpen; $[43] = t19; } else { t19 = $[43]; } let t20; if ($[44] !== filteredTypes.length || $[45] !== isOpen) { t20 = isOpen && filteredTypes.length === 0 && /* @__PURE__ */ _jsx28("div", { style: styles2.emptyState, children: "No node types found" }); $[44] = filteredTypes.length; $[45] = isOpen; $[46] = t20; } else { t20 = $[46]; } let t21; if ($[47] !== className || $[48] !== t15 || $[49] !== t18 || $[50] !== t19 || $[51] !== t20) { t21 = /* @__PURE__ */ _jsxs13("div", { className, style: t15, children: [t18, t19, t20] }); $[47] = className; $[48] = t15; $[49] = t18; $[50] = t19; $[51] = t20; $[52] = t21; } else { t21 = $[52]; } return t21; } function _temp25(prev) { return Math.max(prev - 1, 0); } function _temp12(type) { return { type, label: formatTypeLabel(type) }; } function formatTypeLabel(type) { return type.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); } var styles2 = { container: { position: "relative", width: "200px" }, input: { width: "100%", padding: "8px 12px", fontSize: "14px", border: "1px solid #ddd", borderRadius: "6px", outline: "none", backgroundColor: "#fff" }, dropdown: { position: "absolute", top: "100%", left: 0, right: 0, marginTop: "4px", padding: "4px 0", backgroundColor: "#fff", border: "1px solid #ddd", borderRadius: "6px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", maxHeight: "240px", overflowY: "auto", zIndex: 1e3, listStyle: "none" }, option: { display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", cursor: "pointer", transition: "background-color 0.1s" }, optionHighlighted: { backgroundColor: "#f0f0f0" }, icon: { fontSize: "16px", width: "20px", textAlign: "center" }, optionContent: { display: "flex", flexDirection: "column", gap: "2px" }, optionLabel: { fontSize: "14px", fontWeight: 500 }, optionDescription: { fontSize: "12px", color: "#666" }, emptyState: { position: "absolute", top: "100%", left: 0, right: 0, marginTop: "4px", padding: "12px", backgroundColor: "#fff", border: "1px solid #ddd", borderRadius: "6px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", color: "#666", fontSize: "14px", textAlign: "center" } }; // src/components/SettingsPanel.tsx import { c as _c60 } from "react/compiler-runtime"; import React31 from "react"; // src/components/SettingsPresets.tsx import { c as _c58 } from "react/compiler-runtime"; import React29, { useState as useState5 } from "react"; import { jsx as _jsx29, jsxs as _jsxs14, Fragment as _Fragment6 } from "react/jsx-runtime"; function SettingsPresets(t0) { const $ = _c58(36); const { className, selectClassName, buttonClassName, buttonsClassName, inputClassName } = t0; const { allPresets, activePresetId, hasUnsavedChanges, applyPreset, saveAsPreset, deletePreset, resetSettings } = useCanvasSettings(); const [showSaveDialog, setShowSaveDialog] = useState5(false); const [newPresetName, setNewPresetName] = useState5(""); let t1; if ($[0] !== newPresetName || $[1] !== saveAsPreset) { t1 = () => { if (newPresetName.trim()) { saveAsPreset(newPresetName.trim()); setNewPresetName(""); setShowSaveDialog(false); } }; $[0] = newPresetName; $[1] = saveAsPreset; $[2] = t1; } else { t1 = $[2]; } const handleSave = t1; let t2; if ($[3] !== activePresetId || $[4] !== allPresets || $[5] !== deletePreset) { t2 = () => { if (activePresetId && !allPresets.find((p) => p.id === activePresetId)?.isBuiltIn) { deletePreset(activePresetId); } }; $[3] = activePresetId; $[4] = allPresets; $[5] = deletePreset; $[6] = t2; } else { t2 = $[6]; } const handleDelete = t2; const t3 = activePresetId || ""; let t4; if ($[7] !== applyPreset) { t4 = (e) => e.target.value && applyPreset(e.target.value); $[7] = applyPreset; $[8] = t4; } else { t4 = $[8]; } const t5 = hasUnsavedChanges ? "Custom (modified)" : "Select preset..."; let t6; if ($[9] !== t5) { t6 = /* @__PURE__ */ _jsx29("option", { value: "", disabled: true, children: t5 }); $[9] = t5; $[10] = t6; } else { t6 = $[10]; } let t7; if ($[11] !== allPresets) { t7 = allPresets.map(_temp13); $[11] = allPresets; $[12] = t7; } else { t7 = $[12]; } let t8; if ($[13] !== selectClassName || $[14] !== t3 || $[15] !== t4 || $[16] !== t6 || $[17] !== t7) { t8 = /* @__PURE__ */ _jsxs14("select", { className: selectClassName, value: t3, onChange: t4, children: [t6, t7] }); $[13] = selectClassName; $[14] = t3; $[15] = t4; $[16] = t6; $[17] = t7; $[18] = t8; } else { t8 = $[18]; } let t9; if ($[19] !== activePresetId || $[20] !== allPresets || $[21] !== buttonClassName || $[22] !== handleDelete || $[23] !== handleSave || $[24] !== inputClassName || $[25] !== newPresetName || $[26] !== resetSettings || $[27] !== showSaveDialog) { t9 = showSaveDialog ? /* @__PURE__ */ _jsxs14(_Fragment6, { children: [/* @__PURE__ */ _jsx29("input", { type: "text", className: inputClassName, placeholder: "Preset name", value: newPresetName, onChange: (e_0) => setNewPresetName(e_0.target.value), onKeyDown: (e_1) => e_1.key === "Enter" && handleSave(), autoFocus: true }), /* @__PURE__ */ _jsx29("button", { className: buttonClassName, onClick: handleSave, children: "Save" }), /* @__PURE__ */ _jsx29("button", { className: buttonClassName, onClick: () => setShowSaveDialog(false), children: "Cancel" })] }) : /* @__PURE__ */ _jsxs14(_Fragment6, { children: [/* @__PURE__ */ _jsx29("button", { className: buttonClassName, onClick: () => setShowSaveDialog(true), children: "Save as..." }), activePresetId && !allPresets.find((p_0) => p_0.id === activePresetId)?.isBuiltIn && /* @__PURE__ */ _jsx29("button", { className: buttonClassName, onClick: handleDelete, children: "Delete" }), /* @__PURE__ */ _jsx29("button", { className: buttonClassName, onClick: resetSettings, children: "Reset" })] }); $[19] = activePresetId; $[20] = allPresets; $[21] = buttonClassName; $[22] = handleDelete; $[23] = handleSave; $[24] = inputClassName; $[25] = newPresetName; $[26] = resetSettings; $[27] = showSaveDialog; $[28] = t9; } else { t9 = $[28]; } let t10; if ($[29] !== buttonsClassName || $[30] !== t9) { t10 = /* @__PURE__ */ _jsx29("div", { className: buttonsClassName, children: t9 }); $[29] = buttonsClassName; $[30] = t9; $[31] = t10; } else { t10 = $[31]; } let t11; if ($[32] !== className || $[33] !== t10 || $[34] !== t8) { t11 = /* @__PURE__ */ _jsxs14("div", { className, children: [t8, t10] }); $[32] = className; $[33] = t10; $[34] = t8; $[35] = t11; } else { t11 = $[35]; } return t11; } function _temp13(preset) { return /* @__PURE__ */ _jsxs14("option", { value: preset.id, children: [preset.name, preset.isBuiltIn ? "" : " (custom)"] }, preset.id); } // src/components/SettingsEventMap.tsx init_settings_types(); init_action_registry(); import { c as _c59 } from "react/compiler-runtime"; import React30 from "react"; import { jsx as _jsx30, jsxs as _jsxs15 } from "react/jsx-runtime"; function EventMappingRow(t0) { const $ = _c59(28); const { event, currentActionId, onActionChange, rowClassName, labelClassName, selectClassName } = t0; let t1; let t2; let t3; let t4; let t5; let t6; if ($[0] !== currentActionId || $[1] !== event.label || $[2] !== event.type || $[3] !== labelClassName || $[4] !== onActionChange || $[5] !== rowClassName || $[6] !== selectClassName) { const categories = getActionsByCategories(); t5 = rowClassName; if ($[13] !== event.label || $[14] !== labelClassName) { t6 = /* @__PURE__ */ _jsx30("span", { className: labelClassName, children: event.label }); $[13] = event.label; $[14] = labelClassName; $[15] = t6; } else { t6 = $[15]; } t1 = selectClassName; t2 = currentActionId; if ($[16] !== event.type || $[17] !== onActionChange) { t3 = (e) => onActionChange(event.type, e.target.value); $[16] = event.type; $[17] = onActionChange; $[18] = t3; } else { t3 = $[18]; } t4 = categories.map(_temp26); $[0] = currentActionId; $[1] = event.label; $[2] = event.type; $[3] = labelClassName; $[4] = onActionChange; $[5] = rowClassName; $[6] = selectClassName; $[7] = t1; $[8] = t2; $[9] = t3; $[10] = t4; $[11] = t5; $[12] = t6; } else { t1 = $[7]; t2 = $[8]; t3 = $[9]; t4 = $[10]; t5 = $[11]; t6 = $[12]; } let t7; if ($[19] !== t1 || $[20] !== t2 || $[21] !== t3 || $[22] !== t4) { t7 = /* @__PURE__ */ _jsx30("select", { className: t1, value: t2, onChange: t3, children: t4 }); $[19] = t1; $[20] = t2; $[21] = t3; $[22] = t4; $[23] = t7; } else { t7 = $[23]; } let t8; if ($[24] !== t5 || $[25] !== t6 || $[26] !== t7) { t8 = /* @__PURE__ */ _jsxs15("div", { className: t5, children: [t6, t7] }); $[24] = t5; $[25] = t6; $[26] = t7; $[27] = t8; } else { t8 = $[27]; } return t8; } function _temp26(category) { return /* @__PURE__ */ _jsx30("optgroup", { label: category.label, children: category.actions.map(_temp14) }, category.category); } function _temp14(action) { return /* @__PURE__ */ _jsx30("option", { value: action.id, children: action.label }, action.id); } function SettingsEventMap(t0) { const $ = _c59(17); const { mappings, onActionChange, contentClassName, groupClassName, groupHeadingClassName, rowClassName, labelClassName, selectClassName } = t0; let t1; if ($[0] !== contentClassName || $[1] !== groupClassName || $[2] !== groupHeadingClassName || $[3] !== labelClassName || $[4] !== mappings || $[5] !== onActionChange || $[6] !== rowClassName || $[7] !== selectClassName) { const nodeEvents = Object.values(CanvasEventType).filter(_temp33); const edgeEvents = Object.values(CanvasEventType).filter(_temp42); const backgroundEvents = Object.values(CanvasEventType).filter(_temp52); let t2; if ($[9] !== groupClassName || $[10] !== groupHeadingClassName || $[11] !== labelClassName || $[12] !== mappings || $[13] !== onActionChange || $[14] !== rowClassName || $[15] !== selectClassName) { t2 = (heading, events) => /* @__PURE__ */ _jsxs15("div", { className: groupClassName, children: [/* @__PURE__ */ _jsx30("h3", { className: groupHeadingClassName, children: heading }), events.map((event_2) => /* @__PURE__ */ _jsx30(EventMappingRow, { event: EVENT_TYPE_INFO[event_2], currentActionId: mappings[event_2], onActionChange, rowClassName, labelClassName, selectClassName }, event_2))] }); $[9] = groupClassName; $[10] = groupHeadingClassName; $[11] = labelClassName; $[12] = mappings; $[13] = onActionChange; $[14] = rowClassName; $[15] = selectClassName; $[16] = t2; } else { t2 = $[16]; } const renderGroup = t2; t1 = /* @__PURE__ */ _jsxs15("div", { className: contentClassName, children: [renderGroup("Node Events", nodeEvents), renderGroup("Edge Events", edgeEvents), renderGroup("Background Events", backgroundEvents)] }); $[0] = contentClassName; $[1] = groupClassName; $[2] = groupHeadingClassName; $[3] = labelClassName; $[4] = mappings; $[5] = onActionChange; $[6] = rowClassName; $[7] = selectClassName; $[8] = t1; } else { t1 = $[8]; } return t1; } function _temp52(event_1) { return EVENT_TYPE_INFO[event_1].category === "background"; } function _temp42(event_0) { return EVENT_TYPE_INFO[event_0].category === "edge"; } function _temp33(event) { return EVENT_TYPE_INFO[event].category === "node"; } // src/components/SettingsPanel.tsx import { jsx as _jsx31, jsxs as _jsxs16 } from "react/jsx-runtime"; function QuickActions(t0) { const $ = _c60(17); const { className, checkboxClassName, virtualizationClassName, virtualizationStatsClassName } = t0; const { enabled, toggle, visibleNodes, totalNodes, culledNodes } = useVirtualization(); let t1; if ($[0] !== checkboxClassName || $[1] !== enabled || $[2] !== toggle) { t1 = /* @__PURE__ */ _jsx31("input", { type: "checkbox", className: checkboxClassName, checked: enabled, onChange: toggle }); $[0] = checkboxClassName; $[1] = enabled; $[2] = toggle; $[3] = t1; } else { t1 = $[3]; } let t2; if ($[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = /* @__PURE__ */ _jsx31("span", { children: "Virtualization" }); $[4] = t2; } else { t2 = $[4]; } let t3; if ($[5] !== t1) { t3 = /* @__PURE__ */ _jsxs16("label", { children: [t1, t2] }); $[5] = t1; $[6] = t3; } else { t3 = $[6]; } const t4 = enabled ? `${visibleNodes}/${totalNodes} (${culledNodes} culled)` : "Off"; let t5; if ($[7] !== t4 || $[8] !== virtualizationStatsClassName) { t5 = /* @__PURE__ */ _jsx31("span", { className: virtualizationStatsClassName, children: t4 }); $[7] = t4; $[8] = virtualizationStatsClassName; $[9] = t5; } else { t5 = $[9]; } let t6; if ($[10] !== t3 || $[11] !== t5 || $[12] !== virtualizationClassName) { t6 = /* @__PURE__ */ _jsxs16("div", { className: virtualizationClassName, children: [t3, t5] }); $[10] = t3; $[11] = t5; $[12] = virtualizationClassName; $[13] = t6; } else { t6 = $[13]; } let t7; if ($[14] !== className || $[15] !== t6) { t7 = /* @__PURE__ */ _jsx31("div", { className, children: t6 }); $[14] = className; $[15] = t6; $[16] = t7; } else { t7 = $[16]; } return t7; } function SettingsPanel(t0) { const $ = _c60(35); const { className, headerClassName, contentClassName, footerClassName, rowClassName, labelClassName, selectClassName, buttonClassName, presetClassName, presetSelectClassName, presetButtonsClassName, checkboxClassName, groupClassName, groupHeadingClassName, quickActionsClassName, virtualizationClassName, virtualizationStatsClassName, inputClassName, renderHeader, renderFooter, onClose } = t0; const { mappings, setEventMapping } = useCanvasSettings(); let t1; if ($[0] !== buttonClassName || $[1] !== headerClassName || $[2] !== onClose || $[3] !== renderHeader) { t1 = renderHeader ? renderHeader() : /* @__PURE__ */ _jsxs16("div", { className: headerClassName, children: [/* @__PURE__ */ _jsx31("h2", { children: "Canvas Settings" }), onClose && /* @__PURE__ */ _jsx31("button", { className: buttonClassName, onClick: onClose, children: "\xD7" })] }); $[0] = buttonClassName; $[1] = headerClassName; $[2] = onClose; $[3] = renderHeader; $[4] = t1; } else { t1 = $[4]; } const t2 = presetSelectClassName || selectClassName; let t3; if ($[5] !== buttonClassName || $[6] !== inputClassName || $[7] !== presetButtonsClassName || $[8] !== presetClassName || $[9] !== t2) { t3 = /* @__PURE__ */ _jsx31(SettingsPresets, { className: presetClassName, selectClassName: t2, buttonClassName, buttonsClassName: presetButtonsClassName, inputClassName }); $[5] = buttonClassName; $[6] = inputClassName; $[7] = presetButtonsClassName; $[8] = presetClassName; $[9] = t2; $[10] = t3; } else { t3 = $[10]; } let t4; if ($[11] !== checkboxClassName || $[12] !== quickActionsClassName || $[13] !== virtualizationClassName || $[14] !== virtualizationStatsClassName) { t4 = /* @__PURE__ */ _jsx31(QuickActions, { className: quickActionsClassName, checkboxClassName, virtualizationClassName, virtualizationStatsClassName }); $[11] = checkboxClassName; $[12] = quickActionsClassName; $[13] = virtualizationClassName; $[14] = virtualizationStatsClassName; $[15] = t4; } else { t4 = $[15]; } let t5; if ($[16] !== contentClassName || $[17] !== groupClassName || $[18] !== groupHeadingClassName || $[19] !== labelClassName || $[20] !== mappings || $[21] !== rowClassName || $[22] !== selectClassName || $[23] !== setEventMapping) { t5 = /* @__PURE__ */ _jsx31(SettingsEventMap, { mappings, onActionChange: setEventMapping, contentClassName, groupClassName, groupHeadingClassName, rowClassName, labelClassName, selectClassName }); $[16] = contentClassName; $[17] = groupClassName; $[18] = groupHeadingClassName; $[19] = labelClassName; $[20] = mappings; $[21] = rowClassName; $[22] = selectClassName; $[23] = setEventMapping; $[24] = t5; } else { t5 = $[24]; } let t6; if ($[25] !== footerClassName || $[26] !== renderFooter) { t6 = renderFooter ? renderFooter() : /* @__PURE__ */ _jsx31("div", { className: footerClassName, children: "Changes auto-saved to localStorage." }); $[25] = footerClassName; $[26] = renderFooter; $[27] = t6; } else { t6 = $[27]; } let t7; if ($[28] !== className || $[29] !== t1 || $[30] !== t3 || $[31] !== t4 || $[32] !== t5 || $[33] !== t6) { t7 = /* @__PURE__ */ _jsxs16("div", { className, children: [t1, t3, t4, t5, t6] }); $[28] = className; $[29] = t1; $[30] = t3; $[31] = t4; $[32] = t5; $[33] = t6; $[34] = t7; } else { t7 = $[34]; } return t7; } // src/components/CommandLine/CommandLine.tsx import { c as _c65 } from "react/compiler-runtime"; import React36, { useEffect as useEffect21, useRef as useRef28, useState as useState8 } from "react"; import { useAtom as useAtom7, useAtomValue as useAtomValue42, useSetAtom as useSetAtom31 } from "jotai"; init_registry(); // src/components/CommandLine/CommandSearch.tsx import { c as _c61 } from "react/compiler-runtime"; import React32, { useRef as useRef26, useEffect as useEffect19, useState as useState6 } from "react"; import { useAtom as useAtom5, useSetAtom as useSetAtom29, useAtomValue as useAtomValue40 } from "jotai"; init_registry(); import { jsx as _jsx32, jsxs as _jsxs17 } from "react/jsx-runtime"; function CommandSearch() { const $ = _c61(24); const [state] = useAtom5(commandLineStateAtom); const updateSearchQuery = useSetAtom29(updateSearchQueryAtom); const selectCommand = useSetAtom29(selectCommandAtom); useAtomValue40(commandHistoryAtom); const [selectedIndex, setSelectedIndex] = useAtom5(selectedSuggestionIndexAtom); const inputRef = useRef26(null); const [inputValue, setInputValue] = useState6(""); let t0; let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = () => { inputRef.current?.focus(); }; t1 = []; $[0] = t0; $[1] = t1; } else { t0 = $[0]; t1 = $[1]; } useEffect19(t0, t1); let t2; if ($[2] !== state.phase || $[3] !== state.suggestions) { t2 = state.phase === "searching" ? state.suggestions : commandRegistry.all(); $[2] = state.phase; $[3] = state.suggestions; $[4] = t2; } else { t2 = $[4]; } const suggestions = t2; let t3; if ($[5] !== updateSearchQuery) { t3 = (e) => { const value = e.target.value; setInputValue(value); updateSearchQuery(value); }; $[5] = updateSearchQuery; $[6] = t3; } else { t3 = $[6]; } const handleChange = t3; let t4; if ($[7] !== selectCommand || $[8] !== selectedIndex || $[9] !== setSelectedIndex || $[10] !== suggestions || $[11] !== updateSearchQuery) { t4 = (e_0) => { bb15: switch (e_0.key) { case "ArrowDown": { e_0.preventDefault(); setSelectedIndex((prev_0) => Math.min(prev_0 + 1, suggestions.length - 1)); break bb15; } case "ArrowUp": { e_0.preventDefault(); setSelectedIndex(_temp15); break bb15; } case "Enter": { e_0.preventDefault(); if (suggestions[selectedIndex]) { selectCommand(suggestions[selectedIndex]); } break bb15; } case "Tab": { e_0.preventDefault(); if (suggestions[selectedIndex]) { setInputValue(suggestions[selectedIndex].name); updateSearchQuery(suggestions[selectedIndex].name); } } } }; $[7] = selectCommand; $[8] = selectedIndex; $[9] = setSelectedIndex; $[10] = suggestions; $[11] = updateSearchQuery; $[12] = t4; } else { t4 = $[12]; } const handleKeyDown = t4; let t5; if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t5 = { color: "var(--cmd-input-text)" }; $[13] = t5; } else { t5 = $[13]; } let t6; if ($[14] !== handleChange || $[15] !== handleKeyDown || $[16] !== inputValue) { t6 = /* @__PURE__ */ _jsx32("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: "Type command name...", className: "flex-1 bg-transparent outline-none", style: t5, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: "false" }); $[14] = handleChange; $[15] = handleKeyDown; $[16] = inputValue; $[17] = t6; } else { t6 = $[17]; } let t7; if ($[18] !== inputValue || $[19] !== suggestions) { t7 = inputValue && /* @__PURE__ */ _jsxs17("span", { className: "ml-2 text-xs", style: { color: "var(--hud-text-secondary)", opacity: 0.6 }, children: [suggestions.length, " matches"] }); $[18] = inputValue; $[19] = suggestions; $[20] = t7; } else { t7 = $[20]; } let t8; if ($[21] !== t6 || $[22] !== t7) { t8 = /* @__PURE__ */ _jsxs17("div", { className: "flex flex-1 items-center", children: [t6, t7] }); $[21] = t6; $[22] = t7; $[23] = t8; } else { t8 = $[23]; } return t8; } function _temp15(prev) { return Math.max(prev - 1, 0); } // src/components/CommandLine/CommandInputCollector.tsx import { c as _c64 } from "react/compiler-runtime"; import React35, { useRef as useRef27, useEffect as useEffect20, useState as useState7 } from "react"; import { useAtom as useAtom6, useSetAtom as useSetAtom30, useAtomValue as useAtomValue41 } from "jotai"; init_viewport_store(); // src/components/CommandLine/CollectorInputPhase.tsx import { c as _c62 } from "react/compiler-runtime"; import React33 from "react"; import { jsx as _jsx33, jsxs as _jsxs18, Fragment as _Fragment7 } from "react/jsx-runtime"; function CollectorInputPhase(t0) { const $ = _c62(15); const { command, currentInput, progress, inputModeType } = t0; const isPickMode = inputModeType === "pickPoint" || inputModeType === "pickNode" || inputModeType === "pickNodes"; let t1; if ($[0] !== command || $[1] !== progress) { t1 = progress && /* @__PURE__ */ _jsxs18("div", { className: "flex items-center gap-2 text-xs", style: { color: "var(--hud-text-secondary)" }, children: [/* @__PURE__ */ _jsx33("span", { className: "font-medium", style: { color: "var(--hud-text)" }, children: command.name }), /* @__PURE__ */ _jsx33("span", { children: "\u2022" }), /* @__PURE__ */ _jsxs18("span", { children: ["Step ", progress.current, "/", progress.total] })] }); $[0] = command; $[1] = progress; $[2] = t1; } else { t1 = $[2]; } let t2; if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = { color: "var(--hud-text-secondary)" }; $[3] = t2; } else { t2 = $[3]; } let t3; if ($[4] !== currentInput.prompt) { t3 = /* @__PURE__ */ _jsxs18("span", { style: t2, children: [currentInput.prompt, ":"] }); $[4] = currentInput.prompt; $[5] = t3; } else { t3 = $[5]; } let t4; if ($[6] !== inputModeType || $[7] !== isPickMode) { t4 = isPickMode && /* @__PURE__ */ _jsxs18("span", { className: "animate-pulse text-sm", style: { color: "var(--badge-info-text)" }, children: [inputModeType === "pickPoint" && "Click on canvas or enter x,y", inputModeType === "pickNode" && "Click on a node", inputModeType === "pickNodes" && "Click nodes (Enter when done)"] }); $[6] = inputModeType; $[7] = isPickMode; $[8] = t4; } else { t4 = $[8]; } let t5; if ($[9] !== t3 || $[10] !== t4) { t5 = /* @__PURE__ */ _jsxs18("div", { className: "flex items-center gap-2", children: [t3, t4] }); $[9] = t3; $[10] = t4; $[11] = t5; } else { t5 = $[11]; } let t6; if ($[12] !== t1 || $[13] !== t5) { t6 = /* @__PURE__ */ _jsxs18(_Fragment7, { children: [t1, t5] }); $[12] = t1; $[13] = t5; $[14] = t6; } else { t6 = $[14]; } return t6; } // src/components/CommandLine/CollectorSelectInput.tsx import { c as _c63 } from "react/compiler-runtime"; import React34 from "react"; import { jsx as _jsx34, jsxs as _jsxs19 } from "react/jsx-runtime"; function ShortcutButton(t0) { const $ = _c63(13); const { shortcut, label, description, onClick } = t0; let t1; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { backgroundColor: "var(--list-item-bg-hover)", color: "var(--hud-text)" }; $[0] = t1; } else { t1 = $[0]; } let t2; if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = { backgroundColor: "var(--cmd-bg)", color: "var(--badge-info-text)" }; $[1] = t2; } else { t2 = $[1]; } let t3; if ($[2] !== shortcut) { t3 = shortcut.toUpperCase(); $[2] = shortcut; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== t3) { t4 = /* @__PURE__ */ _jsx34("kbd", { className: "rounded px-1 py-0.5 font-mono text-xs", style: t2, children: t3 }); $[4] = t3; $[5] = t4; } else { t4 = $[5]; } let t5; if ($[6] !== label) { t5 = /* @__PURE__ */ _jsx34("span", { children: label }); $[6] = label; $[7] = t5; } else { t5 = $[7]; } let t6; if ($[8] !== description || $[9] !== onClick || $[10] !== t4 || $[11] !== t5) { t6 = /* @__PURE__ */ _jsxs19("button", { onClick, className: "flex items-center gap-1 rounded px-2 py-0.5 text-sm transition-colors", style: t1, onMouseEnter: _temp16, onMouseLeave: _temp27, title: description, children: [t4, t5] }); $[8] = description; $[9] = onClick; $[10] = t4; $[11] = t5; $[12] = t6; } else { t6 = $[12]; } return t6; } function _temp27(e_0) { return e_0.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)"; } function _temp16(e) { return e.currentTarget.style.backgroundColor = "var(--list-item-border)"; } function CollectorSelectInput(t0) { const $ = _c63(7); const { options, onSelect } = t0; let t1; if ($[0] !== onSelect || $[1] !== options) { let t22; if ($[3] !== onSelect) { t22 = (option) => /* @__PURE__ */ _jsx34(ShortcutButton, { shortcut: option.shortcut, label: option.label, description: option.description, onClick: () => onSelect(option.value) }, option.value); $[3] = onSelect; $[4] = t22; } else { t22 = $[4]; } t1 = options.map(t22); $[0] = onSelect; $[1] = options; $[2] = t1; } else { t1 = $[2]; } let t2; if ($[5] !== t1) { t2 = /* @__PURE__ */ _jsx34("div", { className: "flex items-center gap-2", children: t1 }); $[5] = t1; $[6] = t2; } else { t2 = $[6]; } return t2; } function CollectorBooleanInput(t0) { const $ = _c63(7); const { onSelect } = t0; let t1; if ($[0] !== onSelect) { t1 = /* @__PURE__ */ _jsx34(ShortcutButton, { shortcut: "Y", label: "Yes", onClick: () => onSelect(true) }); $[0] = onSelect; $[1] = t1; } else { t1 = $[1]; } let t2; if ($[2] !== onSelect) { t2 = /* @__PURE__ */ _jsx34(ShortcutButton, { shortcut: "N", label: "No", onClick: () => onSelect(false) }); $[2] = onSelect; $[3] = t2; } else { t2 = $[3]; } let t3; if ($[4] !== t1 || $[5] !== t2) { t3 = /* @__PURE__ */ _jsxs19("div", { className: "flex items-center gap-2", children: [t1, t2] }); $[4] = t1; $[5] = t2; $[6] = t3; } else { t3 = $[6]; } return t3; } // src/components/CommandLine/CommandInputCollector.tsx import { jsx as _jsx35, jsxs as _jsxs20 } from "react/jsx-runtime"; function CommandInputCollector() { const $ = _c64(71); const [state] = useAtom6(commandLineStateAtom); const currentInput = useAtomValue41(currentInputAtom); const progress = useAtomValue41(commandProgressAtom); const inputMode = useAtomValue41(inputModeAtom2); const provideInput = useSetAtom30(provideInputAtom2); const skipInput = useSetAtom30(skipInputAtom); const goBack = useSetAtom30(goBackInputAtom); const inputRef = useRef27(null); const [textValue, setTextValue] = useState7(""); const zoom = useAtomValue41(zoomAtom); const pan = useAtomValue41(panAtom); const viewportRect = useAtomValue41(viewportRectAtom); let t0; let t1; if ($[0] !== currentInput) { t0 = () => { if (currentInput && (currentInput.type === "text" || currentInput.type === "number" || currentInput.type === "point")) { inputRef.current?.focus(); } }; t1 = [currentInput]; $[0] = currentInput; $[1] = t0; $[2] = t1; } else { t0 = $[1]; t1 = $[2]; } useEffect20(t0, t1); let t2; if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t2 = () => { setTextValue(""); }; $[3] = t2; } else { t2 = $[3]; } const t3 = currentInput?.name; let t4; if ($[4] !== t3) { t4 = [t3]; $[4] = t3; $[5] = t4; } else { t4 = $[5]; } useEffect20(t2, t4); let t5; if ($[6] !== pan.x || $[7] !== pan.y || $[8] !== viewportRect || $[9] !== zoom) { t5 = () => { const screenCenterX = viewportRect ? viewportRect.width / 2 : window.innerWidth / 2; const screenCenterY = viewportRect ? viewportRect.height / 2 : window.innerHeight / 2; return { x: Math.round((screenCenterX - pan.x) / zoom), y: Math.round((screenCenterY - pan.y) / zoom) }; }; $[6] = pan.x; $[7] = pan.y; $[8] = viewportRect; $[9] = zoom; $[10] = t5; } else { t5 = $[10]; } const getWorldCenter = t5; let t6; let t7; if ($[11] !== currentInput || $[12] !== getWorldCenter || $[13] !== provideInput) { t6 = () => { if (!currentInput) { return; } const handleGlobalKeyDown = (e) => { const target = e.target; const isInputField = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable; if (e.key === "Enter" && !isInputField) { if (currentInput.type === "point") { e.preventDefault(); provideInput(getWorldCenter()); return; } if (currentInput.default !== void 0) { e.preventDefault(); provideInput(currentInput.default); return; } } if (currentInput.type === "select" && currentInput.options) { const option = currentInput.options.find((opt) => opt.shortcut.toLowerCase() === e.key.toLowerCase()); if (option) { e.preventDefault(); provideInput(option.value); } } if (currentInput.type === "boolean") { if (e.key.toLowerCase() === "y") { e.preventDefault(); provideInput(true); } if (e.key.toLowerCase() === "n") { e.preventDefault(); provideInput(false); } } }; window.addEventListener("keydown", handleGlobalKeyDown); return () => window.removeEventListener("keydown", handleGlobalKeyDown); }; t7 = [currentInput, provideInput, getWorldCenter]; $[11] = currentInput; $[12] = getWorldCenter; $[13] = provideInput; $[14] = t6; $[15] = t7; } else { t6 = $[14]; t7 = $[15]; } useEffect20(t6, t7); let t8; if ($[16] !== currentInput || $[17] !== provideInput || $[18] !== textValue) { t8 = (e_0) => { e_0.preventDefault(); if (!currentInput) { return; } if (textValue === "" && currentInput.default !== void 0) { provideInput(currentInput.default); setTextValue(""); return; } if (currentInput.type === "number") { const num = parseFloat(textValue); if (!isNaN(num)) { provideInput(num); } } else { provideInput(textValue); } setTextValue(""); }; $[16] = currentInput; $[17] = provideInput; $[18] = textValue; $[19] = t8; } else { t8 = $[19]; } const handleSubmit = t8; let t9; if ($[20] !== currentInput || $[21] !== goBack || $[22] !== progress || $[23] !== provideInput || $[24] !== skipInput || $[25] !== textValue) { t9 = (e_1) => { if (e_1.key === "Escape") { e_1.preventDefault(); if (currentInput?.required === false) { skipInput(); } else { if (progress && progress.current > 1) { goBack(); } } return; } if (e_1.key === "Backspace" && textValue === "" && progress && progress.current > 1) { e_1.preventDefault(); goBack(); return; } if (currentInput?.type === "select" && currentInput.options) { const option_0 = currentInput.options.find((opt_0) => opt_0.shortcut.toLowerCase() === e_1.key.toLowerCase()); if (option_0) { e_1.preventDefault(); provideInput(option_0.value); } } }; $[20] = currentInput; $[21] = goBack; $[22] = progress; $[23] = provideInput; $[24] = skipInput; $[25] = textValue; $[26] = t9; } else { t9 = $[26]; } const handleKeyDown = t9; if (state.phase !== "collecting" || !currentInput) { return null; } const isSelectMode = currentInput.type === "select"; let t10; if ($[27] !== currentInput || $[28] !== inputMode.type || $[29] !== progress || $[30] !== state.command) { t10 = /* @__PURE__ */ _jsx35(CollectorInputPhase, { command: state.command, currentInput, progress, inputModeType: inputMode.type }); $[27] = currentInput; $[28] = inputMode.type; $[29] = progress; $[30] = state.command; $[31] = t10; } else { t10 = $[31]; } let t11; if ($[32] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t11 = { color: "var(--hud-text-secondary)" }; $[32] = t11; } else { t11 = $[32]; } let t12; if ($[33] !== currentInput.prompt) { t12 = /* @__PURE__ */ _jsxs20("span", { style: t11, children: [currentInput.prompt, ":"] }); $[33] = currentInput.prompt; $[34] = t12; } else { t12 = $[34]; } let t13; if ($[35] !== currentInput.options || $[36] !== isSelectMode || $[37] !== provideInput) { t13 = isSelectMode && currentInput.options && /* @__PURE__ */ _jsx35(CollectorSelectInput, { options: currentInput.options, onSelect: (v) => provideInput(v) }); $[35] = currentInput.options; $[36] = isSelectMode; $[37] = provideInput; $[38] = t13; } else { t13 = $[38]; } let t14; if ($[39] !== currentInput.default || $[40] !== currentInput.type || $[41] !== handleKeyDown || $[42] !== handleSubmit || $[43] !== textValue) { t14 = (currentInput.type === "text" || currentInput.type === "number") && /* @__PURE__ */ _jsx35("form", { onSubmit: handleSubmit, className: "flex flex-1 items-center", children: /* @__PURE__ */ _jsx35("input", { ref: inputRef, type: currentInput.type === "number" ? "number" : "text", value: textValue, onChange: (e_2) => setTextValue(e_2.target.value), onKeyDown: handleKeyDown, placeholder: currentInput.default !== void 0 ? `Default: ${currentInput.default}` : void 0, className: "flex-1 bg-transparent outline-none", style: { color: "var(--cmd-input-text)" }, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: "false" }) }); $[39] = currentInput.default; $[40] = currentInput.type; $[41] = handleKeyDown; $[42] = handleSubmit; $[43] = textValue; $[44] = t14; } else { t14 = $[44]; } let t15; if ($[45] !== currentInput.type || $[46] !== getWorldCenter || $[47] !== handleKeyDown || $[48] !== provideInput || $[49] !== textValue) { t15 = currentInput.type === "point" && /* @__PURE__ */ _jsx35("form", { onSubmit: (e_3) => { e_3.preventDefault(); if (textValue.trim() === "") { provideInput(getWorldCenter()); setTextValue(""); return; } const match = textValue.match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/); if (match) { provideInput({ x: parseFloat(match[1]), y: parseFloat(match[2]) }); setTextValue(""); } }, className: "flex flex-1 items-center", children: /* @__PURE__ */ _jsx35("input", { ref: inputRef, type: "text", value: textValue, onChange: (e_4) => setTextValue(e_4.target.value), onKeyDown: handleKeyDown, placeholder: "x, y or click on canvas (Enter for center)", className: "flex-1 bg-transparent outline-none", style: { color: "var(--cmd-input-text)" }, autoComplete: "off" }) }); $[45] = currentInput.type; $[46] = getWorldCenter; $[47] = handleKeyDown; $[48] = provideInput; $[49] = textValue; $[50] = t15; } else { t15 = $[50]; } let t16; if ($[51] !== currentInput.type || $[52] !== provideInput) { t16 = currentInput.type === "boolean" && /* @__PURE__ */ _jsx35(CollectorBooleanInput, { onSelect: (v_0) => provideInput(v_0) }); $[51] = currentInput.type; $[52] = provideInput; $[53] = t16; } else { t16 = $[53]; } let t17; if ($[54] !== currentInput.required || $[55] !== skipInput) { t17 = currentInput.required === false && /* @__PURE__ */ _jsx35("button", { onClick: () => skipInput(), className: "rounded px-2 py-0.5 text-xs transition-colors", style: { color: "var(--hud-text-secondary)", opacity: 0.8 }, onMouseEnter: _temp17, onMouseLeave: _temp28, children: "Skip (Esc)" }); $[54] = currentInput.required; $[55] = skipInput; $[56] = t17; } else { t17 = $[56]; } let t18; if ($[57] !== goBack || $[58] !== progress) { t18 = progress && progress.current > 1 && /* @__PURE__ */ _jsx35("button", { onClick: () => goBack(), className: "rounded px-2 py-0.5 text-xs transition-colors", style: { color: "var(--hud-text-secondary)", opacity: 0.8 }, onMouseEnter: _temp34, onMouseLeave: _temp43, children: "Back" }); $[57] = goBack; $[58] = progress; $[59] = t18; } else { t18 = $[59]; } let t19; if ($[60] !== t12 || $[61] !== t13 || $[62] !== t14 || $[63] !== t15 || $[64] !== t16 || $[65] !== t17 || $[66] !== t18) { t19 = /* @__PURE__ */ _jsxs20("div", { className: "flex items-center gap-2", children: [t12, t13, t14, t15, t16, t17, t18] }); $[60] = t12; $[61] = t13; $[62] = t14; $[63] = t15; $[64] = t16; $[65] = t17; $[66] = t18; $[67] = t19; } else { t19 = $[67]; } let t20; if ($[68] !== t10 || $[69] !== t19) { t20 = /* @__PURE__ */ _jsxs20("div", { className: "flex flex-1 flex-col gap-1", children: [t10, t19] }); $[68] = t10; $[69] = t19; $[70] = t20; } else { t20 = $[70]; } return t20; } function _temp43(e_8) { e_8.currentTarget.style.backgroundColor = "transparent"; e_8.currentTarget.style.color = "var(--hud-text-secondary)"; } function _temp34(e_7) { e_7.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)"; e_7.currentTarget.style.color = "var(--hud-text)"; } function _temp28(e_6) { e_6.currentTarget.style.backgroundColor = "transparent"; e_6.currentTarget.style.color = "var(--hud-text-secondary)"; } function _temp17(e_5) { e_5.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)"; e_5.currentTarget.style.color = "var(--hud-text)"; } // src/components/CommandLine/CommandLine.tsx import { jsx as _jsx36, jsxs as _jsxs21 } from "react/jsx-runtime"; var MIN_HEIGHT = 60; var MAX_HEIGHT = 400; var DEFAULT_HEIGHT = 200; function CommandLine(t0) { const $ = _c65(58); const { isModalOpen } = t0; const visible = useAtomValue42(commandLineVisibleAtom); const [state, setState] = useAtom7(commandLineStateAtom); useSetAtom31(closeCommandLineAtom); const progress = useAtomValue42(commandProgressAtom); const selectedIndex = useAtomValue42(selectedSuggestionIndexAtom); const setInputMode = useSetAtom31(inputModeAtom2); const containerRef = useRef28(null); const [height, setHeight] = useState8(DEFAULT_HEIGHT); const [isDragging, setIsDragging] = useState8(false); const dragStartY = useRef28(0); const dragStartHeight = useRef28(DEFAULT_HEIGHT); let t1; let t2; if ($[0] !== isModalOpen || $[1] !== setInputMode || $[2] !== setState || $[3] !== state.phase || $[4] !== visible) { t1 = () => { if (!visible) { return; } const handleKeyDown = (e) => { if (e.key === "Escape") { if (isModalOpen?.()) { return; } e.preventDefault(); e.stopPropagation(); if (state.phase === "collecting" || state.phase === "executing" || state.phase === "error") { setState({ phase: "searching", query: "", suggestions: commandRegistry.all() }); setInputMode({ type: "normal" }); } } }; window.addEventListener("keydown", handleKeyDown, { capture: true }); return () => window.removeEventListener("keydown", handleKeyDown, { capture: true }); }; t2 = [visible, state.phase, setState, setInputMode, isModalOpen]; $[0] = isModalOpen; $[1] = setInputMode; $[2] = setState; $[3] = state.phase; $[4] = visible; $[5] = t1; $[6] = t2; } else { t1 = $[5]; t2 = $[6]; } useEffect21(t1, t2); let t3; if ($[7] !== height) { t3 = (e_0) => { e_0.preventDefault(); setIsDragging(true); dragStartY.current = e_0.clientY; dragStartHeight.current = height; }; $[7] = height; $[8] = t3; } else { t3 = $[8]; } const handleDragStart = t3; let t4; let t5; if ($[9] !== isDragging) { t4 = () => { if (!isDragging) { return; } const handleMouseMove = (e_1) => { const deltaY = dragStartY.current - e_1.clientY; const newHeight = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, dragStartHeight.current + deltaY)); setHeight(newHeight); }; const handleMouseUp = () => { setIsDragging(false); }; window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }; t5 = [isDragging]; $[9] = isDragging; $[10] = t4; $[11] = t5; } else { t4 = $[10]; t5 = $[11]; } useEffect21(t4, t5); if (!visible) { return null; } typeof window !== "undefined" ? Math.min(400, window.innerHeight * 0.5) : 300; const t6 = `${height}px`; let t7; if ($[12] !== t6) { t7 = { height: t6, maxHeight: "50vh", backgroundColor: "var(--cmd-bg)" }; $[12] = t6; $[13] = t7; } else { t7 = $[13]; } const t8 = isDragging ? "var(--cmd-resize-hover)" : "var(--cmd-resize-bg)"; let t9; if ($[14] !== t8) { t9 = { borderColor: "var(--cmd-border)", backgroundColor: t8 }; $[14] = t8; $[15] = t9; } else { t9 = $[15]; } let t10; let t11; if ($[16] !== isDragging) { t10 = (e_2) => !isDragging && (e_2.currentTarget.style.backgroundColor = "var(--cmd-resize-hover)"); t11 = (e_3) => !isDragging && (e_3.currentTarget.style.backgroundColor = "var(--cmd-resize-bg)"); $[16] = isDragging; $[17] = t10; $[18] = t11; } else { t10 = $[17]; t11 = $[18]; } let t12; if ($[19] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t12 = /* @__PURE__ */ _jsx36("div", { className: "h-1 w-12 rounded-full opacity-50", style: { backgroundColor: "var(--hud-icon)" } }); $[19] = t12; } else { t12 = $[19]; } let t13; if ($[20] !== handleDragStart || $[21] !== t10 || $[22] !== t11 || $[23] !== t9) { t13 = /* @__PURE__ */ _jsx36("div", { onMouseDown: handleDragStart, className: "flex h-3 w-full shrink-0 cursor-ns-resize items-center justify-center border-t transition-colors", style: t9, onMouseEnter: t10, onMouseLeave: t11, children: t12 }); $[20] = handleDragStart; $[21] = t10; $[22] = t11; $[23] = t9; $[24] = t13; } else { t13 = $[24]; } let t14; if ($[25] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t14 = /* @__PURE__ */ _jsx36("span", { className: "mr-2 text-lg font-bold select-none", style: { color: "var(--badge-info-text)" }, children: ">" }); $[25] = t14; } else { t14 = $[25]; } let t15; if ($[26] !== state.phase) { t15 = (state.phase === "idle" || state.phase === "searching") && /* @__PURE__ */ _jsx36(CommandSearch, {}); $[26] = state.phase; $[27] = t15; } else { t15 = $[27]; } let t16; if ($[28] !== state.phase) { t16 = state.phase === "collecting" && /* @__PURE__ */ _jsx36(CommandInputCollector, {}); $[28] = state.phase; $[29] = t16; } else { t16 = $[29]; } let t17; if ($[30] !== state.command || $[31] !== state.phase) { t17 = state.phase === "executing" && /* @__PURE__ */ _jsxs21("div", { className: "flex items-center gap-2", style: { color: "var(--badge-warning-text)" }, children: [/* @__PURE__ */ _jsxs21("svg", { className: "h-5 w-5 animate-spin", viewBox: "0 0 24 24", children: [/* @__PURE__ */ _jsx36("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", fill: "none" }), /* @__PURE__ */ _jsx36("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), /* @__PURE__ */ _jsxs21("span", { className: "text-base", children: ["Executing ", state.command.name, "..."] })] }); $[30] = state.command; $[31] = state.phase; $[32] = t17; } else { t17 = $[32]; } let t18; if ($[33] !== setState || $[34] !== state.message || $[35] !== state.phase) { t18 = state.phase === "error" && /* @__PURE__ */ _jsxs21("div", { className: "flex items-center gap-2", style: { color: "var(--badge-error-text)" }, children: [/* @__PURE__ */ _jsxs21("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [/* @__PURE__ */ _jsx36("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ _jsx36("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), /* @__PURE__ */ _jsx36("line", { x1: "9", y1: "9", x2: "15", y2: "15" })] }), /* @__PURE__ */ _jsx36("span", { className: "text-base", children: state.message }), /* @__PURE__ */ _jsx36("button", { onClick: () => setState({ phase: "idle" }), className: "ml-2 rounded px-2 py-1 text-sm transition-colors", style: { backgroundColor: "var(--list-item-bg-hover)", color: "var(--hud-text-secondary)" }, onMouseEnter: _temp18, onMouseLeave: _temp29, children: "Try again" })] }); $[33] = setState; $[34] = state.message; $[35] = state.phase; $[36] = t18; } else { t18 = $[36]; } let t19; if ($[37] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t19 = /* @__PURE__ */ _jsx36("div", { className: "flex-1" }); $[37] = t19; } else { t19 = $[37]; } let t20; if ($[38] !== progress) { t20 = progress && /* @__PURE__ */ _jsxs21("span", { className: "mr-4 rounded px-2 py-1 text-sm", style: { backgroundColor: "var(--list-item-bg-hover)", color: "var(--hud-text-secondary)" }, children: ["Step ", progress.current, "/", progress.total] }); $[38] = progress; $[39] = t20; } else { t20 = $[39]; } let t21; if ($[40] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t21 = /* @__PURE__ */ _jsx36("span", { className: "text-sm select-none", style: { color: "var(--hud-text-secondary)", opacity: 0.6 }, children: "/ to close \u2022 ESC to cancel" }); $[40] = t21; } else { t21 = $[40]; } let t22; if ($[41] !== t15 || $[42] !== t16 || $[43] !== t17 || $[44] !== t18 || $[45] !== t20) { t22 = /* @__PURE__ */ _jsxs21("div", { className: "flex items-center px-4 py-3 font-mono text-sm", children: [t14, t15, t16, t17, t18, t19, t20, t21] }); $[41] = t15; $[42] = t16; $[43] = t17; $[44] = t18; $[45] = t20; $[46] = t22; } else { t22 = $[46]; } let t23; if ($[47] !== selectedIndex || $[48] !== state.phase || $[49] !== state.suggestions) { t23 = state.phase === "searching" && state.suggestions.length > 0 && /* @__PURE__ */ _jsx36("div", { className: "min-h-0 shrink overflow-y-auto border-t", style: { maxHeight: "40vh", borderColor: "var(--cmd-border)" }, children: state.suggestions.map((cmd, index) => /* @__PURE__ */ _jsx36(CommandSuggestionItem, { command: cmd, isHighlighted: index === selectedIndex }, cmd.name)) }); $[47] = selectedIndex; $[48] = state.phase; $[49] = state.suggestions; $[50] = t23; } else { t23 = $[50]; } let t24; if ($[51] !== t22 || $[52] !== t23) { t24 = /* @__PURE__ */ _jsxs21("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children: [t22, t23] }); $[51] = t22; $[52] = t23; $[53] = t24; } else { t24 = $[53]; } let t25; if ($[54] !== t13 || $[55] !== t24 || $[56] !== t7) { t25 = /* @__PURE__ */ _jsxs21("div", { ref: containerRef, className: "fixed right-0 bottom-0 left-0 z-50 flex flex-col shadow-2xl", style: t7, children: [t13, t24] }); $[54] = t13; $[55] = t24; $[56] = t7; $[57] = t25; } else { t25 = $[57]; } return t25; } function _temp29(e_5) { return e_5.currentTarget.style.color = "var(--hud-text-secondary)"; } function _temp18(e_4) { return e_4.currentTarget.style.color = "var(--hud-text)"; } function CommandSuggestionItem(t0) { const $ = _c65(24); const { command, isHighlighted } = t0; const selectCommand = useSetAtom31(selectCommandAtom); let t1; if ($[0] !== command || $[1] !== selectCommand) { t1 = () => selectCommand(command); $[0] = command; $[1] = selectCommand; $[2] = t1; } else { t1 = $[2]; } const t2 = isHighlighted ? "var(--list-item-bg-hover)" : "transparent"; let t3; if ($[3] !== t2) { t3 = { backgroundColor: t2 }; $[3] = t2; $[4] = t3; } else { t3 = $[4]; } let t4; let t5; if ($[5] !== isHighlighted) { t4 = (e) => !isHighlighted && (e.currentTarget.style.backgroundColor = "var(--list-item-bg-hover)"); t5 = (e_0) => !isHighlighted && (e_0.currentTarget.style.backgroundColor = "transparent"); $[5] = isHighlighted; $[6] = t4; $[7] = t5; } else { t4 = $[6]; t5 = $[7]; } let t6; if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t6 = { color: "var(--hud-text)" }; $[8] = t6; } else { t6 = $[8]; } let t7; if ($[9] !== command.name) { t7 = /* @__PURE__ */ _jsx36("span", { className: "font-mono", style: t6, children: command.name }); $[9] = command.name; $[10] = t7; } else { t7 = $[10]; } let t8; if ($[11] !== command.aliases) { t8 = command.aliases && command.aliases.length > 0 && /* @__PURE__ */ _jsxs21("span", { className: "text-xs", style: { color: "var(--hud-text-secondary)", opacity: 0.5 }, children: ["(", command.aliases.join(", "), ")"] }); $[11] = command.aliases; $[12] = t8; } else { t8 = $[12]; } let t9; if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = { color: "var(--hud-text-secondary)" }; $[13] = t9; } else { t9 = $[13]; } let t10; if ($[14] !== command.description) { t10 = /* @__PURE__ */ _jsx36("span", { className: "text-sm", style: t9, children: command.description }); $[14] = command.description; $[15] = t10; } else { t10 = $[15]; } let t11; if ($[16] !== t1 || $[17] !== t10 || $[18] !== t3 || $[19] !== t4 || $[20] !== t5 || $[21] !== t7 || $[22] !== t8) { t11 = /* @__PURE__ */ _jsxs21("button", { onClick: t1, className: "flex w-full items-center gap-3 px-4 py-2 text-left transition-colors", style: t3, onMouseEnter: t4, onMouseLeave: t5, children: [t7, t8, t10] }); $[16] = t1; $[17] = t10; $[18] = t3; $[19] = t4; $[20] = t5; $[21] = t7; $[22] = t8; $[23] = t11; } else { t11 = $[23]; } return t11; } // src/components/CommandFeedbackOverlay.tsx import { c as _c66 } from "react/compiler-runtime"; import { useAtomValue as useAtomValue43 } from "jotai"; init_viewport_store(); import { jsx as _jsx37, jsxs as _jsxs22 } from "react/jsx-runtime"; function CommandFeedbackOverlay() { const $ = _c66(27); const inputMode = useAtomValue43(inputModeAtom2); const feedback = useAtomValue43(commandFeedbackAtom); const currentInput = useAtomValue43(currentInputAtom); const zoom = useAtomValue43(zoomAtom); const pan = useAtomValue43(panAtom); if (inputMode.type === "normal") { return null; } let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = {}; $[0] = t0; } else { t0 = $[0]; } let t1; if ($[1] !== feedback || $[2] !== inputMode.type || $[3] !== pan || $[4] !== zoom) { t1 = (inputMode.type === "pickPoint" || feedback?.crosshair) && feedback?.cursorWorldPos && /* @__PURE__ */ _jsx37(Crosshair, { position: feedback.cursorWorldPos, zoom, pan }); $[1] = feedback; $[2] = inputMode.type; $[3] = pan; $[4] = zoom; $[5] = t1; } else { t1 = $[5]; } let t2; if ($[6] !== feedback || $[7] !== pan || $[8] !== zoom) { t2 = feedback?.hoveredNodeId && /* @__PURE__ */ _jsx37(NodeHighlight, { nodeId: feedback.hoveredNodeId, zoom, pan }); $[6] = feedback; $[7] = pan; $[8] = zoom; $[9] = t2; } else { t2 = $[9]; } let t3; if ($[10] !== feedback || $[11] !== pan || $[12] !== zoom) { t3 = feedback?.previewEdge && /* @__PURE__ */ _jsx37(PreviewEdge, { edge: feedback.previewEdge, zoom, pan }); $[10] = feedback; $[11] = pan; $[12] = zoom; $[13] = t3; } else { t3 = $[13]; } let t4; if ($[14] !== feedback || $[15] !== pan || $[16] !== zoom) { t4 = feedback?.ghostNode && /* @__PURE__ */ _jsx37(GhostNode, { ghost: feedback.ghostNode, zoom, pan }); $[14] = feedback; $[15] = pan; $[16] = zoom; $[17] = t4; } else { t4 = $[17]; } const t5 = currentInput?.prompt; let t6; if ($[18] !== inputMode || $[19] !== t5) { t6 = /* @__PURE__ */ _jsx37(ModeIndicator, { inputMode, prompt: t5 }); $[18] = inputMode; $[19] = t5; $[20] = t6; } else { t6 = $[20]; } let t7; if ($[21] !== t1 || $[22] !== t2 || $[23] !== t3 || $[24] !== t4 || $[25] !== t6) { t7 = /* @__PURE__ */ _jsxs22("div", { className: "pointer-events-none fixed inset-0 z-50", style: t0, children: [t1, t2, t3, t4, t6] }); $[21] = t1; $[22] = t2; $[23] = t3; $[24] = t4; $[25] = t6; $[26] = t7; } else { t7 = $[26]; } return t7; } function Crosshair(t0) { const $ = _c66(10); const { position, zoom, pan } = t0; const screenX = position.x * zoom + pan.x; const screenY = position.y * zoom + pan.y; const t1 = screenX - 20; const t2 = screenY - 20; let t3; if ($[0] !== t1 || $[1] !== t2) { t3 = { left: t1, top: t2, width: 40, height: 40 }; $[0] = t1; $[1] = t2; $[2] = t3; } else { t3 = $[2]; } let t4; let t5; let t6; let t7; let t8; if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = /* @__PURE__ */ _jsx37("line", { x1: "0", y1: "20", x2: "15", y2: "20", stroke: "var(--list-item-border-active)", strokeWidth: "2" }); t5 = /* @__PURE__ */ _jsx37("line", { x1: "25", y1: "20", x2: "40", y2: "20", stroke: "var(--list-item-border-active)", strokeWidth: "2" }); t6 = /* @__PURE__ */ _jsx37("line", { x1: "20", y1: "0", x2: "20", y2: "15", stroke: "var(--list-item-border-active)", strokeWidth: "2" }); t7 = /* @__PURE__ */ _jsx37("line", { x1: "20", y1: "25", x2: "20", y2: "40", stroke: "var(--list-item-border-active)", strokeWidth: "2" }); t8 = /* @__PURE__ */ _jsx37("circle", { cx: "20", cy: "20", r: "3", fill: "var(--list-item-border-active)" }); $[3] = t4; $[4] = t5; $[5] = t6; $[6] = t7; $[7] = t8; } else { t4 = $[3]; t5 = $[4]; t6 = $[5]; t7 = $[6]; t8 = $[7]; } let t9; if ($[8] !== t3) { t9 = /* @__PURE__ */ _jsxs22("svg", { className: "pointer-events-none absolute", style: t3, children: [t4, t5, t6, t7, t8] }); $[8] = t3; $[9] = t9; } else { t9 = $[9]; } return t9; } function NodeHighlight({ nodeId, zoom, pan }) { return null; } function PreviewEdge(t0) { const $ = _c66(15); const { edge, zoom, pan } = t0; const t1 = edge.from.x * zoom + pan.x; const t2 = edge.from.y * zoom + pan.y; let t3; if ($[0] !== t1 || $[1] !== t2) { t3 = { x: t1, y: t2 }; $[0] = t1; $[1] = t2; $[2] = t3; } else { t3 = $[2]; } const fromScreen = t3; const t4 = edge.to.x * zoom + pan.x; const t5 = edge.to.y * zoom + pan.y; let t6; if ($[3] !== t4 || $[4] !== t5) { t6 = { x: t4, y: t5 }; $[3] = t4; $[4] = t5; $[5] = t6; } else { t6 = $[5]; } const toScreen = t6; let t7; if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t7 = { width: "100%", height: "100%" }; $[6] = t7; } else { t7 = $[6]; } let t8; if ($[7] !== fromScreen.x || $[8] !== fromScreen.y || $[9] !== toScreen.x || $[10] !== toScreen.y) { t8 = /* @__PURE__ */ _jsx37("line", { x1: fromScreen.x, y1: fromScreen.y, x2: toScreen.x, y2: toScreen.y, stroke: "var(--list-item-border-active)", strokeWidth: "2", strokeDasharray: "5,5", markerEnd: "url(#arrowhead)" }); $[7] = fromScreen.x; $[8] = fromScreen.y; $[9] = toScreen.x; $[10] = toScreen.y; $[11] = t8; } else { t8 = $[11]; } let t9; if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = /* @__PURE__ */ _jsx37("defs", { children: /* @__PURE__ */ _jsx37("marker", { id: "arrowhead", markerWidth: "10", markerHeight: "7", refX: "9", refY: "3.5", orient: "auto", children: /* @__PURE__ */ _jsx37("polygon", { points: "0 0, 10 3.5, 0 7", fill: "var(--list-item-border-active)" }) }) }); $[12] = t9; } else { t9 = $[12]; } let t10; if ($[13] !== t8) { t10 = /* @__PURE__ */ _jsxs22("svg", { className: "pointer-events-none absolute inset-0", style: t7, children: [t8, t9] }); $[13] = t8; $[14] = t10; } else { t10 = $[14]; } return t10; } function GhostNode(t0) { const $ = _c66(7); const { ghost, zoom, pan } = t0; const t1 = ghost.position.x * zoom + pan.x; const t2 = ghost.position.y * zoom + pan.y; let t3; if ($[0] !== t1 || $[1] !== t2) { t3 = { x: t1, y: t2 }; $[0] = t1; $[1] = t2; $[2] = t3; } else { t3 = $[2]; } const screenPos = t3; const t4 = screenPos.x - 50; const t5 = screenPos.y - 50; let t6; if ($[3] !== ghost.opacity || $[4] !== t4 || $[5] !== t5) { t6 = /* @__PURE__ */ _jsx37("div", { className: "absolute rounded-lg border-2 border-dashed", style: { left: t4, top: t5, width: 100, height: 100, opacity: ghost.opacity, borderColor: "var(--badge-info-border)", backgroundColor: "var(--badge-info-bg)" } }); $[3] = ghost.opacity; $[4] = t4; $[5] = t5; $[6] = t6; } else { t6 = $[6]; } return t6; } function ModeIndicator(t0) { const $ = _c66(7); const { inputMode, prompt } = t0; const displayPrompt = prompt || inputMode.prompt || "Waiting for input..."; let modeLabel; bb0: switch (inputMode.type) { case "pickPoint": { modeLabel = "Click to pick point"; break bb0; } case "pickNode": { modeLabel = "Click a node to select"; break bb0; } case "pickNodes": { modeLabel = "Click nodes to select (Shift+click for multiple)"; break bb0; } case "select": { modeLabel = "Choose an option"; break bb0; } case "text": { modeLabel = "Type your input"; break bb0; } default: { modeLabel = inputMode.type; } } let t1; let t2; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { backgroundColor: "var(--modal-bg)", borderColor: "var(--modal-border)", color: "var(--modal-text-primary)", opacity: 0.9 }; t2 = { color: "var(--badge-info-text)" }; $[0] = t1; $[1] = t2; } else { t1 = $[0]; t2 = $[1]; } let t3; if ($[2] !== modeLabel) { t3 = /* @__PURE__ */ _jsxs22("span", { style: t2, children: [modeLabel, ":"] }); $[2] = modeLabel; $[3] = t3; } else { t3 = $[3]; } let t4; if ($[4] !== displayPrompt || $[5] !== t3) { t4 = /* @__PURE__ */ _jsxs22("div", { className: "fixed bottom-16 left-1/2 -translate-x-1/2 rounded-lg border px-4 py-2 font-mono text-sm shadow-lg", style: t1, children: [t3, " ", displayPrompt] }); $[4] = displayPrompt; $[5] = t3; $[6] = t4; } else { t4 = $[6]; } return t4; } // src/components/NodePorts.tsx init_port_types(); import { c as _c69 } from "react/compiler-runtime"; import React39 from "react"; // src/components/PortBar.tsx init_port_types(); init_graph_store(); import { c as _c68 } from "react/compiler-runtime"; import React38, { useState as useState9 } from "react"; import { useSetAtom as useSetAtom32 } from "jotai"; // src/components/PortHandle.tsx import { c as _c67 } from "react/compiler-runtime"; import React37 from "react"; import { jsx as _jsx38, jsxs as _jsxs23 } from "react/jsx-runtime"; var PORT_ROW_HEIGHT = 24; var TRANSITION_DURATION = "150ms"; function PortHandle(t0) { const $ = _c67(38); const { port, side, isHovered, showExpanded, isCompatible, isDragTarget, onClick, onPointerDown, onMouseEnter, onMouseLeave } = t0; const canvasStyles = useCanvasStyle(); const t1 = isDragTarget && isCompatible ? port.id : void 0; let t2; if ($[0] !== onClick || $[1] !== port) { t2 = () => onClick(port.id, port); $[0] = onClick; $[1] = port; $[2] = t2; } else { t2 = $[2]; } let t3; if ($[3] !== onPointerDown || $[4] !== port) { t3 = (e) => onPointerDown(port.id, port, e); $[3] = onPointerDown; $[4] = port; $[5] = t3; } else { t3 = $[5]; } let t4; if ($[6] !== onMouseEnter || $[7] !== port) { t4 = () => onMouseEnter(port.id, port); $[6] = onMouseEnter; $[7] = port; $[8] = t4; } else { t4 = $[8]; } const t5 = side === "top" || side === "bottom" ? `${PORT_ROW_HEIGHT}px` : void 0; const t6 = isCompatible ? "pointer" : "not-allowed"; const t7 = isHovered && isCompatible ? canvasStyles.nodes.selectedBorderColor : isDragTarget && !isCompatible ? "rgba(255, 0, 0, 0.1)" : "transparent"; const t8 = isDragTarget && !isCompatible ? 0.5 : 1; let t9; if ($[9] !== t5 || $[10] !== t6 || $[11] !== t7 || $[12] !== t8) { t9 = { flex: 1, minHeight: `${PORT_ROW_HEIGHT}px`, minWidth: t5, display: "flex", alignItems: "center", justifyContent: "center", cursor: t6, backgroundColor: t7, transition: `background-color ${TRANSITION_DURATION}`, opacity: t8 }; $[9] = t5; $[10] = t6; $[11] = t7; $[12] = t8; $[13] = t9; } else { t9 = $[13]; } const t10 = port.color || canvasStyles.nodes.selectedBorderColor; const t11 = isHovered ? "scale(1.5)" : isDragTarget && isCompatible ? "scale(1.3)" : "scale(1)"; const t12 = isHovered && isDragTarget && isCompatible ? `0 0 8px ${port.color || canvasStyles.nodes.selectedBorderColor}` : "none"; let t13; if ($[14] !== t10 || $[15] !== t11 || $[16] !== t12) { t13 = /* @__PURE__ */ _jsx38("div", { style: { width: "6px", height: "6px", borderRadius: "50%", backgroundColor: t10, transition: `transform ${TRANSITION_DURATION}, box-shadow ${TRANSITION_DURATION}`, transform: t11, boxShadow: t12 } }); $[14] = t10; $[15] = t11; $[16] = t12; $[17] = t13; } else { t13 = $[17]; } let t14; if ($[18] !== canvasStyles || $[19] !== isCompatible || $[20] !== isDragTarget || $[21] !== isHovered || $[22] !== port.id || $[23] !== port.label || $[24] !== showExpanded || $[25] !== side) { t14 = showExpanded && /* @__PURE__ */ _jsx38("span", { style: { marginLeft: side === "left" || side === "right" ? "4px" : 0, marginTop: side === "top" || side === "bottom" ? "4px" : 0, fontSize: "10px", color: isHovered ? "#fff" : isCompatible ? canvasStyles.nodes.defaultBorderColor : "rgba(128, 128, 128, 0.5)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textDecoration: isDragTarget && !isCompatible ? "line-through" : "none", transition: `opacity ${TRANSITION_DURATION}`, opacity: showExpanded ? 1 : 0 }, children: port.label || port.id }); $[18] = canvasStyles; $[19] = isCompatible; $[20] = isDragTarget; $[21] = isHovered; $[22] = port.id; $[23] = port.label; $[24] = showExpanded; $[25] = side; $[26] = t14; } else { t14 = $[26]; } let t15; if ($[27] !== onMouseLeave || $[28] !== port.id || $[29] !== port.type || $[30] !== t1 || $[31] !== t13 || $[32] !== t14 || $[33] !== t2 || $[34] !== t3 || $[35] !== t4 || $[36] !== t9) { t15 = /* @__PURE__ */ _jsxs23("div", { "data-port-id": port.id, "data-port-type": port.type, "data-drag-port-id": t1, onClick: t2, onPointerDown: t3, onMouseEnter: t4, onMouseLeave, style: t9, children: [t13, t14] }); $[27] = onMouseLeave; $[28] = port.id; $[29] = port.type; $[30] = t1; $[31] = t13; $[32] = t14; $[33] = t2; $[34] = t3; $[35] = t4; $[36] = t9; $[37] = t15; } else { t15 = $[37]; } return t15; } // src/components/PortBar.tsx import { jsx as _jsx39 } from "react/jsx-runtime"; var COLLAPSED_WIDTH = 8; var EXPANDED_WIDTH = 80; var TRANSITION_DURATION2 = "150ms"; function PortBar(t0) { const $ = _c68(66); const { nodeId, side, ports, nodeWidth, nodeHeight, forceExpanded: t1, onPortClick, onPortHover, isDragTarget: t2, dragSourcePort } = t0; const forceExpanded = t1 === void 0 ? false : t1; const isDragTarget = t2 === void 0 ? false : t2; const [isExpanded, setIsExpanded] = useState9(false); const [hoveredPortId, setHoveredPortId] = useState9(null); const canvasStyles = useCanvasStyle(); const setEdgeCreation = useSetAtom32(edgeCreationAtom); const showExpanded = isExpanded || forceExpanded || isDragTarget; let t3; bb0: { const t42 = side === "top" || side === "bottom" ? "row" : "column"; let t52; if ($[0] !== canvasStyles.nodes.defaultBackground || $[1] !== t42) { t52 = { position: "absolute", display: "flex", flexDirection: t42, alignItems: "stretch", transition: `all ${TRANSITION_DURATION2} ease-out`, backgroundColor: canvasStyles.nodes.defaultBackground, borderRadius: "4px", overflow: "hidden", zIndex: 10 }; $[0] = canvasStyles.nodes.defaultBackground; $[1] = t42; $[2] = t52; } else { t52 = $[2]; } const base = t52; const barSize = showExpanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH; const barLength = side === "left" || side === "right" ? Math.max(ports.length * PORT_ROW_HEIGHT, nodeHeight * 0.6) : Math.max(ports.length * PORT_ROW_HEIGHT, nodeWidth * 0.6); switch (side) { case "left": { const t62 = -barSize; const t72 = `${barSize}px`; const t82 = `${barLength}px`; const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`; let t10; if ($[3] !== base || $[4] !== t62 || $[5] !== t72 || $[6] !== t82 || $[7] !== t92) { t10 = { ...base, left: t62, top: "50%", transform: "translateY(-50%)", width: t72, height: t82, borderRight: t92, borderTopRightRadius: 0, borderBottomRightRadius: 0 }; $[3] = base; $[4] = t62; $[5] = t72; $[6] = t82; $[7] = t92; $[8] = t10; } else { t10 = $[8]; } t3 = t10; break bb0; } case "right": { const t62 = -barSize; const t72 = `${barSize}px`; const t82 = `${barLength}px`; const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`; let t10; if ($[9] !== base || $[10] !== t62 || $[11] !== t72 || $[12] !== t82 || $[13] !== t92) { t10 = { ...base, right: t62, top: "50%", transform: "translateY(-50%)", width: t72, height: t82, borderLeft: t92, borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }; $[9] = base; $[10] = t62; $[11] = t72; $[12] = t82; $[13] = t92; $[14] = t10; } else { t10 = $[14]; } t3 = t10; break bb0; } case "top": { const t62 = -barSize; const t72 = `${barSize}px`; const t82 = `${barLength}px`; const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`; let t10; if ($[15] !== base || $[16] !== t62 || $[17] !== t72 || $[18] !== t82 || $[19] !== t92) { t10 = { ...base, top: t62, left: "50%", transform: "translateX(-50%)", height: t72, width: t82, borderBottom: t92, borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }; $[15] = base; $[16] = t62; $[17] = t72; $[18] = t82; $[19] = t92; $[20] = t10; } else { t10 = $[20]; } t3 = t10; break bb0; } case "bottom": { const t62 = -barSize; const t72 = `${barSize}px`; const t82 = `${barLength}px`; const t92 = `1px solid ${canvasStyles.nodes.defaultBorderColor}`; let t10; if ($[21] !== base || $[22] !== t62 || $[23] !== t72 || $[24] !== t82 || $[25] !== t92) { t10 = { ...base, bottom: t62, left: "50%", transform: "translateX(-50%)", height: t72, width: t82, borderTop: t92, borderTopLeftRadius: 0, borderTopRightRadius: 0 }; $[21] = base; $[22] = t62; $[23] = t72; $[24] = t82; $[25] = t92; $[26] = t10; } else { t10 = $[26]; } t3 = t10; break bb0; } } t3 = void 0; } const barStyle = t3; let t4; if ($[27] !== dragSourcePort || $[28] !== isDragTarget) { t4 = (port) => { if (!isDragTarget || !dragSourcePort) { return true; } return arePortsCompatible(dragSourcePort, port); }; $[27] = dragSourcePort; $[28] = isDragTarget; $[29] = t4; } else { t4 = $[29]; } const isPortCompatible = t4; let t5; if ($[30] !== isDragTarget) { t5 = () => { if (!isDragTarget) { setIsExpanded(true); } }; $[30] = isDragTarget; $[31] = t5; } else { t5 = $[31]; } const handleMouseEnter = t5; let t6; if ($[32] !== forceExpanded || $[33] !== onPortHover) { t6 = () => { if (!forceExpanded) { setIsExpanded(false); setHoveredPortId(null); onPortHover?.(null, null); } }; $[32] = forceExpanded; $[33] = onPortHover; $[34] = t6; } else { t6 = $[34]; } const handleMouseLeave = t6; let t7; if ($[35] !== nodeHeight || $[36] !== nodeId || $[37] !== nodeWidth || $[38] !== setEdgeCreation) { t7 = (portId, port_0, e) => { if (e.button !== 0) { return; } e.stopPropagation(); e.preventDefault(); const portPos = calculatePortPosition(0, 0, nodeWidth, nodeHeight, port_0); setEdgeCreation({ isCreating: true, sourceNodeId: nodeId, sourceNodePosition: { x: portPos.x, y: portPos.y }, targetPosition: { x: portPos.x, y: portPos.y }, hoveredTargetNodeId: null, sourceHandle: `widget:${nodeId}:${port_0.type === "input" ? "input" : "output"}:${port_0.id}`, targetHandle: null, sourcePort: portId, targetPort: null, snappedTargetPosition: null }); }; $[35] = nodeHeight; $[36] = nodeId; $[37] = nodeWidth; $[38] = setEdgeCreation; $[39] = t7; } else { t7 = $[39]; } const handlePortPointerDown = t7; let t8; if ($[40] !== handlePortPointerDown || $[41] !== hoveredPortId || $[42] !== isDragTarget || $[43] !== isPortCompatible || $[44] !== onPortClick || $[45] !== onPortHover || $[46] !== ports || $[47] !== showExpanded || $[48] !== side) { let t92; if ($[50] !== handlePortPointerDown || $[51] !== hoveredPortId || $[52] !== isDragTarget || $[53] !== isPortCompatible || $[54] !== onPortClick || $[55] !== onPortHover || $[56] !== showExpanded || $[57] !== side) { t92 = (port_1) => /* @__PURE__ */ _jsx39(PortHandle, { port: port_1, side, isHovered: hoveredPortId === port_1.id, showExpanded, isCompatible: isPortCompatible(port_1), isDragTarget, onClick: (id, p) => onPortClick?.(id, p), onPointerDown: handlePortPointerDown, onMouseEnter: (id_0, p_0) => { setHoveredPortId(id_0); onPortHover?.(id_0, p_0); }, onMouseLeave: () => { setHoveredPortId(null); onPortHover?.(null, null); } }, port_1.id); $[50] = handlePortPointerDown; $[51] = hoveredPortId; $[52] = isDragTarget; $[53] = isPortCompatible; $[54] = onPortClick; $[55] = onPortHover; $[56] = showExpanded; $[57] = side; $[58] = t92; } else { t92 = $[58]; } t8 = ports.map(t92); $[40] = handlePortPointerDown; $[41] = hoveredPortId; $[42] = isDragTarget; $[43] = isPortCompatible; $[44] = onPortClick; $[45] = onPortHover; $[46] = ports; $[47] = showExpanded; $[48] = side; $[49] = t8; } else { t8 = $[49]; } let t9; if ($[59] !== barStyle || $[60] !== handleMouseEnter || $[61] !== handleMouseLeave || $[62] !== nodeId || $[63] !== side || $[64] !== t8) { t9 = /* @__PURE__ */ _jsx39("div", { style: barStyle, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, "data-port-bar": side, "data-node-id": nodeId, children: t8 }); $[59] = barStyle; $[60] = handleMouseEnter; $[61] = handleMouseLeave; $[62] = nodeId; $[63] = side; $[64] = t8; $[65] = t9; } else { t9 = $[65]; } return t9; } // src/components/NodePorts.tsx import { jsx as _jsx40, Fragment as _Fragment8 } from "react/jsx-runtime"; function NodePorts(t0) { const $ = _c69(25); const { nodeId, nodeWidth, nodeHeight, ports: portsProp, sides, onStartConnection, onPortHover, isDragTarget: t1, dragSourcePort } = t0; const isDragTarget = t1 === void 0 ? false : t1; let portsBySide; if ($[0] !== portsProp) { const ports = getNodePorts(portsProp); portsBySide = { top: [], right: [], bottom: [], left: [] }; for (const port of ports) { portsBySide[port.side].push(port); } $[0] = portsProp; $[1] = portsBySide; } else { portsBySide = $[1]; } let t2; if ($[2] !== sides) { t2 = sides || ["left", "right", "top", "bottom"]; $[2] = sides; $[3] = t2; } else { t2 = $[3]; } const sidesToRender = t2; let t3; if ($[4] !== dragSourcePort || $[5] !== isDragTarget || $[6] !== nodeHeight || $[7] !== nodeId || $[8] !== nodeWidth || $[9] !== onPortHover || $[10] !== onStartConnection || $[11] !== portsBySide || $[12] !== sidesToRender) { let t42; if ($[14] !== dragSourcePort || $[15] !== isDragTarget || $[16] !== nodeHeight || $[17] !== nodeId || $[18] !== nodeWidth || $[19] !== onPortHover || $[20] !== onStartConnection || $[21] !== portsBySide) { t42 = (side) => { const sidePorts = portsBySide[side]; if (sidePorts.length === 0) { return null; } return /* @__PURE__ */ _jsx40(PortBar, { nodeId, side, ports: sidePorts, nodeWidth, nodeHeight, onPortClick: (portId, port_0) => onStartConnection?.(nodeId, portId, port_0), onPortHover: (portId_0, port_1) => onPortHover?.(nodeId, portId_0, port_1), isDragTarget, dragSourcePort }, side); }; $[14] = dragSourcePort; $[15] = isDragTarget; $[16] = nodeHeight; $[17] = nodeId; $[18] = nodeWidth; $[19] = onPortHover; $[20] = onStartConnection; $[21] = portsBySide; $[22] = t42; } else { t42 = $[22]; } t3 = sidesToRender.map(t42); $[4] = dragSourcePort; $[5] = isDragTarget; $[6] = nodeHeight; $[7] = nodeId; $[8] = nodeWidth; $[9] = onPortHover; $[10] = onStartConnection; $[11] = portsBySide; $[12] = sidesToRender; $[13] = t3; } else { t3 = $[13]; } let t4; if ($[23] !== t3) { t4 = /* @__PURE__ */ _jsx40(_Fragment8, { children: t3 }); $[23] = t3; $[24] = t4; } else { t4 = $[24]; } return t4; } function getPortWorldPosition(nodeX, nodeY, nodeWidth, nodeHeight, port) { return calculatePortPosition(nodeX, nodeY, nodeWidth, nodeHeight, port); } // src/components/ViewportControls.tsx init_viewport_store(); init_input_store(); import { c as _c70 } from "react/compiler-runtime"; import React40, { useState as useState10 } from "react"; import { useAtom as useAtom8, useAtomValue as useAtomValue44, useSetAtom as useSetAtom33 } from "jotai"; import { jsx as _jsx41, jsxs as _jsxs24 } from "react/jsx-runtime"; function ViewportControls(t0) { const $ = _c70(39); const { minZoom: t1, maxZoom: t2, zoomStep: t3, position: t4, alwaysVisible: t5, className, style } = t0; const minZoom = t1 === void 0 ? 0.1 : t1; const maxZoom = t2 === void 0 ? 5 : t2; const zoomStep = t3 === void 0 ? 0.25 : t3; const position = t4 === void 0 ? "bottom-right" : t4; const alwaysVisible = t5 === void 0 ? false : t5; const [zoom, setZoom] = useAtom8(zoomAtom); useAtom8(panAtom); const isTouchDevice = useAtomValue44(isTouchDeviceAtom); const resetViewport = useSetAtom33(resetViewportAtom); let t6; if ($[0] !== maxZoom || $[1] !== setZoom || $[2] !== zoom || $[3] !== zoomStep) { t6 = () => { setZoom(Math.min(maxZoom, zoom + zoomStep)); }; $[0] = maxZoom; $[1] = setZoom; $[2] = zoom; $[3] = zoomStep; $[4] = t6; } else { t6 = $[4]; } const handleZoomIn = t6; let t7; if ($[5] !== minZoom || $[6] !== setZoom || $[7] !== zoom || $[8] !== zoomStep) { t7 = () => { setZoom(Math.max(minZoom, zoom - zoomStep)); }; $[5] = minZoom; $[6] = setZoom; $[7] = zoom; $[8] = zoomStep; $[9] = t7; } else { t7 = $[9]; } const handleZoomOut = t7; let t8; if ($[10] !== resetViewport) { t8 = () => { resetViewport(); }; $[10] = resetViewport; $[11] = t8; } else { t8 = $[11]; } const handleReset = t8; if (!alwaysVisible && !isTouchDevice) { return null; } let t9; if ($[12] !== position) { t9 = getPositionStyles(position); $[12] = position; $[13] = t9; } else { t9 = $[13]; } const positionStyles = t9; let t10; if ($[14] !== zoom) { t10 = Math.round(zoom * 100); $[14] = zoom; $[15] = t10; } else { t10 = $[15]; } const zoomPercent = t10; let t11; if ($[16] !== positionStyles || $[17] !== style) { t11 = { position: "absolute", ...positionStyles, display: "flex", flexDirection: "column", gap: "4px", zIndex: 100, pointerEvents: "auto", ...style }; $[16] = positionStyles; $[17] = style; $[18] = t11; } else { t11 = $[18]; } const t12 = zoom >= maxZoom; let t13; if ($[19] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t13 = /* @__PURE__ */ _jsx41(PlusIcon, {}); $[19] = t13; } else { t13 = $[19]; } let t14; if ($[20] !== handleZoomIn || $[21] !== t12) { t14 = /* @__PURE__ */ _jsx41(ControlButton, { onClick: handleZoomIn, disabled: t12, "aria-label": "Zoom in", children: t13 }); $[20] = handleZoomIn; $[21] = t12; $[22] = t14; } else { t14 = $[22]; } const t15 = `Reset zoom (${zoomPercent}%)`; const t16 = `${zoomPercent}%`; let t17; if ($[23] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t17 = { fontSize: "10px", fontWeight: 600 }; $[23] = t17; } else { t17 = $[23]; } let t18; if ($[24] !== handleReset || $[25] !== t15 || $[26] !== t16 || $[27] !== zoomPercent) { t18 = /* @__PURE__ */ _jsxs24(ControlButton, { onClick: handleReset, "aria-label": t15, title: t16, style: t17, children: [zoomPercent, "%"] }); $[24] = handleReset; $[25] = t15; $[26] = t16; $[27] = zoomPercent; $[28] = t18; } else { t18 = $[28]; } const t19 = zoom <= minZoom; let t20; if ($[29] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t20 = /* @__PURE__ */ _jsx41(MinusIcon, {}); $[29] = t20; } else { t20 = $[29]; } let t21; if ($[30] !== handleZoomOut || $[31] !== t19) { t21 = /* @__PURE__ */ _jsx41(ControlButton, { onClick: handleZoomOut, disabled: t19, "aria-label": "Zoom out", children: t20 }); $[30] = handleZoomOut; $[31] = t19; $[32] = t21; } else { t21 = $[32]; } let t22; if ($[33] !== className || $[34] !== t11 || $[35] !== t14 || $[36] !== t18 || $[37] !== t21) { t22 = /* @__PURE__ */ _jsxs24("div", { className, style: t11, children: [t14, t18, t21] }); $[33] = className; $[34] = t11; $[35] = t14; $[36] = t18; $[37] = t21; $[38] = t22; } else { t22 = $[38]; } return t22; } function ControlButton(t0) { const $ = _c70(21); let children; let disabled; let onClick; let props; let style; if ($[0] !== t0) { ({ children, style, disabled, onClick, ...props } = t0); $[0] = t0; $[1] = children; $[2] = disabled; $[3] = onClick; $[4] = props; $[5] = style; } else { children = $[1]; disabled = $[2]; onClick = $[3]; props = $[4]; style = $[5]; } const [pulsing, setPulsing] = useState10(false); let t1; if ($[6] !== disabled || $[7] !== onClick) { t1 = (e) => { if (!disabled) { setPulsing(true); } onClick?.(e); }; $[6] = disabled; $[7] = onClick; $[8] = t1; } else { t1 = $[8]; } const handleClick = t1; const t2 = pulsing ? "canvas-tap-pulse" : void 0; let t3; if ($[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t3 = () => setPulsing(false); $[9] = t3; } else { t3 = $[9]; } const t4 = disabled ? "default" : "pointer"; const t5 = disabled ? 0.4 : 1; let t6; if ($[10] !== style || $[11] !== t4 || $[12] !== t5) { t6 = { width: "44px", height: "44px", borderRadius: "12px", border: "1px solid var(--canvas-control-border, rgba(255,255,255,0.1))", backgroundColor: "var(--canvas-control-bg, rgba(30,30,30,0.85))", color: "var(--canvas-control-fg, rgba(255,255,255,0.9))", display: "flex", alignItems: "center", justifyContent: "center", cursor: t4, opacity: t5, touchAction: "manipulation", WebkitTapHighlightColor: "transparent", userSelect: "none", transition: "background-color 150ms, opacity 150ms", backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)", padding: 0, ...style }; $[10] = style; $[11] = t4; $[12] = t5; $[13] = t6; } else { t6 = $[13]; } let t7; if ($[14] !== children || $[15] !== disabled || $[16] !== handleClick || $[17] !== props || $[18] !== t2 || $[19] !== t6) { t7 = /* @__PURE__ */ _jsx41("button", { ...props, disabled, className: t2, onAnimationEnd: t3, onClick: handleClick, style: t6, "data-no-drag": "true", children }); $[14] = children; $[15] = disabled; $[16] = handleClick; $[17] = props; $[18] = t2; $[19] = t6; $[20] = t7; } else { t7 = $[20]; } return t7; } function PlusIcon() { const $ = _c70(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs24("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [/* @__PURE__ */ _jsx41("line", { x1: "9", y1: "4", x2: "9", y2: "14" }), /* @__PURE__ */ _jsx41("line", { x1: "4", y1: "9", x2: "14", y2: "9" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function MinusIcon() { const $ = _c70(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsx41("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ _jsx41("line", { x1: "4", y1: "9", x2: "14", y2: "9" }) }); $[0] = t0; } else { t0 = $[0]; } return t0; } function getPositionStyles(position) { switch (position) { case "bottom-right": return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", right: "max(16px, env(safe-area-inset-right, 16px))" }; case "bottom-left": return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", left: "max(16px, env(safe-area-inset-left, 16px))" }; case "top-right": return { top: "max(16px, env(safe-area-inset-top, 16px))", right: "max(16px, env(safe-area-inset-right, 16px))" }; case "top-left": return { top: "max(16px, env(safe-area-inset-top, 16px))", left: "max(16px, env(safe-area-inset-left, 16px))" }; default: return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", right: "max(16px, env(safe-area-inset-right, 16px))" }; } } // src/components/SelectionOverlay.tsx init_selection_path_store(); init_viewport_store(); import { c as _c71 } from "react/compiler-runtime"; import React41 from "react"; import { useAtomValue as useAtomValue45 } from "jotai"; import { jsx as _jsx42, jsxs as _jsxs25 } from "react/jsx-runtime"; function SelectionOverlay(t0) { const $ = _c71(18); const { fillColor: t1, strokeColor: t2, strokeWidth: t3 } = t0; const fillColor = t1 === void 0 ? "rgba(99, 102, 241, 0.15)" : t1; const strokeColor = t2 === void 0 ? "rgba(99, 102, 241, 0.6)" : t2; const strokeWidth = t3 === void 0 ? 1.5 : t3; const path = useAtomValue45(selectionPathAtom); const selectionRect = useAtomValue45(selectionRectAtom); useAtomValue45(panAtom); const zoom = useAtomValue45(zoomAtom); if (!path) { return null; } let t4; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t4 = { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none", overflow: "visible", zIndex: 9998 }; $[0] = t4; } else { t4 = $[0]; } let t5; if ($[1] !== fillColor || $[2] !== path.type || $[3] !== selectionRect || $[4] !== strokeColor || $[5] !== strokeWidth || $[6] !== zoom) { t5 = path.type === "rect" && selectionRect && /* @__PURE__ */ _jsx42("rect", { x: selectionRect.x, y: selectionRect.y, width: selectionRect.width, height: selectionRect.height, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth / zoom, strokeDasharray: `${4 / zoom} ${4 / zoom}` }); $[1] = fillColor; $[2] = path.type; $[3] = selectionRect; $[4] = strokeColor; $[5] = strokeWidth; $[6] = zoom; $[7] = t5; } else { t5 = $[7]; } let t6; if ($[8] !== fillColor || $[9] !== path.points || $[10] !== path.type || $[11] !== strokeColor || $[12] !== strokeWidth || $[13] !== zoom) { t6 = path.type === "lasso" && path.points.length >= 2 && /* @__PURE__ */ _jsx42("polygon", { points: path.points.map(_temp19).join(" "), fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth / zoom, strokeLinejoin: "round" }); $[8] = fillColor; $[9] = path.points; $[10] = path.type; $[11] = strokeColor; $[12] = strokeWidth; $[13] = zoom; $[14] = t6; } else { t6 = $[14]; } let t7; if ($[15] !== t5 || $[16] !== t6) { t7 = /* @__PURE__ */ _jsxs25("svg", { style: t4, children: [t5, t6] }); $[15] = t5; $[16] = t6; $[17] = t7; } else { t7 = $[17]; } return t7; } function _temp19(p) { return `${p.x},${p.y}`; } // src/components/Minimap.tsx init_graph_derived(); init_graph_store(); init_viewport_store(); init_selection_store(); import { c as _c72 } from "react/compiler-runtime"; import React42, { useRef as useRef29, useEffect as useEffect22 } from "react"; import { useAtomValue as useAtomValue46, useSetAtom as useSetAtom34 } from "jotai"; // src/components/minimap-utils.ts function computeGraphBounds(nodes) { if (nodes.length === 0) return null; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const node of nodes) { const x = node.position.x; const y = node.position.y; const w = node.width ?? 200; const h = node.height ?? 100; if (x < minX) minX = x; if (y < minY) minY = y; if (x + w > maxX) maxX = x + w; if (y + h > maxY) maxY = y + h; } return { minX, minY, maxX, maxY }; } function getMinimapTransform(bounds, minimapWidth, minimapHeight, padding = 20) { const graphW = bounds.maxX - bounds.minX; const graphH = bounds.maxY - bounds.minY; if (graphW <= 0 || graphH <= 0) return null; const scaleX = (minimapWidth - padding * 2) / graphW; const scaleY = (minimapHeight - padding * 2) / graphH; const scale = Math.min(scaleX, scaleY); const offsetX = padding + (minimapWidth - padding * 2 - graphW * scale) / 2; const offsetY = padding + (minimapHeight - padding * 2 - graphH * scale) / 2; return { scale, offsetX, offsetY, minX: bounds.minX, minY: bounds.minY }; } function minimapToWorld(mx, my, transform) { return { x: (mx - transform.offsetX) / transform.scale + transform.minX, y: (my - transform.offsetY) / transform.scale + transform.minY }; } // src/components/Minimap.tsx import { jsx as _jsx43 } from "react/jsx-runtime"; function Minimap(t0) { const $ = _c72(60); const { width: t1, height: t2, backgroundColor: t3, nodeColor: t4, selectedNodeColor: t5, viewportColor: t6, borderColor: t7, edgeColor: t8, position: t9, offset: t10, className } = t0; const width = t1 === void 0 ? 200 : t1; const height = t2 === void 0 ? 150 : t2; const backgroundColor = t3 === void 0 ? "rgba(0, 0, 0, 0.7)" : t3; const nodeColor = t4 === void 0 ? "rgba(148, 163, 184, 0.6)" : t4; const selectedNodeColor = t5 === void 0 ? "rgba(99, 102, 241, 0.8)" : t5; const viewportColor = t6 === void 0 ? "rgba(255, 255, 255, 0.3)" : t6; const borderColor = t7 === void 0 ? "rgba(255, 255, 255, 0.2)" : t7; const edgeColor = t8 === void 0 ? "rgba(148, 163, 184, 0.3)" : t8; const position = t9 === void 0 ? "bottom-right" : t9; const offset = t10 === void 0 ? 12 : t10; const canvasRef = useRef29(null); const nodes = useAtomValue46(uiNodesAtom); const graph = useAtomValue46(graphAtom); const pan = useAtomValue46(panAtom); const zoom = useAtomValue46(zoomAtom); const viewportRect = useAtomValue46(viewportRectAtom); const selectedNodeIds = useAtomValue46(selectedNodeIdsAtom); const setPan = useSetAtom34(panAtom); const isDragging = useRef29(false); let t11; if ($[0] !== nodes) { t11 = computeGraphBounds(nodes); $[0] = nodes; $[1] = t11; } else { t11 = $[1]; } const graphBounds = t11; let t12; if ($[2] !== graphBounds || $[3] !== height || $[4] !== width) { t12 = (mx, my) => { if (!graphBounds) { return null; } const t = getMinimapTransform(graphBounds, width, height); if (!t) { return null; } return minimapToWorld(mx, my, t); }; $[2] = graphBounds; $[3] = height; $[4] = width; $[5] = t12; } else { t12 = $[5]; } const minimapToWorldCoords = t12; let t13; if ($[6] !== setPan || $[7] !== viewportRect || $[8] !== zoom) { t13 = (worldX, worldY) => { if (!viewportRect) { return; } const newPanX = -worldX * zoom + viewportRect.width / 2; const newPanY = -worldY * zoom + viewportRect.height / 2; setPan({ x: newPanX, y: newPanY }); }; $[6] = setPan; $[7] = viewportRect; $[8] = zoom; $[9] = t13; } else { t13 = $[9]; } const panToWorld = t13; let t14; if ($[10] !== backgroundColor || $[11] !== borderColor || $[12] !== edgeColor || $[13] !== graph || $[14] !== graphBounds || $[15] !== height || $[16] !== nodeColor || $[17] !== nodes || $[18] !== pan || $[19] !== selectedNodeColor || $[20] !== selectedNodeIds || $[21] !== viewportColor || $[22] !== viewportRect || $[23] !== width || $[24] !== zoom) { t14 = () => { const canvas = canvasRef.current; if (!canvas) { return; } const ctx = canvas.getContext("2d"); if (!ctx) { return; } const dpr = window.devicePixelRatio || 1; canvas.width = width * dpr; canvas.height = height * dpr; ctx.scale(dpr, dpr); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, width, height); const t_0 = graphBounds ? getMinimapTransform(graphBounds, width, height) : null; if (!t_0) { return; } const nodePositions = /* @__PURE__ */ new Map(); for (const node of nodes) { const x = (node.position.x - t_0.minX) * t_0.scale + t_0.offsetX; const y = (node.position.y - t_0.minY) * t_0.scale + t_0.offsetY; const w = Math.max((node.width ?? 200) * t_0.scale, 2); const h = Math.max((node.height ?? 100) * t_0.scale, 2); nodePositions.set(node.id, { cx: x + w / 2, cy: y + h / 2 }); } ctx.strokeStyle = edgeColor; ctx.lineWidth = 1; graph.forEachEdge((_key, _attrs, source, target) => { const from = nodePositions.get(source); const to = nodePositions.get(target); if (!from || !to) { return; } ctx.beginPath(); ctx.moveTo(from.cx, from.cy); ctx.lineTo(to.cx, to.cy); ctx.stroke(); }); for (const node_0 of nodes) { const pos = nodePositions.get(node_0.id); if (!pos) { continue; } const w_0 = Math.max((node_0.width ?? 200) * t_0.scale, 2); const h_0 = Math.max((node_0.height ?? 100) * t_0.scale, 2); ctx.fillStyle = selectedNodeIds.has(node_0.id) ? selectedNodeColor : nodeColor; ctx.fillRect(pos.cx - w_0 / 2, pos.cy - h_0 / 2, w_0, h_0); } if (viewportRect && viewportRect.width > 0) { const vpWorldMinX = -pan.x / zoom; const vpWorldMinY = -pan.y / zoom; const vpWorldW = viewportRect.width / zoom; const vpWorldH = viewportRect.height / zoom; const vpX = (vpWorldMinX - t_0.minX) * t_0.scale + t_0.offsetX; const vpY = (vpWorldMinY - t_0.minY) * t_0.scale + t_0.offsetY; const vpW = vpWorldW * t_0.scale; const vpH = vpWorldH * t_0.scale; ctx.strokeStyle = viewportColor; ctx.lineWidth = 1.5; ctx.strokeRect(vpX, vpY, vpW, vpH); ctx.fillStyle = viewportColor.replace(/[\d.]+\)$/, "0.1)"); ctx.fillRect(vpX, vpY, vpW, vpH); } ctx.strokeStyle = borderColor; ctx.lineWidth = 1; ctx.strokeRect(0, 0, width, height); }; $[10] = backgroundColor; $[11] = borderColor; $[12] = edgeColor; $[13] = graph; $[14] = graphBounds; $[15] = height; $[16] = nodeColor; $[17] = nodes; $[18] = pan; $[19] = selectedNodeColor; $[20] = selectedNodeIds; $[21] = viewportColor; $[22] = viewportRect; $[23] = width; $[24] = zoom; $[25] = t14; } else { t14 = $[25]; } let t15; if ($[26] !== backgroundColor || $[27] !== borderColor || $[28] !== edgeColor || $[29] !== graph || $[30] !== height || $[31] !== nodeColor || $[32] !== nodes || $[33] !== pan || $[34] !== selectedNodeColor || $[35] !== selectedNodeIds || $[36] !== viewportColor || $[37] !== viewportRect || $[38] !== width || $[39] !== zoom) { t15 = [nodes, graph, pan, zoom, viewportRect, selectedNodeIds, width, height, backgroundColor, nodeColor, selectedNodeColor, viewportColor, borderColor, edgeColor, getMinimapTransform]; $[26] = backgroundColor; $[27] = borderColor; $[28] = edgeColor; $[29] = graph; $[30] = height; $[31] = nodeColor; $[32] = nodes; $[33] = pan; $[34] = selectedNodeColor; $[35] = selectedNodeIds; $[36] = viewportColor; $[37] = viewportRect; $[38] = width; $[39] = zoom; $[40] = t15; } else { t15 = $[40]; } useEffect22(t14, t15); let t16; if ($[41] !== minimapToWorldCoords || $[42] !== panToWorld) { t16 = (e) => { e.stopPropagation(); e.preventDefault(); isDragging.current = true; e.target.setPointerCapture(e.pointerId); const rect = canvasRef.current?.getBoundingClientRect(); if (!rect) { return; } const mx_0 = e.clientX - rect.left; const my_0 = e.clientY - rect.top; const worldPos = minimapToWorldCoords(mx_0, my_0); if (worldPos) { panToWorld(worldPos.x, worldPos.y); } }; $[41] = minimapToWorldCoords; $[42] = panToWorld; $[43] = t16; } else { t16 = $[43]; } const handlePointerDown = t16; let t17; if ($[44] !== minimapToWorldCoords || $[45] !== panToWorld) { t17 = (e_0) => { if (!isDragging.current) { return; } e_0.stopPropagation(); const rect_0 = canvasRef.current?.getBoundingClientRect(); if (!rect_0) { return; } const mx_1 = e_0.clientX - rect_0.left; const my_1 = e_0.clientY - rect_0.top; const worldPos_0 = minimapToWorldCoords(mx_1, my_1); if (worldPos_0) { panToWorld(worldPos_0.x, worldPos_0.y); } }; $[44] = minimapToWorldCoords; $[45] = panToWorld; $[46] = t17; } else { t17 = $[46]; } const handlePointerMove = t17; let t18; if ($[47] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t18 = (e_1) => { isDragging.current = false; e_1.target.releasePointerCapture(e_1.pointerId); }; $[47] = t18; } else { t18 = $[47]; } const handlePointerUp = t18; let t19; if ($[48] !== offset || $[49] !== position) { t19 = { position: "absolute", zIndex: 9999, ...position.includes("top") ? { top: offset } : { bottom: offset }, ...position.includes("left") ? { left: offset } : { right: offset } }; $[48] = offset; $[49] = position; $[50] = t19; } else { t19 = $[50]; } const positionStyles = t19; let t20; if ($[51] !== height || $[52] !== positionStyles || $[53] !== width) { t20 = { ...positionStyles, width, height, borderRadius: 6, cursor: "pointer", touchAction: "none" }; $[51] = height; $[52] = positionStyles; $[53] = width; $[54] = t20; } else { t20 = $[54]; } let t21; if ($[55] !== className || $[56] !== handlePointerDown || $[57] !== handlePointerMove || $[58] !== t20) { t21 = /* @__PURE__ */ _jsx43("canvas", { ref: canvasRef, className, style: t20, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }); $[55] = className; $[56] = handlePointerDown; $[57] = handlePointerMove; $[58] = t20; $[59] = t21; } else { t21 = $[59]; } return t21; } // src/components/GroupNode.tsx init_group_store(); import { c as _c73 } from "react/compiler-runtime"; import React43 from "react"; import { useAtomValue as useAtomValue47, useSetAtom as useSetAtom35 } from "jotai"; import { jsx as _jsx44, jsxs as _jsxs26, Fragment as _Fragment9 } from "react/jsx-runtime"; function GroupNode(t0) { const $ = _c73(42); const { node, isSelected, renderHeader, className, style } = t0; const collapsed = useAtomValue47(collapsedGroupsAtom); const toggleCollapse = useSetAtom35(toggleGroupCollapseAtom); const getChildCount = useAtomValue47(groupChildCountAtom); let childCount; let isCollapsed; let t1; let t2; let t3; let t4; let t5; let t6; let t7; if ($[0] !== className || $[1] !== collapsed || $[2] !== getChildCount || $[3] !== isSelected || $[4] !== node || $[5] !== renderHeader || $[6] !== style || $[7] !== toggleCollapse) { isCollapsed = collapsed.has(node.id); childCount = getChildCount(node.id); let t82; if ($[17] !== node.id || $[18] !== toggleCollapse) { t82 = (e) => { e.stopPropagation(); toggleCollapse(node.id); }; $[17] = node.id; $[18] = toggleCollapse; $[19] = t82; } else { t82 = $[19]; } const handleToggle = t82; t6 = className; const t92 = `2px ${isSelected ? "solid" : "dashed"} ${isSelected ? "#4A90D9" : "#666"}`; const t102 = isCollapsed ? "rgba(100, 100, 100, 0.15)" : "rgba(100, 100, 100, 0.05)"; if ($[20] !== style || $[21] !== t102 || $[22] !== t92) { t7 = { width: "100%", height: "100%", border: t92, borderRadius: "8px", background: t102, display: "flex", flexDirection: "column", overflow: "hidden", ...style }; $[20] = style; $[21] = t102; $[22] = t92; $[23] = t7; } else { t7 = $[23]; } if ($[24] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t1 = { display: "flex", alignItems: "center", gap: "6px", padding: "6px 10px", background: "rgba(100, 100, 100, 0.2)", borderBottom: "1px solid rgba(100, 100, 100, 0.2)", cursor: "pointer", userSelect: "none", fontSize: "13px", color: "#ccc" }; $[24] = t1; } else { t1 = $[24]; } t2 = handleToggle; t3 = "true"; let t11; if ($[25] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t11 = { fontSize: "10px", width: "14px", textAlign: "center" }; $[25] = t11; } else { t11 = $[25]; } const t12 = isCollapsed ? "\u25B6" : "\u25BC"; if ($[26] !== t12) { t4 = /* @__PURE__ */ _jsx44("span", { style: t11, children: t12 }); $[26] = t12; $[27] = t4; } else { t4 = $[27]; } t5 = renderHeader ? renderHeader({ node, isCollapsed, childCount }) : /* @__PURE__ */ _jsxs26(_Fragment9, { children: [/* @__PURE__ */ _jsx44("span", { style: { fontWeight: 500 }, children: node.label || "Group" }), /* @__PURE__ */ _jsxs26("span", { style: { opacity: 0.6, marginLeft: "auto" }, children: [childCount, " node", childCount !== 1 ? "s" : ""] })] }); $[0] = className; $[1] = collapsed; $[2] = getChildCount; $[3] = isSelected; $[4] = node; $[5] = renderHeader; $[6] = style; $[7] = toggleCollapse; $[8] = childCount; $[9] = isCollapsed; $[10] = t1; $[11] = t2; $[12] = t3; $[13] = t4; $[14] = t5; $[15] = t6; $[16] = t7; } else { childCount = $[8]; isCollapsed = $[9]; t1 = $[10]; t2 = $[11]; t3 = $[12]; t4 = $[13]; t5 = $[14]; t6 = $[15]; t7 = $[16]; } let t8; if ($[28] !== t1 || $[29] !== t2 || $[30] !== t3 || $[31] !== t4 || $[32] !== t5) { t8 = /* @__PURE__ */ _jsxs26("div", { style: t1, onClick: t2, "data-no-drag": t3, children: [t4, t5] }); $[28] = t1; $[29] = t2; $[30] = t3; $[31] = t4; $[32] = t5; $[33] = t8; } else { t8 = $[33]; } let t9; if ($[34] !== childCount || $[35] !== isCollapsed) { t9 = isCollapsed && /* @__PURE__ */ _jsxs26("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "#888", fontSize: "12px", fontStyle: "italic" }, children: [childCount, " collapsed node", childCount !== 1 ? "s" : ""] }); $[34] = childCount; $[35] = isCollapsed; $[36] = t9; } else { t9 = $[36]; } let t10; if ($[37] !== t6 || $[38] !== t7 || $[39] !== t8 || $[40] !== t9) { t10 = /* @__PURE__ */ _jsxs26("div", { className: t6, style: t7, children: [t8, t9] }); $[37] = t6; $[38] = t7; $[39] = t8; $[40] = t9; $[41] = t10; } else { t10 = $[41]; } return t10; } // src/components/CanvasAnimations.tsx import { c as _c74 } from "react/compiler-runtime"; import React44 from "react"; import { jsx as _jsx45 } from "react/jsx-runtime"; var CANVAS_ANIMATION_STYLES = ` @keyframes canvas-search-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.5); } 50% { box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.3); } } .canvas-search-highlight { animation: canvas-search-pulse 1.5s ease-in-out infinite; } @keyframes canvas-edge-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes canvas-edge-fade-out { from { opacity: 1; } to { opacity: 0; } } .canvas-edge-enter { animation: canvas-edge-fade-in 300ms ease-out forwards; } .canvas-edge-exit { animation: canvas-edge-fade-out 300ms ease-out forwards; pointer-events: none; } @keyframes canvas-drop-target-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5); } 50% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.3); } } .canvas-drop-target { animation: canvas-drop-target-pulse 1s ease-in-out infinite; outline: 2px dashed #3b82f6; outline-offset: 2px; } @keyframes canvas-tap-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } } .canvas-tap-pulse { animation: canvas-tap-pulse 250ms ease-out; } /* iPad / touch: hide cursor indicators that aren't useful on coarse pointers */ @media (pointer: coarse) { [data-canvas-root], [data-canvas-root] * { cursor: default !important; } } /* Respect prefers-reduced-motion: disable canvas CSS animations */ @media (prefers-reduced-motion: reduce) { .canvas-search-highlight, .canvas-drop-target, .canvas-tap-pulse { animation: none !important; } .canvas-edge-enter { animation: none !important; opacity: 1; } .canvas-edge-exit { animation: none !important; opacity: 0; } } `; function CanvasAnimations() { const $ = _c74(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsx45("style", { dangerouslySetInnerHTML: { __html: CANVAS_ANIMATION_STYLES } }); $[0] = t0; } else { t0 = $[0]; } return t0; } // src/components/TouchActionButton/index.tsx init_input_store(); init_external_keyboard_store(); init_selection_store(); init_graph_derived(); init_graph_mutations(); init_clipboard_store(); init_history_store(); import { c as _c77 } from "react/compiler-runtime"; import React47, { useState as useState11, useRef as useRef30 } from "react"; import { useAtomValue as useAtomValue48, useSetAtom as useSetAtom36 } from "jotai"; // src/components/TouchActionButton/icons.tsx import { c as _c75 } from "react/compiler-runtime"; import React45 from "react"; import { jsx as _jsx46, jsxs as _jsxs27 } from "react/jsx-runtime"; function PlusIcon2() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: [/* @__PURE__ */ _jsx46("line", { x1: "12", y1: "6", x2: "12", y2: "18" }), /* @__PURE__ */ _jsx46("line", { x1: "6", y1: "12", x2: "18", y2: "12" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function MergeIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [/* @__PURE__ */ _jsx46("path", { d: "M6 4 L10 10 L6 16" }), /* @__PURE__ */ _jsx46("path", { d: "M14 4 L10 10 L14 16" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function DuplicateIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [/* @__PURE__ */ _jsx46("rect", { x: "3", y: "5", width: "10", height: "10", rx: "2" }), /* @__PURE__ */ _jsx46("rect", { x: "7", y: "5", width: "10", height: "10", rx: "2" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function SelectAllIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [/* @__PURE__ */ _jsx46("rect", { x: "3", y: "3", width: "14", height: "14", rx: "2", strokeDasharray: "3 2" }), /* @__PURE__ */ _jsx46("polyline", { points: "7 10 9 12 13 8" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function SearchIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [/* @__PURE__ */ _jsx46("circle", { cx: "9", cy: "9", r: "5" }), /* @__PURE__ */ _jsx46("line", { x1: "13", y1: "13", x2: "17", y2: "17" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function UndoIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [/* @__PURE__ */ _jsx46("polyline", { points: "5 9 2 6 5 3" }), /* @__PURE__ */ _jsx46("path", { d: "M2 6h10a5 5 0 0 1 0 10H8" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } function RedoIcon() { const $ = _c75(1); let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = /* @__PURE__ */ _jsxs27("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [/* @__PURE__ */ _jsx46("polyline", { points: "15 9 18 6 15 3" }), /* @__PURE__ */ _jsx46("path", { d: "M18 6H8a5 5 0 0 0 0 10h4" })] }); $[0] = t0; } else { t0 = $[0]; } return t0; } // src/components/TouchActionButton/RadialMenu.tsx import { c as _c76 } from "react/compiler-runtime"; import React46 from "react"; import { jsx as _jsx47, jsxs as _jsxs28 } from "react/jsx-runtime"; var FAB_SIZE = 56; var ITEM_SIZE = 48; var STAGGER_MS = 35; function getRadialPositions(count, radius, position) { let startAngle; let endAngle; switch (position) { case "bottom-left": startAngle = -20; endAngle = -160; break; case "bottom-right": startAngle = -160; endAngle = -20; break; case "top-left": startAngle = 20; endAngle = 160; break; case "top-right": startAngle = 160; endAngle = 20; break; default: startAngle = -20; endAngle = -160; } const positions = []; for (let i = 0; i < count; i++) { const t = count === 1 ? 0.5 : i / (count - 1); const angleDeg = startAngle + (endAngle - startAngle) * t; const angleRad = angleDeg * Math.PI / 180; positions.push({ dx: Math.cos(angleRad) * radius, dy: Math.sin(angleRad) * radius, angle: angleDeg }); } return positions; } function getPositionStyles2(position) { switch (position) { case "bottom-left": return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", left: "max(16px, env(safe-area-inset-left, 16px))" }; case "bottom-right": return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", right: "max(16px, env(safe-area-inset-right, 16px))" }; case "top-left": return { top: "max(16px, env(safe-area-inset-top, 16px))", left: "max(16px, env(safe-area-inset-left, 16px))" }; case "top-right": return { top: "max(16px, env(safe-area-inset-top, 16px))", right: "max(16px, env(safe-area-inset-right, 16px))" }; default: return { bottom: "max(16px, env(safe-area-inset-bottom, 16px))", left: "max(16px, env(safe-area-inset-left, 16px))" }; } } function RadialMenuItem(t0) { const $ = _c76(31); const { item, position: pos, index: i, totalItems, isOpen, isHighlighted, onTap } = t0; const deployed = isOpen; const t1 = deployed ? `translate(${pos.dx}px, ${pos.dy}px) scale(1)` : "translate(0, 0) scale(0)"; const t2 = deployed ? item.disabled ? 0.35 : 1 : 0; const t3 = deployed ? `transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1) ${i * STAGGER_MS}ms, opacity 200ms ease ${i * STAGGER_MS}ms` : `transform 200ms cubic-bezier(0.55, 0, 1, 0.45) ${(totalItems - 1 - i) * 20}ms, opacity 150ms ease ${(totalItems - 1 - i) * 20}ms`; const t4 = deployed ? "auto" : "none"; let t5; if ($[0] !== t1 || $[1] !== t2 || $[2] !== t3 || $[3] !== t4) { t5 = { position: "absolute", left: FAB_SIZE / 2 - ITEM_SIZE / 2, top: FAB_SIZE / 2 - ITEM_SIZE / 2, transform: t1, opacity: t2, transition: t3, pointerEvents: t4, willChange: "transform, opacity" }; $[0] = t1; $[1] = t2; $[2] = t3; $[3] = t4; $[4] = t5; } else { t5 = $[4]; } let t6; if ($[5] !== item || $[6] !== onTap) { t6 = () => onTap(item); $[5] = item; $[6] = onTap; $[7] = t6; } else { t6 = $[7]; } const t7 = `1.5px solid ${isHighlighted ? "rgba(99,160,255,0.5)" : "var(--canvas-control-border, rgba(255,255,255,0.12))"}`; const t8 = isHighlighted ? "var(--canvas-control-active-bg, rgba(59,130,246,0.9))" : "var(--canvas-control-bg, rgba(30,30,30,0.88))"; const t9 = item.disabled ? "default" : "pointer"; const t10 = isHighlighted ? "scale(1.18)" : "scale(1)"; const t11 = isHighlighted ? "0 0 0 3px rgba(59,130,246,0.25), 0 4px 16px rgba(59,130,246,0.35)" : "0 2px 8px rgba(0,0,0,0.35)"; let t12; if ($[8] !== t10 || $[9] !== t11 || $[10] !== t7 || $[11] !== t8 || $[12] !== t9) { t12 = { width: ITEM_SIZE, height: ITEM_SIZE, borderRadius: "50%", border: t7, backgroundColor: t8, color: "var(--canvas-control-fg, rgba(255,255,255,0.9))", display: "flex", alignItems: "center", justifyContent: "center", cursor: t9, touchAction: "manipulation", WebkitTapHighlightColor: "transparent", userSelect: "none", backdropFilter: "blur(16px) saturate(1.5)", WebkitBackdropFilter: "blur(16px) saturate(1.5)", padding: 0, transition: "background-color 180ms ease, border-color 180ms ease, transform 180ms cubic-bezier(0.34, 1.2, 0.64, 1), box-shadow 250ms ease", transform: t10, boxShadow: t11 }; $[8] = t10; $[9] = t11; $[10] = t7; $[11] = t8; $[12] = t9; $[13] = t12; } else { t12 = $[13]; } let t13; if ($[14] !== item.disabled || $[15] !== item.icon || $[16] !== item.label || $[17] !== t12 || $[18] !== t6) { t13 = /* @__PURE__ */ _jsx47("button", { onPointerUp: t6, disabled: item.disabled, "aria-label": item.label, "data-no-drag": "true", style: t12, children: item.icon }); $[14] = item.disabled; $[15] = item.icon; $[16] = item.label; $[17] = t12; $[18] = t6; $[19] = t13; } else { t13 = $[19]; } const t14 = isHighlighted ? 1 : 0; const t15 = isHighlighted ? "translateX(-50%) translateY(0)" : "translateX(-50%) translateY(4px)"; let t16; if ($[20] !== t14 || $[21] !== t15) { t16 = { position: "absolute", top: -24, left: "50%", fontSize: "11px", fontWeight: 600, letterSpacing: "0.02em", color: "var(--canvas-control-fg, rgba(255,255,255,0.95))", backgroundColor: "var(--canvas-control-bg, rgba(20,20,20,0.92))", backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)", padding: "3px 9px", borderRadius: "7px", whiteSpace: "nowrap", pointerEvents: "none", opacity: t14, transform: t15, transition: "opacity 150ms ease, transform 150ms ease" }; $[20] = t14; $[21] = t15; $[22] = t16; } else { t16 = $[22]; } let t17; if ($[23] !== item.label || $[24] !== t16) { t17 = /* @__PURE__ */ _jsx47("span", { style: t16, children: item.label }); $[23] = item.label; $[24] = t16; $[25] = t17; } else { t17 = $[25]; } let t18; if ($[26] !== item.id || $[27] !== t13 || $[28] !== t17 || $[29] !== t5) { t18 = /* @__PURE__ */ _jsxs28("div", { style: t5, children: [t13, t17] }, item.id); $[26] = item.id; $[27] = t13; $[28] = t17; $[29] = t5; $[30] = t18; } else { t18 = $[30]; } return t18; } // src/components/TouchActionButton/index.tsx import { jsx as _jsx48, jsxs as _jsxs29 } from "react/jsx-runtime"; function TouchActionButton(t0) { const $ = _c77(95); const { position: t1, alwaysVisible: t2, className, style, holdDelay: t3, radius: t4 } = t0; const position = t1 === void 0 ? "bottom-left" : t1; const alwaysVisible = t2 === void 0 ? false : t2; const holdDelay = t3 === void 0 ? 250 : t3; const radius = t4 === void 0 ? 80 : t4; const isTouchDevice = useAtomValue48(isTouchDeviceAtom); const hasExternalKeyboard = useAtomValue48(hasExternalKeyboardAtom); const [isOpen, setIsOpen] = useState11(false); const [highlightedId, setHighlightedId] = useState11(null); const selectedNodeIds = useAtomValue48(selectedNodeIdsAtom); const nodeKeys = useAtomValue48(nodeKeysAtom); const mergeNodes = useSetAtom36(mergeNodesAtom); const duplicateSelection = useSetAtom36(duplicateSelectionAtom); const clearSelection = useSetAtom36(clearSelectionAtom); const addNodesToSelection = useSetAtom36(addNodesToSelectionAtom); const openCommandLine = useSetAtom36(openCommandLineAtom); const undo = useSetAtom36(undoAtom); const redo = useSetAtom36(redoAtom); const canUndo = useAtomValue48(canUndoAtom); const canRedo = useAtomValue48(canRedoAtom); const holdTimerRef = useRef30(null); const containerRef = useRef30(null); const fabCenterRef = useRef30(null); const didOpenRef = useRef30(false); if (!alwaysVisible && (!isTouchDevice || hasExternalKeyboard)) { return null; } const hasSelection = selectedNodeIds.size > 0; const canMerge = selectedNodeIds.size >= 2; let t5; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t5 = /* @__PURE__ */ _jsx48(UndoIcon, {}); $[0] = t5; } else { t5 = $[0]; } const t6 = !canUndo; let t7; if ($[1] !== undo) { t7 = () => undo(); $[1] = undo; $[2] = t7; } else { t7 = $[2]; } let t8; if ($[3] !== t6 || $[4] !== t7) { t8 = { id: "undo", label: "Undo", icon: t5, disabled: t6, action: t7 }; $[3] = t6; $[4] = t7; $[5] = t8; } else { t8 = $[5]; } let t9; if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t9 = /* @__PURE__ */ _jsx48(RedoIcon, {}); $[6] = t9; } else { t9 = $[6]; } const t10 = !canRedo; let t11; if ($[7] !== redo) { t11 = () => redo(); $[7] = redo; $[8] = t11; } else { t11 = $[8]; } let t12; if ($[9] !== t10 || $[10] !== t11) { t12 = { id: "redo", label: "Redo", icon: t9, disabled: t10, action: t11 }; $[9] = t10; $[10] = t11; $[11] = t12; } else { t12 = $[11]; } let t13; if ($[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t13 = /* @__PURE__ */ _jsx48(MergeIcon, {}); $[12] = t13; } else { t13 = $[12]; } const t14 = !canMerge; let t15; if ($[13] !== canMerge || $[14] !== mergeNodes || $[15] !== selectedNodeIds) { t15 = () => { if (canMerge) { mergeNodes({ nodeIds: Array.from(selectedNodeIds) }); } }; $[13] = canMerge; $[14] = mergeNodes; $[15] = selectedNodeIds; $[16] = t15; } else { t15 = $[16]; } let t16; if ($[17] !== t14 || $[18] !== t15) { t16 = { id: "merge", label: "Merge", icon: t13, disabled: t14, action: t15 }; $[17] = t14; $[18] = t15; $[19] = t16; } else { t16 = $[19]; } let t17; if ($[20] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t17 = /* @__PURE__ */ _jsx48(DuplicateIcon, {}); $[20] = t17; } else { t17 = $[20]; } const t18 = !hasSelection; let t19; if ($[21] !== duplicateSelection || $[22] !== hasSelection) { t19 = () => { if (hasSelection) { duplicateSelection(); } }; $[21] = duplicateSelection; $[22] = hasSelection; $[23] = t19; } else { t19 = $[23]; } let t20; if ($[24] !== t18 || $[25] !== t19) { t20 = { id: "duplicate", label: "Duplicate", icon: t17, disabled: t18, action: t19 }; $[24] = t18; $[25] = t19; $[26] = t20; } else { t20 = $[26]; } let t21; if ($[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t21 = /* @__PURE__ */ _jsx48(SelectAllIcon, {}); $[27] = t21; } else { t21 = $[27]; } let t22; if ($[28] !== addNodesToSelection || $[29] !== clearSelection || $[30] !== nodeKeys) { t22 = { id: "select-all", label: "Select All", icon: t21, action: () => { clearSelection(); addNodesToSelection(nodeKeys); } }; $[28] = addNodesToSelection; $[29] = clearSelection; $[30] = nodeKeys; $[31] = t22; } else { t22 = $[31]; } let t23; if ($[32] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t23 = /* @__PURE__ */ _jsx48(SearchIcon, {}); $[32] = t23; } else { t23 = $[32]; } let t24; if ($[33] !== openCommandLine) { t24 = { id: "search", label: "Search", icon: t23, action: () => openCommandLine() }; $[33] = openCommandLine; $[34] = t24; } else { t24 = $[34]; } let t25; if ($[35] !== t12 || $[36] !== t16 || $[37] !== t20 || $[38] !== t22 || $[39] !== t24 || $[40] !== t8) { t25 = [t8, t12, t16, t20, t22, t24]; $[35] = t12; $[36] = t16; $[37] = t20; $[38] = t22; $[39] = t24; $[40] = t8; $[41] = t25; } else { t25 = $[41]; } const actions = t25; let t26; if ($[42] !== actions.length || $[43] !== position || $[44] !== radius) { t26 = getRadialPositions(actions.length, radius, position); $[42] = actions.length; $[43] = position; $[44] = radius; $[45] = t26; } else { t26 = $[45]; } const itemPositions = t26; let t27; if ($[46] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t27 = () => { if (holdTimerRef.current) { clearTimeout(holdTimerRef.current); holdTimerRef.current = null; } }; $[46] = t27; } else { t27 = $[46]; } const clearHoldTimer = t27; let t28; if ($[47] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t28 = () => { const container = containerRef.current; if (!container) { return null; } const fab = container.querySelector("[data-fab-button]"); if (!fab) { return null; } const rect = fab.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; }; $[47] = t28; } else { t28 = $[47]; } const getFabCenter = t28; let t29; if ($[48] !== actions || $[49] !== itemPositions) { t29 = (clientX, clientY) => { const center = fabCenterRef.current; if (!center) { return null; } let closest = null; let closestDist = Infinity; const hitRadius = ITEM_SIZE * 0.75; for (let i = 0; i < actions.length; i++) { const pos = itemPositions[i]; const itemX = center.x + pos.dx; const itemY = center.y + pos.dy; const dist = Math.hypot(clientX - itemX, clientY - itemY); if (dist < hitRadius && dist < closestDist && !actions[i].disabled) { closest = actions[i].id; closestDist = dist; } } return closest; }; $[48] = actions; $[49] = itemPositions; $[50] = t29; } else { t29 = $[50]; } const findClosestItem = t29; let t30; if ($[51] !== holdDelay) { t30 = (e) => { e.target.setPointerCapture(e.pointerId); fabCenterRef.current = getFabCenter(); didOpenRef.current = false; holdTimerRef.current = setTimeout(() => { setIsOpen(true); didOpenRef.current = true; holdTimerRef.current = null; }, holdDelay); }; $[51] = holdDelay; $[52] = t30; } else { t30 = $[52]; } const handlePointerDown = t30; let t31; if ($[53] !== findClosestItem) { t31 = (e_0) => { if (!didOpenRef.current) { return; } setHighlightedId(findClosestItem(e_0.clientX, e_0.clientY)); }; $[53] = findClosestItem; $[54] = t31; } else { t31 = $[54]; } const handlePointerMove = t31; let t32; if ($[55] !== actions || $[56] !== findClosestItem) { t32 = (e_1) => { clearHoldTimer(); if (didOpenRef.current) { const id = findClosestItem(e_1.clientX, e_1.clientY); if (id) { const item = actions.find((a) => a.id === id); if (item && !item.disabled) { item.action(); } } setIsOpen(false); setHighlightedId(null); didOpenRef.current = false; } else { clearHoldTimer(); setIsOpen((o) => { if (o) { setHighlightedId(null); return false; } didOpenRef.current = true; fabCenterRef.current = getFabCenter(); return true; }); } }; $[55] = actions; $[56] = findClosestItem; $[57] = t32; } else { t32 = $[57]; } const handlePointerUp = t32; let t33; if ($[58] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t33 = () => { clearHoldTimer(); setIsOpen(false); setHighlightedId(null); didOpenRef.current = false; }; $[58] = t33; } else { t33 = $[58]; } const handlePointerCancel = t33; let t34; if ($[59] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t34 = (item_0) => { if (item_0.disabled) { return; } item_0.action(); setIsOpen(false); setHighlightedId(null); didOpenRef.current = false; }; $[59] = t34; } else { t34 = $[59]; } const handleItemTap = t34; let t35; if ($[60] !== position) { t35 = getPositionStyles2(position); $[60] = position; $[61] = t35; } else { t35 = $[61]; } const positionStyles = t35; let t36; if ($[62] !== positionStyles || $[63] !== style) { t36 = { position: "absolute", ...positionStyles, zIndex: 100, pointerEvents: "auto", ...style }; $[62] = positionStyles; $[63] = style; $[64] = t36; } else { t36 = $[64]; } let t37; if ($[65] !== actions || $[66] !== highlightedId || $[67] !== isOpen || $[68] !== itemPositions) { t37 = actions.map((item_1, i_0) => /* @__PURE__ */ _jsx48(RadialMenuItem, { item: item_1, position: itemPositions[i_0], index: i_0, totalItems: actions.length, isOpen, isHighlighted: highlightedId === item_1.id, onTap: handleItemTap }, item_1.id)); $[65] = actions; $[66] = highlightedId; $[67] = isOpen; $[68] = itemPositions; $[69] = t37; } else { t37 = $[69]; } const t38 = radius * 2 + ITEM_SIZE; const t39 = radius * 2 + ITEM_SIZE; const t40 = isOpen ? "translate(-50%, -50%) scale(1)" : "translate(-50%, -50%) scale(0)"; const t41 = isOpen ? "transform 400ms cubic-bezier(0.34, 1.3, 0.64, 1), opacity 300ms ease" : "transform 250ms ease, opacity 150ms ease"; const t42 = isOpen ? 1 : 0; let t43; if ($[70] !== t38 || $[71] !== t39 || $[72] !== t40 || $[73] !== t41 || $[74] !== t42) { t43 = /* @__PURE__ */ _jsx48("div", { style: { position: "absolute", left: FAB_SIZE / 2, top: FAB_SIZE / 2, width: t38, height: t39, borderRadius: "50%", transform: t40, background: "radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%)", transition: t41, opacity: t42, pointerEvents: "none" } }); $[70] = t38; $[71] = t39; $[72] = t40; $[73] = t41; $[74] = t42; $[75] = t43; } else { t43 = $[75]; } const t44 = isOpen ? "Close actions" : "Hold for actions"; const t45 = `1.5px solid ${isOpen ? "rgba(99,160,255,0.4)" : "var(--canvas-control-border, rgba(255,255,255,0.15))"}`; const t46 = isOpen ? "var(--canvas-control-active-bg, rgba(59,130,246,0.9))" : "var(--canvas-control-bg, rgba(30,30,30,0.88))"; const t47 = isOpen ? "rotate(45deg) scale(1.05)" : "rotate(0deg) scale(1)"; const t48 = isOpen ? "0 0 0 4px rgba(59,130,246,0.15), 0 6px 20px rgba(0,0,0,0.35)" : "0 4px 12px rgba(0,0,0,0.3)"; let t49; if ($[76] !== t45 || $[77] !== t46 || $[78] !== t47 || $[79] !== t48) { t49 = { position: "relative", width: FAB_SIZE, height: FAB_SIZE, borderRadius: "50%", border: t45, backgroundColor: t46, color: "var(--canvas-control-fg, rgba(255,255,255,0.95))", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", touchAction: "none", WebkitTapHighlightColor: "transparent", userSelect: "none", backdropFilter: "blur(16px) saturate(1.5)", WebkitBackdropFilter: "blur(16px) saturate(1.5)", padding: 0, transition: "background-color 250ms ease, border-color 250ms ease, transform 350ms cubic-bezier(0.34, 1.3, 0.64, 1), box-shadow 300ms ease", transform: t47, boxShadow: t48 }; $[76] = t45; $[77] = t46; $[78] = t47; $[79] = t48; $[80] = t49; } else { t49 = $[80]; } let t50; if ($[81] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t50 = /* @__PURE__ */ _jsx48(PlusIcon2, {}); $[81] = t50; } else { t50 = $[81]; } let t51; if ($[82] !== handlePointerDown || $[83] !== handlePointerMove || $[84] !== handlePointerUp || $[85] !== isOpen || $[86] !== t44 || $[87] !== t49) { t51 = /* @__PURE__ */ _jsx48("button", { "data-fab-button": "true", onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, "aria-label": t44, "aria-expanded": isOpen, "data-no-drag": "true", style: t49, children: t50 }); $[82] = handlePointerDown; $[83] = handlePointerMove; $[84] = handlePointerUp; $[85] = isOpen; $[86] = t44; $[87] = t49; $[88] = t51; } else { t51 = $[88]; } let t52; if ($[89] !== className || $[90] !== t36 || $[91] !== t37 || $[92] !== t43 || $[93] !== t51) { t52 = /* @__PURE__ */ _jsxs29("div", { ref: containerRef, className, "data-no-drag": "true", style: t36, children: [t37, t43, t51] }); $[89] = className; $[90] = t36; $[91] = t37; $[92] = t43; $[93] = t51; $[94] = t52; } else { t52 = $[94]; } return t52; } // src/components/CanvasToast.tsx init_toast_store(); import { c as _c78 } from "react/compiler-runtime"; import React48 from "react"; import { useAtomValue as useAtomValue49 } from "jotai"; import { jsx as _jsx49 } from "react/jsx-runtime"; function CanvasToast(t0) { const $ = _c78(12); const { className, style } = t0; const toast = useAtomValue49(canvasToastAtom); let t1; if ($[0] !== style) { t1 = { position: "absolute", bottom: "24px", left: "50%", transform: "translateX(-50%)", zIndex: 200, pointerEvents: "none", ...style }; $[0] = style; $[1] = t1; } else { t1 = $[1]; } const t2 = toast ? 1 : 0; const t3 = toast ? "translateY(0)" : "translateY(8px)"; let t4; if ($[2] !== t2 || $[3] !== t3) { t4 = { fontSize: "13px", fontWeight: 500, color: "var(--canvas-control-fg, rgba(255,255,255,0.95))", backgroundColor: "var(--canvas-control-bg, rgba(30,30,30,0.92))", backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)", border: "1px solid var(--canvas-control-border, rgba(255,255,255,0.1))", padding: "6px 16px", borderRadius: "8px", whiteSpace: "nowrap", opacity: t2, transform: t3, transition: "opacity 200ms ease, transform 200ms ease" }; $[2] = t2; $[3] = t3; $[4] = t4; } else { t4 = $[4]; } const t5 = toast?.message ?? ""; let t6; if ($[5] !== t4 || $[6] !== t5) { t6 = /* @__PURE__ */ _jsx49("div", { style: t4, children: t5 }); $[5] = t4; $[6] = t5; $[7] = t6; } else { t6 = $[7]; } let t7; if ($[8] !== className || $[9] !== t1 || $[10] !== t6) { t7 = /* @__PURE__ */ _jsx49("div", { className, style: t1, children: t6 }); $[8] = className; $[9] = t1; $[10] = t6; $[11] = t7; } else { t7 = $[11]; } return t7; } // src/components/AlignmentGuides.tsx init_snap_store(); init_viewport_store(); import { c as _c79 } from "react/compiler-runtime"; import React49 from "react"; import { useAtomValue as useAtomValue50 } from "jotai"; import { jsx as _jsx50, jsxs as _jsxs30 } from "react/jsx-runtime"; var GUIDE_COLOR = "rgba(99, 160, 255, 0.6)"; function AlignmentGuides() { const $ = _c79(22); const guides = useAtomValue50(alignmentGuidesAtom); const pan = useAtomValue50(panAtom); const zoom = useAtomValue50(zoomAtom); const viewportRect = useAtomValue50(viewportRectAtom); const hasGuides = guides.verticalGuides.length > 0 || guides.horizontalGuides.length > 0; if (!hasGuides || !viewportRect) { return null; } const worldMinX = -pan.x / zoom; const worldMinY = -pan.y / zoom; const worldMaxX = worldMinX + viewportRect.width / zoom; const worldMaxY = worldMinY + viewportRect.height / zoom; let t0; if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) { t0 = { position: "absolute", left: 0, top: 0, width: "100%", height: "100%", pointerEvents: "none", overflow: "visible", zIndex: 999 }; $[0] = t0; } else { t0 = $[0]; } let t1; if ($[1] !== guides.verticalGuides || $[2] !== worldMaxY || $[3] !== worldMinY || $[4] !== zoom) { let t22; if ($[6] !== worldMaxY || $[7] !== worldMinY || $[8] !== zoom) { t22 = (x, i) => /* @__PURE__ */ _jsx50("line", { x1: x, y1: worldMinY, x2: x, y2: worldMaxY, stroke: GUIDE_COLOR, strokeWidth: 1 / zoom, strokeDasharray: `${4 / zoom} ${4 / zoom}` }, `v-${i}`); $[6] = worldMaxY; $[7] = worldMinY; $[8] = zoom; $[9] = t22; } else { t22 = $[9]; } t1 = guides.verticalGuides.map(t22); $[1] = guides.verticalGuides; $[2] = worldMaxY; $[3] = worldMinY; $[4] = zoom; $[5] = t1; } else { t1 = $[5]; } let t2; if ($[10] !== guides.horizontalGuides || $[11] !== worldMaxX || $[12] !== worldMinX || $[13] !== zoom) { let t32; if ($[15] !== worldMaxX || $[16] !== worldMinX || $[17] !== zoom) { t32 = (y, i_0) => /* @__PURE__ */ _jsx50("line", { x1: worldMinX, y1: y, x2: worldMaxX, y2: y, stroke: GUIDE_COLOR, strokeWidth: 1 / zoom, strokeDasharray: `${4 / zoom} ${4 / zoom}` }, `h-${i_0}`); $[15] = worldMaxX; $[16] = worldMinX; $[17] = zoom; $[18] = t32; } else { t32 = $[18]; } t2 = guides.horizontalGuides.map(t32); $[10] = guides.horizontalGuides; $[11] = worldMaxX; $[12] = worldMinX; $[13] = zoom; $[14] = t2; } else { t2 = $[14]; } let t3; if ($[19] !== t1 || $[20] !== t2) { t3 = /* @__PURE__ */ _jsxs30("svg", { style: t0, children: [t1, t2] }); $[19] = t1; $[20] = t2; $[21] = t3; } else { t3 = $[21]; } return t3; } // src/nodes/NoteNode/NoteNode.tsx init_debug(); import React50, { useEffect as useEffect23, useRef as useRef31 } from "react"; import { useCreateBlockNote, useEditorChange } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/shadcn"; import { en } from "@blocknote/core/locales"; import "@blocknote/core/fonts/inter.css"; import "@blocknote/shadcn/style.css"; import { jsx as _jsx51 } from "react/jsx-runtime"; var debug22 = createDebug("note-node"); function NoteNode({ nodeData, storage, theme = "light", placeholder = "Start typing...", isResizing }) { const isSyncingFromStorage = useRef31(false); const isSyncingToStorage = useRef31(false); const lastStorageContent = useRef31(""); const editor = useCreateBlockNote({ initialContent: [{ id: crypto.randomUUID(), type: "paragraph", content: "" }], dictionary: { ...en, placeholders: { ...en.placeholders, emptyDocument: placeholder, default: placeholder, heading: "Enter a heading" } } }, []); useEditorChange(() => { if (isSyncingFromStorage.current) { return; } try { isSyncingToStorage.current = true; const html = editor.blocksToFullHTML(editor.document); if (html !== lastStorageContent.current) { lastStorageContent.current = html; storage.onChange(html); } } catch (err) { debug22.error("Failed to sync to storage: %O", err); } finally { isSyncingToStorage.current = false; } }, editor); useEffect23(() => { if (!editor) return; const handleStorageChange = (newContent) => { if (isSyncingToStorage.current) { return; } queueMicrotask(() => { if (newContent === lastStorageContent.current) return; lastStorageContent.current = newContent; if (newContent && newContent.trim()) { try { isSyncingFromStorage.current = true; const blocks = editor.tryParseHTMLToBlocks(newContent); editor.replaceBlocks(editor.document, blocks); } catch (err_0) { debug22.error("Failed to parse HTML from storage: %O", err_0); } finally { isSyncingFromStorage.current = false; } } }); }; const unsubscribe = storage.subscribe?.(handleStorageChange); if (storage.content && storage.content !== lastStorageContent.current) { handleStorageChange(storage.content); } return () => { unsubscribe?.(); }; }, [editor, storage]); if (storage.isLoading) { return /* @__PURE__ */ _jsx51("div", { className: "note-node note-node--loading", style: styles3.loading, children: "Loading..." }); } return /* @__PURE__ */ _jsx51("div", { className: "note-node", style: styles3.container, "data-no-drag": "true", onClick: (e) => { e.stopPropagation(); }, children: /* @__PURE__ */ _jsx51(BlockNoteView, { editor, theme }) }); } var styles3 = { container: { height: "100%", width: "100%", overflow: "auto", touchAction: "auto" }, loading: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%", color: "#666" } }; // src/commands/index.ts init_registry(); // src/commands/keyboard.ts var DEFAULT_SHORTCUTS = { openCommandLine: "/", closeCommandLine: "Escape", clearSelection: "Escape", copy: "ctrl+c", cut: "ctrl+x", paste: "ctrl+v", duplicate: "ctrl+d", selectAll: "ctrl+a", delete: "Delete", search: "ctrl+f", nextSearchResult: "Enter", prevSearchResult: "shift+Enter", nextSearchResultAlt: "ctrl+g", prevSearchResultAlt: "ctrl+shift+g", mergeNodes: "ctrl+m" }; function useGlobalKeyboard(_options) { } function useKeyState(_key) { return false; } // src/commands/executor.ts function collectInput(_get, _set, _inputDef, _collected) { return Promise.reject(new Error("Interactive input collection is not yet implemented. Pre-fill all inputs via initialInputs.")); } async function executeCommandInteractive(get, set, command, initialInputs) { const collected = { ...initialInputs }; for (let i = 0; i < command.inputs.length; i++) { const inputDef = command.inputs[i]; if (collected[inputDef.name] !== void 0) { continue; } if (inputDef.required === false && inputDef.default !== void 0) { collected[inputDef.name] = inputDef.default; continue; } set(commandLineStateAtom, { phase: "collecting", command, inputIndex: i, collected }); const value = await collectInput(get, set, inputDef, collected); collected[inputDef.name] = value; } set(commandLineStateAtom, { phase: "executing", command }); } function handlePickedPoint(set, point) { set(provideInputAtom2, point); } function handlePickedNode(set, node) { set(provideInputAtom2, node); } function cancelCommand(set) { set(closeCommandLineAtom); } // src/commands/CommandProvider.tsx init_graph_store(); init_selection_store(); init_viewport_store(); init_history_store(); import { c as _c80 } from "react/compiler-runtime"; import React51, { createContext as createContext4, useContext as useContext4, useEffect as useEffect24, useRef as useRef32 } from "react"; import { useAtomValue as useAtomValue51, useSetAtom as useSetAtom37, useAtom as useAtom9 } from "jotai"; import { useStore as useStore6 } from "jotai"; init_registry(); import { jsx as _jsx52 } from "react/jsx-runtime"; var CommandContextContext = /* @__PURE__ */ createContext4(null); function CommandProvider(t0) { const $ = _c80(52); const { children, onCreateNode, onUpdateNode, onDeleteNode, onCreateEdge, onDeleteEdge, onForceLayoutPersist } = t0; const store = useStore6(); const currentGraphId = useAtomValue51(currentGraphIdAtom); const selectedNodeIds = useAtomValue51(selectedNodeIdsAtom); const zoom = useAtomValue51(zoomAtom); const pan = useAtomValue51(panAtom); const undo = useSetAtom37(undoAtom); const redo = useSetAtom37(redoAtom); const { fitToBounds } = useFitToBounds(); let t1; if ($[0] !== onForceLayoutPersist) { t1 = onForceLayoutPersist ? async (updates) => { await onForceLayoutPersist(updates.map(_temp20)); } : void 0; $[0] = onForceLayoutPersist; $[1] = t1; } else { t1 = $[1]; } const persistCallback = t1; let t2; if ($[2] !== persistCallback) { t2 = { onPositionsChanged: persistCallback }; $[2] = persistCallback; $[3] = t2; } else { t2 = $[3]; } const { applyForceLayout } = useForceLayout(t2); let t3; if ($[4] !== persistCallback) { t3 = { onPositionsChanged: persistCallback }; $[4] = persistCallback; $[5] = t3; } else { t3 = $[5]; } const { applyLayout: applyTreeLayoutTopDown } = useTreeLayout(t3); let t4; if ($[6] !== persistCallback) { t4 = { direction: "left-right", onPositionsChanged: persistCallback }; $[6] = persistCallback; $[7] = t4; } else { t4 = $[7]; } const { applyLayout: applyTreeLayoutLeftRight } = useTreeLayout(t4); let t5; if ($[8] !== persistCallback) { t5 = { onPositionsChanged: persistCallback }; $[8] = persistCallback; $[9] = t5; } else { t5 = $[9]; } const { applyLayout: applyGridLayoutDefault } = useGridLayout(t5); const closeCommandLine = useSetAtom37(closeCommandLineAtom); const setCommandError = useSetAtom37(setCommandErrorAtom); let t6; if ($[10] !== applyForceLayout || $[11] !== applyGridLayoutDefault || $[12] !== applyTreeLayoutLeftRight || $[13] !== applyTreeLayoutTopDown || $[14] !== currentGraphId || $[15] !== fitToBounds || $[16] !== onCreateEdge || $[17] !== onCreateNode || $[18] !== onDeleteEdge || $[19] !== onDeleteNode || $[20] !== onUpdateNode || $[21] !== pan || $[22] !== redo || $[23] !== selectedNodeIds || $[24] !== store.get || $[25] !== store.set || $[26] !== undo || $[27] !== zoom) { t6 = () => ({ get: store.get, set: store.set, currentGraphId, selectedNodeIds, viewport: { zoom, pan }, mutations: { createNode: async (payload) => { if (!onCreateNode) { throw new Error("onCreateNode callback not provided to CommandProvider"); } return onCreateNode(payload); }, updateNode: async (nodeId, updates_0) => { if (!onUpdateNode) { throw new Error("onUpdateNode callback not provided to CommandProvider"); } return onUpdateNode(nodeId, updates_0); }, deleteNode: async (nodeId_0) => { if (!onDeleteNode) { throw new Error("onDeleteNode callback not provided to CommandProvider"); } return onDeleteNode(nodeId_0); }, createEdge: async (payload_0) => { if (!onCreateEdge) { throw new Error("onCreateEdge callback not provided to CommandProvider"); } return onCreateEdge(payload_0); }, deleteEdge: async (edgeId) => { if (!onDeleteEdge) { throw new Error("onDeleteEdge callback not provided to CommandProvider"); } return onDeleteEdge(edgeId); } }, layout: { fitToBounds: (mode, padding) => { const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection; fitToBounds(fitMode, padding); }, applyForceLayout, applyTreeLayout: async (opts) => { if (opts?.direction === "left-right") { await applyTreeLayoutLeftRight(); } else { await applyTreeLayoutTopDown(); } }, applyGridLayout: async () => { await applyGridLayoutDefault(); } }, history: { undo, redo } }); $[10] = applyForceLayout; $[11] = applyGridLayoutDefault; $[12] = applyTreeLayoutLeftRight; $[13] = applyTreeLayoutTopDown; $[14] = currentGraphId; $[15] = fitToBounds; $[16] = onCreateEdge; $[17] = onCreateNode; $[18] = onDeleteEdge; $[19] = onDeleteNode; $[20] = onUpdateNode; $[21] = pan; $[22] = redo; $[23] = selectedNodeIds; $[24] = store.get; $[25] = store.set; $[26] = undo; $[27] = zoom; $[28] = t6; } else { t6 = $[28]; } const getContext = t6; let t7; if ($[29] !== closeCommandLine || $[30] !== getContext || $[31] !== setCommandError) { t7 = async (commandName, inputs) => { const command = commandRegistry.get(commandName); if (!command) { throw new Error(`Unknown command: ${commandName}`); } for (const inputDef of command.inputs) { if (inputDef.required !== false && inputs[inputDef.name] === void 0) { throw new Error(`Missing required input: ${inputDef.name}`); } } const ctx = getContext(); ; try { await command.execute(inputs, ctx); closeCommandLine(); } catch (t82) { const error = t82; const message = error instanceof Error ? error.message : "Command execution failed"; setCommandError(message); throw error; } }; $[29] = closeCommandLine; $[30] = getContext; $[31] = setCommandError; $[32] = t7; } else { t7 = $[32]; } const executeCommand = t7; const hasCommand = _temp210; const [commandState, setCommandState] = useAtom9(commandLineStateAtom); const isExecutingRef = useRef32(false); let t8; if ($[33] !== commandState || $[34] !== getContext || $[35] !== setCommandError || $[36] !== setCommandState || $[37] !== store) { t8 = () => { if (commandState.phase === "executing" && !isExecutingRef.current) { const { command: command_0 } = commandState; if (command_0.inputs.length === 0) { isExecutingRef.current = true; const ctx_0 = getContext(); command_0.execute({}, ctx_0).then(() => { setCommandState({ phase: "searching", query: "", suggestions: commandRegistry.all() }); }).catch((error_0) => { const message_0 = error_0 instanceof Error ? error_0.message : "Command execution failed"; setCommandError(message_0); }).finally(() => { isExecutingRef.current = false; }); } return; } if (commandState.phase !== "collecting") { isExecutingRef.current = false; return; } const { command: command_1, inputIndex, collected } = commandState; const lastInputIndex = command_1.inputs.length - 1; const lastInput = command_1.inputs[lastInputIndex]; if (inputIndex === lastInputIndex && collected[lastInput.name] !== void 0 && !isExecutingRef.current) { isExecutingRef.current = true; setCommandState({ phase: "executing", command: command_1 }); const ctx_1 = getContext(); command_1.execute(collected, ctx_1).then(() => { setCommandState({ phase: "searching", query: "", suggestions: commandRegistry.all() }); store.set(inputModeAtom2, { type: "normal" }); store.set(commandFeedbackAtom, null); }).catch((error_1) => { const message_1 = error_1 instanceof Error ? error_1.message : "Command execution failed"; setCommandError(message_1); }).finally(() => { isExecutingRef.current = false; }); } }; $[33] = commandState; $[34] = getContext; $[35] = setCommandError; $[36] = setCommandState; $[37] = store; $[38] = t8; } else { t8 = $[38]; } let t9; if ($[39] !== closeCommandLine || $[40] !== commandState || $[41] !== getContext || $[42] !== setCommandError || $[43] !== setCommandState || $[44] !== store) { t9 = [commandState, getContext, closeCommandLine, setCommandError, setCommandState, store]; $[39] = closeCommandLine; $[40] = commandState; $[41] = getContext; $[42] = setCommandError; $[43] = setCommandState; $[44] = store; $[45] = t9; } else { t9 = $[45]; } useEffect24(t8, t9); let t10; if ($[46] !== executeCommand || $[47] !== getContext) { t10 = { executeCommand, getContext, hasCommand }; $[46] = executeCommand; $[47] = getContext; $[48] = t10; } else { t10 = $[48]; } const value = t10; let t11; if ($[49] !== children || $[50] !== value) { t11 = /* @__PURE__ */ _jsx52(CommandContextContext, { value, children }); $[49] = children; $[50] = value; $[51] = t11; } else { t11 = $[51]; } return t11; } function _temp210(name) { return commandRegistry.has(name); } function _temp20(u) { return { nodeId: u.nodeId, x: u.position.x, y: u.position.y }; } function useCommandContext() { const context = useContext4(CommandContextContext); if (!context) { throw new Error("useCommandContext must be used within a CommandProvider"); } return context; } function useExecuteCommand() { const { executeCommand } = useCommandContext(); return executeCommand; } // src/commands/builtins/viewport-commands.ts init_registry(); var fitToViewCommand = { name: "fitToView", description: "Fit all nodes in the viewport", aliases: ["fit", "fitAll"], category: "viewport", inputs: [], execute: async (_inputs, ctx) => { ctx.layout.fitToBounds("graph"); } }; var fitSelectionCommand = { name: "fitSelection", description: "Fit selected nodes in the viewport", aliases: ["fitSel"], category: "viewport", inputs: [], execute: async (_inputs, ctx) => { if (ctx.selectedNodeIds.size === 0) { throw new Error("No nodes selected"); } ctx.layout.fitToBounds("selection"); } }; var resetViewportCommand = { name: "resetViewport", description: "Reset zoom and pan to default", aliases: ["reset", "home"], category: "viewport", inputs: [], execute: async (_inputs, ctx) => { const { resetViewportAtom: resetViewportAtom2 } = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports)); ctx.set(resetViewportAtom2); } }; var zoomInCommand = { name: "zoomIn", description: "Zoom in on the canvas", aliases: ["+", "in"], category: "viewport", inputs: [], execute: async (_inputs, ctx) => { const { zoomAtom: zoomAtom2, setZoomAtom: setZoomAtom2 } = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports)); const currentZoom = ctx.get(zoomAtom2); ctx.set(setZoomAtom2, { zoom: Math.min(5, currentZoom * 1.25) }); } }; var zoomOutCommand = { name: "zoomOut", description: "Zoom out on the canvas", aliases: ["-", "out"], category: "viewport", inputs: [], execute: async (_inputs, ctx) => { const { zoomAtom: zoomAtom2, setZoomAtom: setZoomAtom2 } = await Promise.resolve().then(() => (init_viewport_store(), viewport_store_exports)); const currentZoom = ctx.get(zoomAtom2); ctx.set(setZoomAtom2, { zoom: Math.max(0.1, currentZoom / 1.25) }); } }; function registerViewportCommands() { registerCommand(fitToViewCommand); registerCommand(fitSelectionCommand); registerCommand(resetViewportCommand); registerCommand(zoomInCommand); registerCommand(zoomOutCommand); } // src/commands/builtins/selection-commands.ts init_registry(); var selectAllCommand = { name: "selectAll", description: "Select all nodes in the graph", aliases: ["all"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { nodeKeysAtom: nodeKeysAtom2, selectedNodeIdsAtom: selectedNodeIdsAtom2 } = await Promise.resolve().then(() => (init_core(), core_exports)); const nodeKeys = ctx.get(nodeKeysAtom2); ctx.set(selectedNodeIdsAtom2, new Set(nodeKeys)); } }; var clearSelectionCommand = { name: "clearSelection", description: "Clear all selection", aliases: ["deselect", "clear"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { clearSelectionAtom: clearSelectionAtom2 } = await Promise.resolve().then(() => (init_core(), core_exports)); ctx.set(clearSelectionAtom2); } }; var invertSelectionCommand = { name: "invertSelection", description: "Invert the current selection", aliases: ["invert"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { nodeKeysAtom: nodeKeysAtom2, selectedNodeIdsAtom: selectedNodeIdsAtom2 } = await Promise.resolve().then(() => (init_core(), core_exports)); const allNodeKeys = ctx.get(nodeKeysAtom2); const currentSelection = ctx.selectedNodeIds; const invertedSelection = allNodeKeys.filter((id) => !currentSelection.has(id)); ctx.set(selectedNodeIdsAtom2, new Set(invertedSelection)); } }; function registerSelectionCommands() { registerCommand(selectAllCommand); registerCommand(clearSelectionCommand); registerCommand(invertSelectionCommand); } // src/commands/builtins/history-commands.ts init_registry(); var undoCommand = { name: "undo", description: "Undo the last action", aliases: ["z"], category: "history", inputs: [], execute: async (_inputs, ctx) => { ctx.history.undo(); } }; var redoCommand = { name: "redo", description: "Redo the last undone action", aliases: ["y"], category: "history", inputs: [], execute: async (_inputs, ctx) => { ctx.history.redo(); } }; function registerHistoryCommands() { registerCommand(undoCommand); registerCommand(redoCommand); } // src/commands/builtins/layout-commands.ts init_registry(); var forceLayoutCommand = { name: "forceLayout", description: "Apply force-directed layout to all nodes", aliases: ["force", "autoLayout"], category: "layout", inputs: [], execute: async (_inputs, ctx) => { await ctx.layout.applyForceLayout(); } }; var treeLayoutCommand = { name: "treeLayout", description: "Arrange nodes in a hierarchical tree (top-down)", aliases: ["tree"], category: "layout", inputs: [], execute: async (_inputs, ctx) => { await ctx.layout.applyTreeLayout(); } }; var gridLayoutCommand = { name: "gridLayout", description: "Arrange nodes in a uniform grid", aliases: ["grid"], category: "layout", inputs: [], execute: async (_inputs, ctx) => { await ctx.layout.applyGridLayout(); } }; var horizontalLayoutCommand = { name: "horizontalLayout", description: "Arrange nodes in a horizontal tree (left-to-right)", aliases: ["horizontal", "hLayout"], category: "layout", inputs: [], execute: async (_inputs, ctx) => { await ctx.layout.applyTreeLayout({ direction: "left-right" }); } }; function registerLayoutCommands() { registerCommand(forceLayoutCommand); registerCommand(treeLayoutCommand); registerCommand(gridLayoutCommand); registerCommand(horizontalLayoutCommand); } // src/commands/builtins/clipboard-commands.ts init_registry(); var copyCommand = { name: "copy", description: "Copy selected nodes to clipboard", aliases: ["cp"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { copyToClipboardAtom: copyToClipboardAtom2, hasClipboardContentAtom: hasClipboardContentAtom2 } = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports)); if (ctx.selectedNodeIds.size === 0) { throw new Error("No nodes selected to copy"); } ctx.set(copyToClipboardAtom2, Array.from(ctx.selectedNodeIds)); const hasContent = ctx.get(hasClipboardContentAtom2); if (!hasContent) { throw new Error("Failed to copy nodes"); } } }; var cutCommand = { name: "cut", description: "Cut selected nodes (copy to clipboard)", aliases: ["x"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { copyToClipboardAtom: copyToClipboardAtom2, hasClipboardContentAtom: hasClipboardContentAtom2 } = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports)); if (ctx.selectedNodeIds.size === 0) { throw new Error("No nodes selected to cut"); } ctx.set(copyToClipboardAtom2, Array.from(ctx.selectedNodeIds)); const hasContent = ctx.get(hasClipboardContentAtom2); if (!hasContent) { throw new Error("Failed to cut nodes"); } for (const nodeId of ctx.selectedNodeIds) { await ctx.mutations.deleteNode(nodeId); } } }; var pasteCommand = { name: "paste", description: "Paste nodes from clipboard", aliases: ["v"], category: "selection", inputs: [{ name: "position", type: "point", prompt: "Click to paste at position (or Enter for default offset)", required: false }], execute: async (inputs, ctx) => { const { pasteFromClipboardAtom: pasteFromClipboardAtom2, clipboardAtom: clipboardAtom2, PASTE_OFFSET: PASTE_OFFSET2 } = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports)); const clipboard = ctx.get(clipboardAtom2); if (!clipboard || clipboard.nodes.length === 0) { throw new Error("Clipboard is empty"); } let offset = PASTE_OFFSET2; if (inputs.position) { const pos = inputs.position; offset = { x: pos.x - clipboard.bounds.minX, y: pos.y - clipboard.bounds.minY }; } const newNodeIds = ctx.set(pasteFromClipboardAtom2, offset); if (!newNodeIds || newNodeIds.length === 0) { throw new Error("Failed to paste nodes"); } }, feedback: (collected, currentInput) => { if (currentInput.name === "position") { return { crosshair: true }; } return null; } }; var duplicateCommand = { name: "duplicate", description: "Duplicate selected nodes", aliases: ["d", "dup"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { duplicateSelectionAtom: duplicateSelectionAtom2 } = await Promise.resolve().then(() => (init_clipboard_store(), clipboard_store_exports)); if (ctx.selectedNodeIds.size === 0) { throw new Error("No nodes selected to duplicate"); } const newNodeIds = ctx.set(duplicateSelectionAtom2); if (!newNodeIds || newNodeIds.length === 0) { throw new Error("Failed to duplicate nodes"); } } }; var deleteSelectedCommand = { name: "deleteSelected", description: "Delete selected nodes", aliases: ["del", "delete", "remove"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { if (ctx.selectedNodeIds.size === 0) { throw new Error("No nodes selected to delete"); } const { pushHistoryAtom: pushHistoryAtom2 } = await Promise.resolve().then(() => (init_core(), core_exports)); ctx.set(pushHistoryAtom2, "Delete nodes"); for (const nodeId of ctx.selectedNodeIds) { await ctx.mutations.deleteNode(nodeId); } } }; function registerClipboardCommands() { registerCommand(copyCommand); registerCommand(cutCommand); registerCommand(pasteCommand); registerCommand(duplicateCommand); registerCommand(deleteSelectedCommand); } // src/commands/builtins/group-commands.ts init_registry(); var groupNodesCommand = { name: "groupNodes", description: "Group selected nodes into a container", aliases: ["group"], category: "nodes", inputs: [{ name: "label", type: "text", prompt: "Group label:", required: false, default: "Group" }], execute: async (inputs, ctx) => { if (ctx.selectedNodeIds.size < 2) return; const { addNodeToLocalGraphAtom: addNodeToLocalGraphAtom2 } = await Promise.resolve().then(() => (init_graph_mutations(), graph_mutations_exports)); const { groupSelectedNodesAtom: groupSelectedNodesAtom2 } = await Promise.resolve().then(() => (init_group_store(), group_store_exports)); const label = inputs.label || "Group"; const groupNodeId = crypto.randomUUID(); ctx.set(addNodeToLocalGraphAtom2, { id: groupNodeId, graph_id: ctx.currentGraphId || "", label, node_type: "group", configuration: null, ui_properties: { x: 0, y: 0, width: 500, height: 500, size: 15, zIndex: 0 }, data: null, created_at: (/* @__PURE__ */ new Date()).toISOString(), updated_at: (/* @__PURE__ */ new Date()).toISOString() }); ctx.set(groupSelectedNodesAtom2, { nodeIds: Array.from(ctx.selectedNodeIds), groupNodeId }); } }; var ungroupNodesCommand = { name: "ungroupNodes", description: "Remove grouping from a group node", aliases: ["ungroup"], category: "nodes", inputs: [{ name: "groupNode", type: "node", prompt: "Pick a group node to ungroup:" }], execute: async (inputs, ctx) => { const groupId = inputs.groupNode; if (!groupId) return; const { ungroupNodesAtom: ungroupNodesAtom2 } = await Promise.resolve().then(() => (init_group_store(), group_store_exports)); ctx.set(ungroupNodesAtom2, groupId); } }; var collapseGroupCommand = { name: "collapseGroup", description: "Collapse a group node to hide its children", aliases: ["collapse"], category: "nodes", inputs: [{ name: "groupNode", type: "node", prompt: "Pick a group node to collapse:" }], execute: async (inputs, ctx) => { const groupId = inputs.groupNode; if (!groupId) return; const { collapseGroupAtom: collapseGroupAtom2 } = await Promise.resolve().then(() => (init_group_store(), group_store_exports)); ctx.set(collapseGroupAtom2, groupId); } }; var expandGroupCommand = { name: "expandGroup", description: "Expand a collapsed group node", aliases: ["expand"], category: "nodes", inputs: [{ name: "groupNode", type: "node", prompt: "Pick a group node to expand:" }], execute: async (inputs, ctx) => { const groupId = inputs.groupNode; if (!groupId) return; const { expandGroupAtom: expandGroupAtom2 } = await Promise.resolve().then(() => (init_group_store(), group_store_exports)); ctx.set(expandGroupAtom2, groupId); } }; function registerGroupCommands() { registerCommand(groupNodesCommand); registerCommand(ungroupNodesCommand); registerCommand(collapseGroupCommand); registerCommand(expandGroupCommand); } // src/commands/builtins/search-commands.ts init_registry(); var searchNodesCommand = { name: "searchNodes", description: "Search nodes by label, type, or ID", aliases: ["find", "search"], category: "selection", inputs: [{ name: "query", type: "text", prompt: "Search:", required: true }], execute: async (inputs, ctx) => { const query = inputs.query; if (!query) return; const { setSearchQueryAtom: setSearchQueryAtom2 } = await Promise.resolve().then(() => (init_search_store(), search_store_exports)); ctx.set(setSearchQueryAtom2, query); } }; var clearSearchCommand = { name: "clearSearch", description: "Clear the active search filter", aliases: ["clearsearch"], category: "selection", inputs: [], execute: async (_inputs, ctx) => { const { clearSearchAtom: clearSearchAtom2 } = await Promise.resolve().then(() => (init_search_store(), search_store_exports)); ctx.set(clearSearchAtom2); } }; function registerSearchCommands() { registerCommand(searchNodesCommand); registerCommand(clearSearchCommand); } // src/commands/builtins/merge-commands.ts init_registry(); var mergeNodesCommand = { name: "mergeNodes", description: "Merge selected nodes into one (first selected survives)", aliases: ["merge"], category: "nodes", inputs: [], execute: async (_inputs, ctx) => { if (ctx.selectedNodeIds.size < 2) return; const { mergeNodesAtom: mergeNodesAtom2 } = await Promise.resolve().then(() => (init_graph_mutations(), graph_mutations_exports)); const { clearSelectionAtom: clearSelectionAtom2, addNodesToSelectionAtom: addNodesToSelectionAtom2 } = await Promise.resolve().then(() => (init_selection_store(), selection_store_exports)); const nodeIds = Array.from(ctx.selectedNodeIds); ctx.set(mergeNodesAtom2, { nodeIds }); ctx.set(clearSelectionAtom2); ctx.set(addNodesToSelectionAtom2, [nodeIds[0]]); } }; function registerMergeCommands() { registerCommand(mergeNodesCommand); } // src/commands/builtins/serialization-commands.ts init_registry(); var exportCanvasCommand = { name: "exportCanvas", description: "Export the current canvas to a JSON snapshot (copies to clipboard)", aliases: ["export"], category: "custom", inputs: [], execute: async (_inputs, ctx) => { const { exportGraph: exportGraph2 } = await Promise.resolve().then(() => (init_canvas_serializer(), canvas_serializer_exports)); const { showToastAtom: showToastAtom2 } = await Promise.resolve().then(() => (init_toast_store(), toast_store_exports)); const { graphAtom: graphAtom2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports)); const graph = ctx.get(graphAtom2); const nodeCount = graph.order; const edgeCount = graph.size; if (nodeCount === 0) { throw new Error("Canvas is empty \u2014 nothing to export"); } const store = { get: ctx.get, set: ctx.set }; const snapshot = exportGraph2(store); const json = JSON.stringify(snapshot, null, 2); await navigator.clipboard.writeText(json); ctx.set(showToastAtom2, `Exported ${nodeCount} nodes, ${edgeCount} edges to clipboard`); } }; var importCanvasCommand = { name: "importCanvas", description: "Import a canvas from a JSON snapshot (reads from clipboard)", aliases: ["import"], category: "custom", inputs: [], execute: async (_inputs, ctx) => { const { importGraph: importGraph2, validateSnapshot: validateSnapshot2 } = await Promise.resolve().then(() => (init_canvas_serializer(), canvas_serializer_exports)); const { showToastAtom: showToastAtom2 } = await Promise.resolve().then(() => (init_toast_store(), toast_store_exports)); const { pushHistoryAtom: pushHistoryAtom2 } = await Promise.resolve().then(() => (init_history_store(), history_store_exports)); let json; try { json = await navigator.clipboard.readText(); } catch { throw new Error("Could not read clipboard \u2014 paste the snapshot JSON manually"); } let data; try { data = JSON.parse(json); } catch { throw new Error("Clipboard does not contain valid JSON"); } const result = validateSnapshot2(data); if (!result.valid) { throw new Error(`Invalid snapshot: ${result.errors[0]}`); } ctx.set(pushHistoryAtom2, "Import canvas"); const store = { get: ctx.get, set: ctx.set }; const snapshot = data; importGraph2(store, snapshot); ctx.set(showToastAtom2, `Imported ${snapshot.nodes.length} nodes, ${snapshot.edges.length} edges`); } }; function registerSerializationCommands() { registerCommand(exportCanvasCommand); registerCommand(importCanvasCommand); } // src/commands/builtins/index.ts function registerBuiltinCommands() { registerViewportCommands(); registerSelectionCommands(); registerHistoryCommands(); registerLayoutCommands(); registerClipboardCommands(); registerGroupCommands(); registerSearchCommands(); registerMergeCommands(); registerSerializationCommands(); } // src/gestures/index.ts var gestures_exports = {}; __export(gestures_exports, { ACTIVE_INTERACTION_CONTEXT: () => ACTIVE_INTERACTION_CONTEXT, DEFAULT_CONTEXT: () => DEFAULT_CONTEXT, GestureProvider: () => GestureProvider, IDLE: () => IDLE, INPUT_MODE_CONTEXTS: () => INPUT_MODE_CONTEXTS, InputProvider: () => InputProvider, KEYBOARD_MANIPULATE_CONTEXT: () => KEYBOARD_MANIPULATE_CONTEXT, KEYBOARD_NAVIGATE_CONTEXT: () => KEYBOARD_NAVIGATE_CONTEXT, LONG_PRESS_TIMER: () => LONG_PRESS_TIMER, NO_HELD_KEYS: () => NO_HELD_KEYS, NO_MODIFIERS: () => NO_MODIFIERS, PALM_REJECTION_CONTEXT: () => PALM_REJECTION_CONTEXT, PICK_NODES_CONTEXT: () => PICK_NODES_CONTEXT, PICK_NODE_CONTEXT: () => PICK_NODE_CONTEXT, PICK_POINT_CONTEXT: () => PICK_POINT_CONTEXT, PanInertia: () => PanInertia, SEARCH_CONTEXT: () => SEARCH_CONTEXT, SETTLE_TIMER: () => SETTLE_TIMER, TimedStateRunner: () => TimedStateRunner, VelocitySampler: () => VelocitySampler, ZoomInertia: () => ZoomInertia, activateFocusedNode: () => activateFocusedNode, buildMappingIndex: () => buildMappingIndex, cancelActiveInteraction: () => cancelActiveInteraction, clearHandlers: () => clearHandlers, createPinchHandlers: () => createPinchHandlers, createWheelHandler: () => createWheelHandler, cutSelection: () => cutSelection, cycleFocus: () => cycleFocus, deleteSelection: () => deleteSelection, dispatch: () => dispatch, escapeInput: () => escapeInput, extractModifiers: () => extractModifiers, findNearestNode: () => findNearestNode, getCurrentSubject: () => getCurrentSubject, getHandler: () => getHandler, indexContext: () => indexContext, isKeyInputEvent: () => isKeyInputEvent, isPointerGestureEvent: () => isPointerGestureEvent, navigateFocus: () => navigateFocus, normalizePointer: () => normalizePointer, nudgeSelection: () => nudgeSelection, registerAction: () => registerAction2, resolve: () => resolve, snapZoom: () => snapZoom, specificity: () => specificity, transition: () => transition, unregisterAction: () => unregisterAction2, useCanvasGestures: () => useCanvasGestures, useGestureContext: () => useGestureContext, useGestureSystem: () => useGestureSystem, useGuardContext: () => useGuardContext, useInertia: () => useInertia, useInputContext: () => useInputContext, useInputModeGestureContext: () => useInputModeGestureContext, useInputSystem: () => useInputSystem, useNodeGestures: () => useNodeGestures, useRegisterInputActions: () => useRegisterInputActions }); init_types2(); init_dispatcher(); // src/index.ts var CANVAS_VERSION = "2.0.0"; var canvasVersion = { version: CANVAS_VERSION, major: 2, minor: 0, patch: 0, /** Check if current version is at least the specified version */ isAtLeast: (major, minor = 0, patch = 0) => { if (canvasVersion.major > major) return true; if (canvasVersion.major < major) return false; if (canvasVersion.minor > minor) return true; if (canvasVersion.minor < minor) return false; return canvasVersion.patch >= patch; } }; export { ActionCategory, AlignmentGuides, BUILT_IN_PRESETS, BuiltInActionId, CANVAS_VERSION, Canvas, CanvasAnimations, CanvasDbProvider, CanvasEventType, CanvasProvider, CanvasStyleProvider, CanvasToast, ComboboxOption, ComboboxSearch, CommandFeedbackOverlay, CommandInputCollector, CommandLine, CommandProvider, CommandSearch, ConnectedNode, ConnectedNodeRenderer, ContextMenuAction, ContextMenuDivider, Crosshairs, DEFAULT_GESTURE_RULES, DEFAULT_MAPPINGS, DEFAULT_PORT, DEFAULT_SHORTCUTS, DeleteIcon, EDGE_ANIMATION_DURATION, EVENT_TYPE_INFO, EdgeLabel, EdgeLabelEditor, EdgeOverlay, EdgePath, EdgePreviewLine, EdgeRenderer, EventMappingRow, FallbackNodeTypeComponent, FitToBoundsMode, FitToViewIcon, Grid, GroupNode, HIT_TARGET_SIZES, InMemoryStorageAdapter, LockedNodeOverlay, Minimap, Node, NodeContextMenu, NodeErrorBoundary, NodePorts, NodeRenderer, NodeTypeCombobox, NoteNode, PASTE_OFFSET, PluginError, PortBar, PortHandle, ResizeHandle, SNAPSHOT_VERSION, SelectionOverlay, SettingsEventMap, SettingsPanel, SettingsPresets, SpatialGrid, SupabaseStorageAdapter, TouchActionButton, VIRTUALIZATION_BUFFER, Viewport, ViewportControls, ZOOM_EXIT_THRESHOLD, ZOOM_TRANSITION_THRESHOLD, activePointersAtom, activePresetAtom, activePresetIdAtom, addGestureRuleAtom, addNodeToLocalGraphAtom, addNodesToSelectionAtom, alignmentGuidesAtom, allPresetsAtom, animateFitToBoundsAtom, animateZoomToNodeAtom, applyDelta, applyPresetAtom, areNodesClose, arePortsCompatible, autoResizeGroupAtom, bezierHorizontal, bezierSmart, bezierVertical, buildActionHelpers, buildRuleIndex, calculateBounds, calculatePortPosition, canPortAcceptConnection, canRedoAtom, canUndoAtom, cancelCommand, cancelSelectionAtom, canvasIsDarkAtom, canvasKeys, canvasMark, canvasSettingsAtom, canvasStyleOverridesAtom, canvasStylesAtom, canvasToastAtom, canvasVersion, canvasWrap, centerOnNodeAtom, checkNodesOverlap, classifyPointer, cleanupAllNodePositionsAtom, cleanupNodePositionAtom, clearActions, clearAlignmentGuidesAtom, clearClipboardAtom, clearCommandErrorAtom, clearCustomEdgePathCalculators, clearEdgeSelectionAtom, clearGraphOnSwitchAtom, clearHistoryAtom, clearMutationQueueAtom, clearNodeTypeRegistry, clearPendingState, clearPlugins, clearPointersAtom, clearSearchAtom, clearSelectionAtom, clearSelectionCommand, clipboardAtom, clipboardNodeCountAtom, closeCommandLineAtom, collapseGroupAtom, collapsedEdgeRemapAtom, collapsedGroupsAtom, collectInput, commandFeedbackAtom, commandHistoryAtom, commandLineStateAtom, commandLineVisibleAtom, commandProgressAtom, commandRegistry, completeMutationAtom, conditionalSnap, consumerGestureRulesAtom, copyCommand, copyToClipboardAtom, createActionContext, createActionContextFromReactEvent, createActionContextFromTouchEvent, createCanvasAPI, createComponentRegistry, createDebug, createFallbackComponent, createGraphEdge, createGraphNode, createSnapshot, currentGraphIdAtom, currentInputAtom, cutCommand, cutToClipboardAtom, debug, defaultDarkStyles, defaultEdgePathCalculator, defaultLightStyles, deleteGraphEdge, deleteGraphNode, deletePresetAtom, deleteSelectedCommand, departingEdgesAtom, dequeueMutationAtom, detectInputCapabilities, draggingNodeIdAtom, dropTargetNodeIdAtom, duplicateCommand, duplicateSelectionAtom, edgeCreationAtom, edgeFamilyAtom, edgeKeysAtom, edgeKeysWithTempEdgeAtom, editingEdgeLabelAtom, endNodeDragAtom, endSelectionAtom, eventMappingsAtom, executeAction, executeCommandInteractive, expandGroupAtom, exportGraph, fetchGraphEdges, fetchGraphNode, fetchGraphNodes, findAlignmentGuides, fingerCountAtom, fitSelectionCommand, fitToBoundsAtom, fitToViewCommand, focusedNodeIdAtom, forceLayoutCommand, formatRuleLabel, fuzzyMatch, gestureRuleIndexAtom, gestureRuleSettingsAtom, gestureRulesAtom, gestures_exports as gestures, getAction, getActionForEvent, getActionsByCategories, getActionsByCategory, getAllActions, getAllPlugins, getCustomEdgePathCalculatorNames, getEdgePathCalculator, getGestureThresholds, getHitTargetSize, getNextQueuedMutationAtom, getNodeBounds, getNodeCenter, getNodeDescendants, getNodeGestureConfig, getNodePorts, getNodeTypeComponent, getPendingState, getPlugin, getPluginGestureContexts, getPluginIds, getPortWorldPosition, getRegisteredNodeTypes, getSnapGuides, getViewportGestureConfig, goBackInputAtom, goToLockedPageAtom, graphAtom, graphOptions, graphUpdateVersionAtom, groupChildCountAtom, groupSelectedNodesAtom, handleNodePointerDownSelectionAtom, handlePickedNode, handlePickedPoint, hasAction, hasClipboardContentAtom, hasCustomEdgePathCalculator, hasExternalKeyboardAtom, hasFocusedNodeAtom, hasLockedNodeAtom, hasNodeTypeComponent, hasPendingMutations, hasPlugin, hasSelectionAtom, hasUnsavedChangesAtom, highestZIndexAtom, highlightedSearchIndexAtom, highlightedSearchNodeIdAtom, historyLabelsAtom, historyStateAtom, hitTestNode, hitTestPort, importGraph, incrementRetryCountAtom, inputCapabilitiesAtom, inputModeAtom, interactionFeedbackAtom, invertDelta, invertSelectionCommand, isCommandActiveAtom, isFilterActiveAtom, isGroupNodeAtom, isMultiTouchAtom, isNodeCollapsed, isOnlineAtom, isPanelOpenAtom, isPickNodeModeAtom, isPickingModeAtom, isSelectingAtom, isSnappingActiveAtom, isStylusActiveAtom, isTouchDeviceAtom, isZoomTransitioningAtom, keyboardInteractionModeAtom, lastSyncErrorAtom, lastSyncTimeAtom, loadGraphFromDbAtom, lockNodeAtom, lockedNodeDataAtom, lockedNodeIdAtom, lockedNodePageCountAtom, lockedNodePageIndexAtom, matchSpecificity, mergeNodesAtom, mergeRules, mergeWithDefaults, moveNodesToGroupAtom, mutationQueueAtom, nestNodesOnDropAtom, nextLockedPageAtom, nextSearchResultAtom, nodeChildrenAtom, nodeFamilyAtom, nodeKeysAtom, nodeParentAtom, nodePositionAtomFamily, nodePositionUpdateCounterAtom, openCommandLineAtom, optimisticDeleteEdgeAtom, optimisticDeleteNodeAtom, palmRejectionEnabledAtom, panAtom, pasteCommand, pasteFromClipboardAtom, pendingInputResolverAtom, pendingMutationsCountAtom, pendingNodeMutations, perfEnabledAtom, pointInPolygon, pointerDownAtom, pointerUpAtom, preDragNodeAttributesAtom, prefersReducedMotionAtom, prevLockedPageAtom, prevSearchResultAtom, primaryInputSourceAtom, provideInputAtom, pushDeltaAtom, pushHistoryAtom, queueMutationAtom, redoAtom, redoCommand, redoCountAtom, registerAction, registerBuiltinCommands, registerClipboardCommands, registerCommand, registerEdgePathCalculator, registerHistoryCommands, registerLayoutCommands, registerNodeType, registerNodeTypes, registerPlugin, registerSelectionCommands, registerViewportCommands, removeEdgeWithAnimationAtom, removeFromGroupAtom, removeGestureRuleAtom, removeNodesFromSelectionAtom, resetGestureRulesAtom, resetInputModeAtom, resetKeyboardInteractionModeAtom, resetSettingsAtom, resetViewportAtom, resetViewportCommand, resolveEdgePathCalculator, resolveGesture, resolveGestureIndexed, saveAsPresetAtom, screenToWorldAtom, searchEdgeResultCountAtom, searchEdgeResultsAtom, searchQueryAtom, searchResultCountAtom, searchResultsArrayAtom, searchResultsAtom, searchTotalResultCountAtom, selectAllCommand, selectCommandAtom, selectEdgeAtom, selectSingleNodeAtom, selectedEdgeIdAtom, selectedNodeIdsAtom, selectedNodesCountAtom, selectedSuggestionIndexAtom, selectionPathAtom, selectionRectAtom, setCommandErrorAtom, setEventMappingAtom, setFocusedNodeAtom, setGridSizeAtom, setHitTestProvider, setKeyboardInteractionModeAtom, setNodeCenter, setNodeParentAtom, setOnlineStatusAtom, setPanelOpenAtom, setPerfEnabled, setSearchQueryAtom, setVirtualizationEnabledAtom, setZoomAtom, showToastAtom, skipInputAtom, smoothStep, snapAlignmentEnabledAtom, snapEnabledAtom, snapGridSizeAtom, snapTemporaryDisableAtom, snapToGrid, spatialIndexAtom, splitNodeAtom, startMutationAtom, startNodeDragAtom, startPickNodeAtom, startPickNodesAtom, startPickPointAtom, startSelectionAtom, stepHorizontal, stepSmart, stepVertical, storageAdapterAtom, straight, swapEdgeAtomicAtom, syncStateAtom, syncStatusAtom, toggleAlignmentGuidesAtom, toggleGroupCollapseAtom, toggleNodeInSelectionAtom, togglePanelAtom, toggleSnapAtom, toggleVirtualizationAtom, trackMutationErrorAtom, uiNodesAtom, undoAtom, undoCommand, undoCountAtom, ungroupNodesAtom, unlockNodeAtom, unregisterAction, unregisterEdgePathCalculator, unregisterNodeType, unregisterPlugin, updateEdgeLabelAtom, updateGestureRuleAtom, updateGraphEdge, updateGraphNode, updateInteractionFeedbackAtom, updateNodePositionAtom, updatePresetAtom, updateSearchQueryAtom, updateSelectionAtom, useActionExecutor, useAnimatedLayout, useArrowKeyNavigation, useCanvasDrag, useCanvasGraph, useCanvasHistory, useCanvasSelection, useCanvasSettings, useCanvasStyle, useCanvasTheme, useCanvasViewport, useCommandContext, useCommandLine, useCreateEdge, useCreateNode, useDeleteEdge, useDeleteNode, useExecuteCommand, useFitToBounds, useForceLayout, useGestureResolver, useGetGraphBounds, useGlobalKeyboard, useGraphEdges, useGraphNodes, useGridLayout, useKeyState, useLayout, useNodeDrag, useNodeResize, useNodeSelection, usePlugin, usePlugins, useSelectionBounds, useSplitGesture, useStorageAdapter, useTapGesture, useTreeLayout, useUpdateEdge, useUpdateNode, useVirtualization, useZoomTransition, validateSnapshot, viewportRectAtom, virtualizationEnabledAtom, virtualizationMetricsAtom, visibleBoundsAtom, visibleEdgeKeysAtom, visibleNodeKeysAtom, watchExternalKeyboardAtom, watchReducedMotionAtom, worldToScreenAtom, zoomAnimationTargetAtom, zoomAtom, zoomFocusNodeIdAtom, zoomInCommand, zoomOutCommand, zoomTransitionProgressAtom }; //# sourceMappingURL=index.mjs.map