@pixerate/editor 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,9 +31,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  ColorHighlighter: () => ColorHighlighter,
34
+ DEFAULT_NODE_HEIGHT: () => DEFAULT_NODE_HEIGHT,
35
+ DEFAULT_NODE_WIDTH: () => DEFAULT_NODE_WIDTH,
36
+ DEFAULT_PLACEMENT_GAP: () => DEFAULT_PLACEMENT_GAP,
34
37
  DataSourceBinder: () => DataSourceBinder,
35
38
  DependencyGraph: () => DependencyGraph,
36
39
  DirtyTracker: () => DirtyTracker,
40
+ EDITOR_MCP_TOOLS: () => EDITOR_MCP_TOOLS,
37
41
  EditorController: () => EditorController,
38
42
  FontSize: () => FontSize,
39
43
  FormulaEvaluator: () => FormulaEvaluator,
@@ -43,48 +47,86 @@ __export(src_exports, {
43
47
  INSTRUCTION_REGEX: () => INSTRUCTION_REGEX,
44
48
  Image: () => Image2,
45
49
  LoadingNode: () => LoadingNode,
50
+ MARKDOWN_MENTION_SPLIT_REGEX: () => MARKDOWN_MENTION_SPLIT_REGEX,
51
+ MCP_TOOL_EDIT_CANVAS: () => MCP_TOOL_EDIT_CANVAS,
52
+ MCP_TOOL_EDIT_PROMPT: () => MCP_TOOL_EDIT_PROMPT,
53
+ MCP_TOOL_GET_EDITOR_STATE: () => MCP_TOOL_GET_EDITOR_STATE,
54
+ MENTION_REGEX: () => MENTION_REGEX,
46
55
  Markdown: () => Markdown2,
47
56
  Mention: () => Mention,
48
57
  SlashCommands: () => SlashCommands,
49
58
  SmilieReplacer: () => SmilieReplacer,
50
59
  SpreadsheetController: () => SpreadsheetController,
60
+ StreamingLayoutManager: () => StreamingLayoutManager,
51
61
  TEMPLATE_REGEX: () => TEMPLATE_REGEX,
52
62
  TemplateSuggestions: () => TemplateSuggestions,
53
63
  VARIABLE_REGEX: () => VARIABLE_REGEX,
64
+ boundsOf: () => boundsOf,
65
+ calculateDagreReflowDisplacement: () => calculateDagreReflowDisplacement,
66
+ calculateDirectionalDisplacement: () => calculateDirectionalDisplacement,
67
+ centerNodes: () => centerNodes,
68
+ centre: () => centre,
54
69
  colNameToIndex: () => colNameToIndex,
70
+ computeNodesBoundingBox: () => computeNodesBoundingBox,
71
+ createBezierTrajectory: () => createBezierTrajectory,
55
72
  createDirtyTracker: () => createDirtyTracker,
56
73
  createEditor: () => createEditor,
74
+ createFanOutTrajectory: () => createFanOutTrajectory,
57
75
  createHistoryManager: () => createHistoryManager,
58
76
  createMentionExtension: () => createMentionExtension,
59
77
  createRichTextPreset: () => createRichTextPreset,
78
+ defaultEasing: () => defaultEasing,
60
79
  defaultIsEqual: () => defaultIsEqual,
80
+ ensureLayout: () => ensureLayout,
61
81
  escapeHtml: () => escapeHtml,
82
+ estimateNodeSize: () => estimateNodeSize,
62
83
  extractCellDependencies: () => extractCellDependencies,
63
84
  extractInstructionNames: () => extractInstructionNames,
85
+ extractMentions: () => extractMentions,
64
86
  extractMentionsFromDoc: () => extractMentionsFromDoc,
65
87
  extractTemplateNames: () => extractTemplateNames,
66
88
  extractVariableNames: () => extractVariableNames,
67
89
  findHexColors: () => findHexColors,
90
+ flowSide: () => flowSide,
91
+ getCenteredNodePosition: () => getCenteredNodePosition,
68
92
  getEditorMarkdown: () => getEditorMarkdown,
69
93
  getEditorText: () => getEditorText,
70
94
  getLatestVersion: () => getLatestVersion,
95
+ getLayoutedNodes: () => getLayoutedNodes,
71
96
  handleImageInsertion: () => handleImageInsertion,
72
97
  htmlToPlainText: () => htmlToPlainText,
73
98
  imagePastePluginKey: () => imagePastePluginKey,
74
99
  indexToColName: () => indexToColName,
75
100
  insertImageNode: () => insertImageNode,
76
101
  instructionRegex: () => instructionRegex,
102
+ isEventFromTextInput: () => isEventFromTextInput,
103
+ isValidCanvasNode: () => isValidCanvasNode,
104
+ isValidUrl: () => isValidUrl,
105
+ linearPositionTrajectory: () => linearPositionTrajectory,
106
+ linearTrajectory: () => linearTrajectory,
77
107
  mapRawOffsetToResolved: () => mapRawOffsetToResolved,
78
108
  mapResolvedOffsetToRaw: () => mapResolvedOffsetToRaw,
79
109
  markdownToTipTapHtml: () => markdownToTipTapHtml,
110
+ overlaps: () => overlaps,
111
+ parseCanvasClipboardData: () => parseCanvasClipboardData,
80
112
  parseCellReference: () => parseCellReference,
81
113
  parseFormula: () => parseFormula,
114
+ parseMentionSegments: () => parseMentionSegments,
82
115
  parseRange: () => parseRange,
116
+ placeBeside: () => placeBeside,
83
117
  plainTextToTipTapHtml: () => plainTextToTipTapHtml,
118
+ planAgentCommand: () => planAgentCommand,
119
+ preprocessMarkdownFileLinks: () => preprocessMarkdownFileLinks,
120
+ preprocessMarkdownMentions: () => preprocessMarkdownMentions,
121
+ remapPastedNodes: () => remapPastedNodes,
84
122
  removeImageNode: () => removeImageNode,
123
+ repositionNodesTo: () => repositionNodesTo,
85
124
  resolveTemplates: () => resolveTemplates,
86
125
  resolveTemplatesWithMapping: () => resolveTemplatesWithMapping,
87
126
  resolveVariables: () => resolveVariables,
127
+ runMultiNodeTransition: () => runMultiNodeTransition,
128
+ serializeCanvasNodes: () => serializeCanvasNodes,
129
+ stripMarkdownCode: () => stripMarkdownCode,
88
130
  templateRegex: () => templateRegex,
89
131
  tokenizeFormula: () => tokenizeFormula,
90
132
  tokenizePrompt: () => tokenizePrompt,
@@ -122,10 +164,10 @@ function tokenizePrompt(text) {
122
164
  while ((match = varRegex.exec(text)) !== null) {
123
165
  const start = match.index;
124
166
  const end = start + match[0].length;
125
- const overlaps = rawMatches.some(
167
+ const overlaps2 = rawMatches.some(
126
168
  (m) => !(end <= m.start || start >= m.end)
127
169
  );
128
- if (!overlaps) {
170
+ if (!overlaps2) {
129
171
  rawMatches.push({
130
172
  type: "variable",
131
173
  raw: match[0],
@@ -142,10 +184,10 @@ function tokenizePrompt(text) {
142
184
  while ((match = instRegex.exec(text)) !== null) {
143
185
  const start = match.index;
144
186
  const end = start + match[0].length;
145
- const overlaps = rawMatches.some(
187
+ const overlaps2 = rawMatches.some(
146
188
  (m) => !(end <= m.start || start >= m.end)
147
189
  );
148
- if (!overlaps) {
190
+ if (!overlaps2) {
149
191
  rawMatches.push({
150
192
  type: "instruction",
151
193
  raw: match[0],
@@ -2290,6 +2332,150 @@ function createRichTextPreset(options = {}) {
2290
2332
  return extensions2;
2291
2333
  }
2292
2334
 
2335
+ // src/serializers/mentions.ts
2336
+ var MENTION_REGEX = /(?:^|[\s>(*_"';\\/[\]{}~`:]|&quot;|&gt;|&lt;|&amp;)@(?:"([^"]+)"|&quot;([^&]+)&quot;|([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*))/g;
2337
+ var MARKDOWN_MENTION_SPLIT_REGEX = /(```[\s\S]*?```|~~~[\s\S]*?~~~|<pre[\s\S]*?<\/pre>|<code[\s\S]*?<\/code>|`[^`\n]+`|<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["']|data-mention=["'][^"']*["'])[^>]*>[\s\S]*?<\/span>|<[^>]+>|\]\([^)]+\)|(?:https?|file|vscode|cursor|mailto):\/\/[^\s<)]+)/gi;
2338
+ function stripMarkdownCode(content) {
2339
+ if (!content || typeof content !== "string") return "";
2340
+ return content.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/<pre[\s\S]*?<\/pre>/gi, "").replace(/<code[\s\S]*?<\/code>/gi, "").replace(/`[^`\n]*`/g, "");
2341
+ }
2342
+ function extractMentions(content) {
2343
+ if (!content || typeof content !== "string") return [];
2344
+ const normalizedMentionNodes = content.replace(
2345
+ /<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["'])[^>]*>([\s\S]*?)<\/span>/gi,
2346
+ (match2, inner) => {
2347
+ const labelMatch = match2.match(/\bdata-label=["']([^"']+)["']/i);
2348
+ if (labelMatch) return ` @${labelMatch[1]} `;
2349
+ const idMatch = match2.match(/\bdata-id=["']([^"']+)["']/i);
2350
+ if (idMatch) return ` @${idMatch[1]} `;
2351
+ return ` ${inner} `;
2352
+ }
2353
+ );
2354
+ const cleanContent = stripMarkdownCode(normalizedMentionNodes);
2355
+ const withoutUrls = cleanContent.replace(/(?:https?|ftp|file|vscode|cursor|mailto):\/\/[^\s<>()]+/gi, "").replace(/\]\([^)]*\)/g, "]");
2356
+ const textOnly = withoutUrls.replace(/<[^>]+>/g, " ");
2357
+ const mentions = [];
2358
+ const seen = /* @__PURE__ */ new Set();
2359
+ const regex = new RegExp(MENTION_REGEX.source, "g");
2360
+ let match;
2361
+ while ((match = regex.exec(textOnly)) !== null) {
2362
+ const handle = (match[1] ?? match[2] ?? match[3] ?? "").trim();
2363
+ if (handle && !seen.has(handle)) {
2364
+ seen.add(handle);
2365
+ mentions.push(handle);
2366
+ }
2367
+ }
2368
+ return mentions;
2369
+ }
2370
+ function parseMentionSegments(content) {
2371
+ if (!content) return [];
2372
+ const segments = [];
2373
+ const regex = new RegExp(MENTION_REGEX.source, "g");
2374
+ let lastIndex = 0;
2375
+ let match;
2376
+ while ((match = regex.exec(content)) !== null) {
2377
+ const fullMatch = match[0];
2378
+ const atIndex = fullMatch.indexOf("@");
2379
+ const prefix = atIndex > 0 ? fullMatch.slice(0, atIndex) : "";
2380
+ const mentionToken = fullMatch.slice(atIndex);
2381
+ const handle = match[1] ?? match[2] ?? match[3];
2382
+ const matchStart = match.index + prefix.length;
2383
+ if (matchStart > lastIndex) {
2384
+ segments.push({
2385
+ type: "text",
2386
+ value: content.slice(lastIndex, matchStart)
2387
+ });
2388
+ }
2389
+ segments.push({
2390
+ type: "mention",
2391
+ value: mentionToken,
2392
+ handle
2393
+ });
2394
+ lastIndex = matchStart + mentionToken.length;
2395
+ }
2396
+ if (lastIndex < content.length) {
2397
+ segments.push({
2398
+ type: "text",
2399
+ value: content.slice(lastIndex)
2400
+ });
2401
+ }
2402
+ return segments;
2403
+ }
2404
+ function preprocessMarkdownMentions(content, optionsOrMentionables = []) {
2405
+ if (!content || typeof content !== "string") return "";
2406
+ const options = Array.isArray(optionsOrMentionables) ? { mentionables: optionsOrMentionables } : optionsOrMentionables;
2407
+ const allMentionables = options.mentionables || [];
2408
+ const defaultType = options.defaultType || "user";
2409
+ const resolveMention = options.resolveMention;
2410
+ const parts = content.split(MARKDOWN_MENTION_SPLIT_REGEX);
2411
+ return parts.map((part) => {
2412
+ if (!part) return "";
2413
+ if (part.startsWith("```") || part.startsWith("~~~") || part.startsWith("`") || part.startsWith("](") || /^(?:https?|file|vscode|cursor|mailto):\/\//i.test(part) || /^<pre[\s>]/i.test(part) || /^<code[\s>]/i.test(part) || /^<[^>]+>$/i.test(part) || /^<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["']|data-mention=["'][^"']*["'])/i.test(
2414
+ part
2415
+ )) {
2416
+ return part;
2417
+ }
2418
+ const regex = new RegExp(MENTION_REGEX.source, "g");
2419
+ return part.replace(regex, (match, quotedHandle, escapedQuotedHandle, standardHandle) => {
2420
+ const atIndex = match.indexOf("@");
2421
+ const prefix = atIndex > 0 ? match.slice(0, atIndex) : "";
2422
+ const rawHandle = (quotedHandle || escapedQuotedHandle || standardHandle || "").trim();
2423
+ if (!rawHandle) return match;
2424
+ const lowerHandle = rawHandle.toLowerCase();
2425
+ const entity = allMentionables.find((e) => {
2426
+ if (e.label?.toLowerCase() === lowerHandle) return true;
2427
+ if (e.handle?.toLowerCase() === lowerHandle) return true;
2428
+ if (e.name?.toLowerCase() === lowerHandle) return true;
2429
+ if (e.id?.toLowerCase() === lowerHandle) return true;
2430
+ if (e.type === "agent" && `agent${(e.handle || e.label || "").toLowerCase()}` === lowerHandle) return true;
2431
+ if (e.aliases?.some(
2432
+ (a) => a.toLowerCase() === lowerHandle || e.type === "agent" && `agent${a.toLowerCase()}` === lowerHandle
2433
+ )) {
2434
+ return true;
2435
+ }
2436
+ return false;
2437
+ });
2438
+ let id = entity?.id;
2439
+ let label = entity?.label || entity?.handle || rawHandle;
2440
+ let type = entity?.type;
2441
+ let avatarUrl = entity?.avatarUrl;
2442
+ let color = entity?.color || entity?.hexColor;
2443
+ if (!entity) {
2444
+ const customResolved = resolveMention?.(rawHandle);
2445
+ if (customResolved) {
2446
+ id = customResolved.id ?? id;
2447
+ label = customResolved.label ?? customResolved.handle ?? label;
2448
+ type = customResolved.type ?? type;
2449
+ avatarUrl = customResolved.avatarUrl ?? avatarUrl;
2450
+ color = customResolved.color ?? customResolved.hexColor ?? color;
2451
+ } else {
2452
+ const stripped = lowerHandle.replace(/^agent[\s\-_]*/i, "");
2453
+ if (["supervisor", "coordinator", "planner", "validator", "executor"].includes(stripped)) {
2454
+ type = "agent";
2455
+ id = stripped;
2456
+ label = stripped;
2457
+ } else if (lowerHandle === "agents") {
2458
+ type = "team";
2459
+ id = "agents";
2460
+ label = "agents";
2461
+ } else {
2462
+ type = defaultType;
2463
+ id = rawHandle;
2464
+ label = rawHandle;
2465
+ }
2466
+ }
2467
+ }
2468
+ const avatarAttr = avatarUrl ? ` data-avatar-url="${escapeHtml(avatarUrl)}"` : "";
2469
+ const colorAttr = color ? ` data-color="${escapeHtml(color)}"` : "";
2470
+ return `${prefix}<span class="pixerate-mention-node" data-type="mention" data-id="${escapeHtml(
2471
+ id || label
2472
+ )}" data-label="${escapeHtml(label)}" data-mention-suggestion-char="@" data-type-name="${escapeHtml(
2473
+ type || defaultType
2474
+ )}"${avatarAttr}${colorAttr}>@${escapeHtml(label)}</span>`;
2475
+ });
2476
+ }).join("");
2477
+ }
2478
+
2293
2479
  // src/serializers/plain-text.ts
2294
2480
  function escapeHtml(text) {
2295
2481
  if (!text) return "";
@@ -2414,6 +2600,16 @@ function extractMentionsFromDoc(docOrEditor) {
2414
2600
  });
2415
2601
  }
2416
2602
  }
2603
+ if (mentions.length === 0) {
2604
+ const rawHandles = extractMentions(docOrEditor);
2605
+ for (const handle of rawHandles) {
2606
+ mentions.push({
2607
+ id: handle,
2608
+ label: handle,
2609
+ type: "user"
2610
+ });
2611
+ }
2612
+ }
2417
2613
  }
2418
2614
  return mentions;
2419
2615
  }
@@ -2433,9 +2629,31 @@ md2.validateLink = (url) => {
2433
2629
  }
2434
2630
  return defaultValidateLink.call(md2, url);
2435
2631
  };
2436
- function markdownToTipTapHtml(markdown) {
2632
+ function preprocessMarkdownFileLinks(content) {
2633
+ if (!content) return "";
2634
+ return content.replace(
2635
+ /\[([^\]]+)\]\(((?:file|vscode|cursor):\/\/[^)]+)\)/g,
2636
+ (_match, rawLabel, href) => {
2637
+ const trimmed = rawLabel.trim();
2638
+ const codeMatch = trimmed.match(/^`([^`]+)`$/);
2639
+ const labelContent = codeMatch ? `<code>${codeMatch[1]}</code>` : trimmed;
2640
+ return `<a href="${href}" data-file-link="true" title="Click to copy file path">${labelContent}</a>`;
2641
+ }
2642
+ );
2643
+ }
2644
+ function markdownToTipTapHtml(markdown, options) {
2437
2645
  if (!markdown) return "<p></p>";
2438
- return md2.render(markdown);
2646
+ let content = markdown;
2647
+ if (options?.preprocessFileLinks) {
2648
+ content = preprocessMarkdownFileLinks(content);
2649
+ }
2650
+ if (options?.preprocessMentions || options?.mentionables || options?.preprocessOptions) {
2651
+ const mentionOpts = options.preprocessOptions || {
2652
+ mentionables: options.mentionables
2653
+ };
2654
+ content = preprocessMarkdownMentions(content, mentionOpts);
2655
+ }
2656
+ return md2.render(content);
2439
2657
  }
2440
2658
  function getEditorMarkdown(editor) {
2441
2659
  if (!editor) return "";
@@ -4160,12 +4378,1024 @@ var HistoryManager = class {
4160
4378
  function createHistoryManager(options = {}) {
4161
4379
  return new HistoryManager(options);
4162
4380
  }
4381
+
4382
+ // src/canvas/layout.ts
4383
+ var import_dagre = __toESM(require("@dagrejs/dagre"), 1);
4384
+ var DEFAULT_NODE_WIDTH = 200;
4385
+ var DEFAULT_NODE_HEIGHT = 60;
4386
+ var DEFAULT_PLACEMENT_GAP = 48;
4387
+ function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
4388
+ const rect = screenDimensions ?? (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 });
4389
+ const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
4390
+ return {
4391
+ x: flowPos.x - width / 2,
4392
+ y: flowPos.y - height / 2
4393
+ };
4394
+ }
4395
+ function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
4396
+ if (nodes.length === 0) {
4397
+ return [];
4398
+ }
4399
+ const bounds = getNodesBounds(nodes);
4400
+ const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
4401
+ const centeredPosition = getCenteredNodePosition(
4402
+ bounds.width,
4403
+ bounds.height,
4404
+ screenToFlowPosition,
4405
+ screenDimensions
4406
+ );
4407
+ const deltaX = centeredPosition.x - boundingBoxOrigin.x;
4408
+ const deltaY = centeredPosition.y - boundingBoxOrigin.y;
4409
+ return nodes.map((node) => ({
4410
+ ...node,
4411
+ position: {
4412
+ x: node.position.x + deltaX,
4413
+ y: node.position.y + deltaY
4414
+ }
4415
+ }));
4416
+ }
4417
+ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
4418
+ if (nodes.length === 0) return [];
4419
+ const {
4420
+ direction = "LR",
4421
+ ranksep = 100,
4422
+ nodesep = 25,
4423
+ defaultNodeWidth = DEFAULT_NODE_WIDTH,
4424
+ defaultNodeHeight = DEFAULT_NODE_HEIGHT
4425
+ } = options;
4426
+ const dagreGraph = new import_dagre.default.graphlib.Graph();
4427
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
4428
+ dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
4429
+ const nodeIds = new Set(nodes.map((node) => node.id));
4430
+ nodes.forEach((node) => {
4431
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
4432
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
4433
+ dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
4434
+ });
4435
+ edges.forEach((edge) => {
4436
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
4437
+ dagreGraph.setEdge(edge.source, edge.target);
4438
+ }
4439
+ });
4440
+ import_dagre.default.layout(dagreGraph);
4441
+ return nodes.map((node) => {
4442
+ const nodeWithPosition = dagreGraph.node(node.id);
4443
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
4444
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
4445
+ return {
4446
+ ...node,
4447
+ position: {
4448
+ x: nodeWithPosition.x - nodeWidth / 2,
4449
+ y: nodeWithPosition.y - nodeHeight / 2
4450
+ }
4451
+ };
4452
+ });
4453
+ }
4454
+ function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
4455
+ const label = String(node.data?.label ?? node.data?.title ?? node.id ?? "");
4456
+ const lines = label.split(/\n|<br\s*\/?>/);
4457
+ const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
4458
+ const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
4459
+ const height = Math.max(defaultHeight, lines.length * 20 + 24);
4460
+ return { width, height };
4461
+ }
4462
+ function boundsOf(rects) {
4463
+ if (!rects.length) return null;
4464
+ const minX = Math.min(...rects.map((r) => r.x));
4465
+ const minY = Math.min(...rects.map((r) => r.y));
4466
+ const maxX = Math.max(...rects.map((r) => r.x + r.width));
4467
+ const maxY = Math.max(...rects.map((r) => r.y + r.height));
4468
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
4469
+ }
4470
+ function centre(r) {
4471
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
4472
+ }
4473
+ function overlaps(a, b, pad = 16) {
4474
+ return a.x < b.x + b.width + pad && a.x + a.width + pad > b.x && a.y < b.y + b.height + pad && a.y + a.height + pad > b.y;
4475
+ }
4476
+ function flowSide(direction = "LR") {
4477
+ switch (direction) {
4478
+ case "LR":
4479
+ return "right";
4480
+ case "RL":
4481
+ return "left";
4482
+ case "BT":
4483
+ return "above";
4484
+ case "TB":
4485
+ default:
4486
+ return "below";
4487
+ }
4488
+ }
4489
+ function ensureLayout(nodes, edges, options = {}) {
4490
+ if (nodes.length === 0) return [];
4491
+ const stored = options.storedPositions ?? {};
4492
+ const missing = nodes.filter((n) => !stored[n.id]);
4493
+ if (missing.length === 0) {
4494
+ return nodes.map((n) => ({
4495
+ ...n,
4496
+ position: { ...stored[n.id] }
4497
+ }));
4498
+ }
4499
+ const autoLayouted = getLayoutedNodes(nodes, edges, void 0, options);
4500
+ const autoMap = new Map(
4501
+ autoLayouted.map((n) => [n.id, n.position])
4502
+ );
4503
+ const both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));
4504
+ if (both.length === 0) {
4505
+ return autoLayouted;
4506
+ }
4507
+ let dx = 0;
4508
+ let dy = 0;
4509
+ for (const n of both) {
4510
+ const s = stored[n.id];
4511
+ const a = autoMap.get(n.id);
4512
+ dx += s.x - a.x;
4513
+ dy += s.y - a.y;
4514
+ }
4515
+ dx /= both.length;
4516
+ dy /= both.length;
4517
+ return nodes.map((node) => {
4518
+ if (stored[node.id]) {
4519
+ return {
4520
+ ...node,
4521
+ position: { ...stored[node.id] }
4522
+ };
4523
+ }
4524
+ const autoPos = autoMap.get(node.id) ?? { x: 0, y: 0 };
4525
+ return {
4526
+ ...node,
4527
+ position: {
4528
+ x: Math.round(autoPos.x + dx),
4529
+ y: Math.round(autoPos.y + dy)
4530
+ }
4531
+ };
4532
+ });
4533
+ }
4534
+ function placeBeside(rects, size, side, nearNodeId, options = {}) {
4535
+ const {
4536
+ gap = DEFAULT_PLACEMENT_GAP,
4537
+ ignore = [],
4538
+ maxAttempts = 32
4539
+ } = options;
4540
+ const skip = new Set([nearNodeId, ...ignore].filter(Boolean));
4541
+ const obstacles = Object.entries(rects).filter(([id]) => !skip.has(id)).map(([, r]) => r);
4542
+ const anchor = nearNodeId && rects[nearNodeId] || boundsOf(Object.values(rects)) || { x: 0, y: 0, width: 0, height: 0 };
4543
+ const anchorCenter = centre(anchor);
4544
+ let baseX;
4545
+ let baseY;
4546
+ switch (side) {
4547
+ case "right":
4548
+ baseX = anchor.x + anchor.width + gap;
4549
+ baseY = anchorCenter.y - size.height / 2;
4550
+ break;
4551
+ case "left":
4552
+ baseX = anchor.x - gap - size.width;
4553
+ baseY = anchorCenter.y - size.height / 2;
4554
+ break;
4555
+ case "above":
4556
+ baseX = anchorCenter.x - size.width / 2;
4557
+ baseY = anchor.y - gap - size.height;
4558
+ break;
4559
+ case "below":
4560
+ default:
4561
+ baseX = anchorCenter.x - size.width / 2;
4562
+ baseY = anchor.y + anchor.height + gap;
4563
+ break;
4564
+ }
4565
+ const isHorizontal = side === "right" || side === "left";
4566
+ const step = (isHorizontal ? size.height : size.width) + 20;
4567
+ for (let i = 0; i < maxAttempts; i++) {
4568
+ const offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);
4569
+ const candidateBox = {
4570
+ x: isHorizontal ? baseX : baseX + offset,
4571
+ y: isHorizontal ? baseY + offset : baseY,
4572
+ width: size.width,
4573
+ height: size.height
4574
+ };
4575
+ if (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {
4576
+ return {
4577
+ x: Math.round(candidateBox.x),
4578
+ y: Math.round(candidateBox.y)
4579
+ };
4580
+ }
4581
+ }
4582
+ return {
4583
+ x: Math.round(baseX),
4584
+ y: Math.round(baseY)
4585
+ };
4586
+ }
4587
+
4588
+ // src/canvas/trajectory.ts
4589
+ var defaultEasing = (t) => --t * t * t + 1;
4590
+ var linearTrajectory = (origin, target, progress) => {
4591
+ return {
4592
+ position: {
4593
+ x: origin.x + (target.x - origin.x) * progress,
4594
+ y: origin.y + (target.y - origin.y) * progress
4595
+ },
4596
+ scale: progress,
4597
+ opacity: Math.min(1, progress * 1.5)
4598
+ };
4599
+ };
4600
+ var linearPositionTrajectory = (origin, target, progress) => {
4601
+ return {
4602
+ position: {
4603
+ x: origin.x + (target.x - origin.x) * progress,
4604
+ y: origin.y + (target.y - origin.y) * progress
4605
+ }
4606
+ };
4607
+ };
4608
+ function createFanOutTrajectory(options = {}) {
4609
+ const {
4610
+ curvature = 0.4,
4611
+ startScale = 0,
4612
+ endScale = 1,
4613
+ startOpacity = 0,
4614
+ endOpacity = 1
4615
+ } = options;
4616
+ return (origin, target, progress, index, total) => {
4617
+ const dx = target.x - origin.x;
4618
+ const dy = target.y - origin.y;
4619
+ const normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;
4620
+ const midX = origin.x + dx * 0.4;
4621
+ const midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;
4622
+ const t = progress;
4623
+ const oneMinusT = 1 - t;
4624
+ const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;
4625
+ const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;
4626
+ const scale = startScale + (endScale - startScale) * progress;
4627
+ const opacity = startOpacity + (endOpacity - startOpacity) * progress;
4628
+ return {
4629
+ position: { x, y },
4630
+ scale,
4631
+ opacity
4632
+ };
4633
+ };
4634
+ }
4635
+ function createBezierTrajectory(getControlPoint) {
4636
+ return (origin, target, progress) => {
4637
+ const cp = getControlPoint(origin, target);
4638
+ const t = progress;
4639
+ const oneMinusT = 1 - t;
4640
+ const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;
4641
+ const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;
4642
+ return {
4643
+ position: { x, y },
4644
+ scale: progress,
4645
+ opacity: progress
4646
+ };
4647
+ };
4648
+ }
4649
+ function runMultiNodeTransition(transitions, options = {}) {
4650
+ if (transitions.length === 0) {
4651
+ options.onComplete?.();
4652
+ return () => {
4653
+ };
4654
+ }
4655
+ const { defaultDuration = 400, defaultEasing: easingFunc = defaultEasing, onUpdate, onComplete } = options;
4656
+ let isCancelled = false;
4657
+ const startTime = Date.now();
4658
+ const items = transitions.map((tr, i) => ({
4659
+ ...tr,
4660
+ index: tr.index ?? i,
4661
+ total: tr.total ?? transitions.length,
4662
+ duration: tr.duration ?? defaultDuration,
4663
+ easing: tr.easing ?? easingFunc,
4664
+ delay: tr.delay ?? 0,
4665
+ trajectory: tr.trajectory ?? linearTrajectory
4666
+ }));
4667
+ function frame() {
4668
+ if (isCancelled) return;
4669
+ const now = Date.now();
4670
+ let allCompleted = true;
4671
+ for (let i = 0; i < items.length; i++) {
4672
+ const item = items[i];
4673
+ const elapsed = now - (startTime + item.delay);
4674
+ if (elapsed < 0) {
4675
+ allCompleted = false;
4676
+ continue;
4677
+ }
4678
+ const rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);
4679
+ const progress = item.duration <= 0 ? 1 : item.easing(rawProgress);
4680
+ const point = item.trajectory(item.from, item.to, progress, item.index, item.total);
4681
+ item.node.position = { ...point.position };
4682
+ if (point.scale !== void 0 || point.opacity !== void 0) {
4683
+ if (!item.node.data) {
4684
+ item.node.data = {};
4685
+ }
4686
+ if (point.scale !== void 0) {
4687
+ item.node.data.scale = point.scale;
4688
+ }
4689
+ if (point.opacity !== void 0) {
4690
+ item.node.data.opacity = point.opacity;
4691
+ const currentStyle = (item.node.style || "").replace(/opacity:\s*[^;]+;?/g, "").trim();
4692
+ item.node.style = `${currentStyle ? currentStyle + "; " : ""}opacity: ${point.opacity};`;
4693
+ }
4694
+ }
4695
+ if (rawProgress < 1) {
4696
+ allCompleted = false;
4697
+ }
4698
+ }
4699
+ onUpdate?.();
4700
+ if (!allCompleted) {
4701
+ if (typeof requestAnimationFrame !== "undefined") {
4702
+ requestAnimationFrame(frame);
4703
+ }
4704
+ } else {
4705
+ onComplete?.();
4706
+ }
4707
+ }
4708
+ frame();
4709
+ return () => {
4710
+ isCancelled = true;
4711
+ };
4712
+ }
4713
+
4714
+ // src/canvas/displacement.ts
4715
+ var import_dagre2 = __toESM(require("@dagrejs/dagre"), 1);
4716
+ function computeNodesBoundingBox(nodes, defaultWidth = 200, defaultHeight = 60) {
4717
+ if (nodes.length === 0) {
4718
+ return { x: 0, y: 0, width: 0, height: 0 };
4719
+ }
4720
+ let minX = Infinity;
4721
+ let minY = Infinity;
4722
+ let maxX = -Infinity;
4723
+ let maxY = -Infinity;
4724
+ for (const node of nodes) {
4725
+ const width = node.measured?.width ?? node.width ?? defaultWidth;
4726
+ const height = node.measured?.height ?? node.height ?? defaultHeight;
4727
+ minX = Math.min(minX, node.position.x);
4728
+ minY = Math.min(minY, node.position.y);
4729
+ maxX = Math.max(maxX, node.position.x + width);
4730
+ maxY = Math.max(maxY, node.position.y + height);
4731
+ }
4732
+ return {
4733
+ x: minX,
4734
+ y: minY,
4735
+ width: maxX - minX,
4736
+ height: maxY - minY
4737
+ };
4738
+ }
4739
+ function calculateDirectionalDisplacement(params) {
4740
+ const {
4741
+ existingNodes,
4742
+ originNode,
4743
+ childNodes: childNodes2,
4744
+ getNodesBounds,
4745
+ options = {}
4746
+ } = params;
4747
+ const {
4748
+ direction = "right",
4749
+ gap = 40,
4750
+ spreadSiblingLanes = false,
4751
+ siblingTolerance = 50,
4752
+ minY
4753
+ } = options;
4754
+ const displacedMap = /* @__PURE__ */ new Map();
4755
+ if (childNodes2.length === 0 || existingNodes.length === 0) {
4756
+ return displacedMap;
4757
+ }
4758
+ const childBounds = getNodesBounds ? getNodesBounds(childNodes2) : computeNodesBoundingBox(childNodes2);
4759
+ const originWidth = originNode.measured?.width ?? originNode.width ?? 200;
4760
+ const originHeight = originNode.measured?.height ?? originNode.height ?? 60;
4761
+ const deltaPrimary = direction === "right" || direction === "left" ? childBounds.width + gap : childBounds.height + gap;
4762
+ const deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);
4763
+ const deltaDown = Math.max(
4764
+ 0,
4765
+ childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap
4766
+ );
4767
+ const equalDelta = Math.max(deltaUp, deltaDown);
4768
+ for (const node of existingNodes) {
4769
+ if (node.id === originNode.id) continue;
4770
+ const isSiblingRow = Math.abs(node.position.y - originNode.position.y) < siblingTolerance;
4771
+ switch (direction) {
4772
+ case "right": {
4773
+ if (node.position.x > originNode.position.x) {
4774
+ displacedMap.set(node.id, {
4775
+ x: node.position.x + deltaPrimary,
4776
+ y: node.position.y
4777
+ });
4778
+ }
4779
+ break;
4780
+ }
4781
+ case "left": {
4782
+ if (node.position.x < originNode.position.x) {
4783
+ displacedMap.set(node.id, {
4784
+ x: node.position.x - deltaPrimary,
4785
+ y: node.position.y
4786
+ });
4787
+ }
4788
+ break;
4789
+ }
4790
+ case "down": {
4791
+ if (node.position.y > originNode.position.y) {
4792
+ displacedMap.set(node.id, {
4793
+ x: node.position.x,
4794
+ y: node.position.y + deltaPrimary
4795
+ });
4796
+ }
4797
+ break;
4798
+ }
4799
+ case "up": {
4800
+ if (node.position.y < originNode.position.y) {
4801
+ displacedMap.set(node.id, {
4802
+ x: node.position.x,
4803
+ y: node.position.y - deltaPrimary
4804
+ });
4805
+ }
4806
+ break;
4807
+ }
4808
+ case "vertical": {
4809
+ if (isSiblingRow) break;
4810
+ if (node.position.y < originNode.position.y) {
4811
+ displacedMap.set(node.id, {
4812
+ x: node.position.x,
4813
+ y: node.position.y - equalDelta
4814
+ });
4815
+ } else if (node.position.y > originNode.position.y) {
4816
+ displacedMap.set(node.id, {
4817
+ x: node.position.x,
4818
+ y: node.position.y + equalDelta
4819
+ });
4820
+ }
4821
+ break;
4822
+ }
4823
+ }
4824
+ }
4825
+ if (spreadSiblingLanes && (direction === "right" || direction === "left")) {
4826
+ for (const [nodeId, pos] of displacedMap.entries()) {
4827
+ const node = existingNodes.find((n) => n.id === nodeId);
4828
+ if (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {
4829
+ const isAbove = node.position.y < originNode.position.y;
4830
+ displacedMap.set(nodeId, {
4831
+ x: pos.x,
4832
+ y: pos.y + (isAbove ? -deltaUp : deltaDown)
4833
+ });
4834
+ }
4835
+ }
4836
+ }
4837
+ if (minY !== void 0) {
4838
+ for (const [nodeId, pos] of displacedMap.entries()) {
4839
+ if (pos.y < minY) {
4840
+ displacedMap.set(nodeId, { x: pos.x, y: minY });
4841
+ }
4842
+ }
4843
+ }
4844
+ return displacedMap;
4845
+ }
4846
+ function calculateDagreReflowDisplacement(params) {
4847
+ const {
4848
+ currentNodes,
4849
+ futureNodes,
4850
+ futureEdges,
4851
+ options
4852
+ } = params;
4853
+ const {
4854
+ anchorNodeId,
4855
+ direction = "LR",
4856
+ ranksep = 100,
4857
+ nodesep = 25,
4858
+ defaultNodeWidth = 200,
4859
+ defaultNodeHeight = 60
4860
+ } = options;
4861
+ const anchorNode = currentNodes.find((n) => n.id === anchorNodeId);
4862
+ if (!anchorNode) return /* @__PURE__ */ new Map();
4863
+ const g = new import_dagre2.default.graphlib.Graph();
4864
+ g.setDefaultEdgeLabel(() => ({}));
4865
+ g.setGraph({ rankdir: direction, ranksep, nodesep });
4866
+ const nodeIds = new Set(futureNodes.map((n) => n.id));
4867
+ futureNodes.forEach((node) => {
4868
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
4869
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
4870
+ g.setNode(node.id, { width: nodeWidth, height: nodeHeight });
4871
+ });
4872
+ futureEdges.forEach((edge) => {
4873
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
4874
+ g.setEdge(edge.source, edge.target);
4875
+ }
4876
+ });
4877
+ import_dagre2.default.layout(g);
4878
+ const anchorLayoutPos = g.node(anchorNodeId);
4879
+ if (!anchorLayoutPos) return /* @__PURE__ */ new Map();
4880
+ const anchorWidth = anchorNode.measured?.width ?? anchorNode.width ?? defaultNodeWidth;
4881
+ const anchorHeight = anchorNode.measured?.height ?? anchorNode.height ?? defaultNodeHeight;
4882
+ const anchorLayoutUpperLeft = {
4883
+ x: anchorLayoutPos.x - anchorWidth / 2,
4884
+ y: anchorLayoutPos.y - anchorHeight / 2
4885
+ };
4886
+ const deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;
4887
+ const deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;
4888
+ const displacementMap = /* @__PURE__ */ new Map();
4889
+ currentNodes.forEach((node) => {
4890
+ if (node.id === anchorNodeId) return;
4891
+ const layoutNode = g.node(node.id);
4892
+ if (!layoutNode) return;
4893
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
4894
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
4895
+ const upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;
4896
+ const upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;
4897
+ if (Math.abs(upperLeftX - node.position.x) > 1 || Math.abs(upperLeftY - node.position.y) > 1) {
4898
+ displacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });
4899
+ }
4900
+ });
4901
+ return displacementMap;
4902
+ }
4903
+
4904
+ // src/canvas/clipboard.ts
4905
+ function isEventFromTextInput(event) {
4906
+ const target = event.target;
4907
+ if (!target) return false;
4908
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable || target.closest?.('[contenteditable="true"]') || target.closest?.(".ProseMirror")) {
4909
+ return true;
4910
+ }
4911
+ return false;
4912
+ }
4913
+ function isValidCanvasNode(obj) {
4914
+ return typeof obj === "object" && obj !== null && "id" in obj && typeof obj.id === "string" && "position" in obj && typeof obj.position === "object" && obj.position !== null && "x" in obj.position && "y" in obj.position;
4915
+ }
4916
+ function isValidUrl(url) {
4917
+ try {
4918
+ const parsed = new URL(url);
4919
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
4920
+ } catch {
4921
+ return false;
4922
+ }
4923
+ }
4924
+ function serializeCanvasNodes(nodes, edges) {
4925
+ const payload = {
4926
+ version: 1,
4927
+ nodes,
4928
+ edges
4929
+ };
4930
+ return JSON.stringify(payload);
4931
+ }
4932
+ function parseCanvasClipboardData(raw) {
4933
+ try {
4934
+ const parsed = JSON.parse(raw);
4935
+ if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.nodes) && parsed.nodes.length > 0 && parsed.nodes.every(isValidCanvasNode)) {
4936
+ return parsed;
4937
+ }
4938
+ } catch {
4939
+ }
4940
+ return null;
4941
+ }
4942
+ function remapPastedNodes(nodes, edges = [], offset = { x: 30, y: 30 }, idGenerator = () => `node-${Math.random().toString(36).substring(2, 9)}`) {
4943
+ const idMap = /* @__PURE__ */ new Map();
4944
+ const newNodes = nodes.map((node) => {
4945
+ const newId = idGenerator();
4946
+ idMap.set(node.id, newId);
4947
+ return {
4948
+ ...node,
4949
+ id: newId,
4950
+ position: {
4951
+ x: node.position.x + offset.x,
4952
+ y: node.position.y + offset.y
4953
+ },
4954
+ selected: true
4955
+ };
4956
+ });
4957
+ const newEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((edge) => ({
4958
+ ...edge,
4959
+ id: `edge-${Math.random().toString(36).substring(2, 9)}`,
4960
+ source: idMap.get(edge.source),
4961
+ target: idMap.get(edge.target),
4962
+ selected: false
4963
+ }));
4964
+ return { nodes: newNodes, edges: newEdges };
4965
+ }
4966
+ function repositionNodesTo(nodes, targetOrigin) {
4967
+ if (nodes.length === 0) return [];
4968
+ const minX = Math.min(...nodes.map((n) => n.position.x));
4969
+ const minY = Math.min(...nodes.map((n) => n.position.y));
4970
+ const deltaX = targetOrigin.x - minX;
4971
+ const deltaY = targetOrigin.y - minY;
4972
+ return nodes.map((n) => ({
4973
+ ...n,
4974
+ position: {
4975
+ x: n.position.x + deltaX,
4976
+ y: n.position.y + deltaY
4977
+ }
4978
+ }));
4979
+ }
4980
+
4981
+ // src/canvas/streaming.ts
4982
+ var StreamingLayoutManager = class {
4983
+ throttleMs;
4984
+ glideDurationMs;
4985
+ layoutOptions;
4986
+ onLayoutUpdated;
4987
+ pendingTimer = null;
4988
+ activeTransitionCancel = null;
4989
+ targetPositions = /* @__PURE__ */ new Map();
4990
+ constructor(options = {}) {
4991
+ this.throttleMs = options.throttleMs ?? 200;
4992
+ this.glideDurationMs = options.glideDurationMs ?? 300;
4993
+ this.layoutOptions = options.layoutOptions ?? { direction: "LR" };
4994
+ this.onLayoutUpdated = options.onLayoutUpdated;
4995
+ }
4996
+ /**
4997
+ * Feed incoming nodes and edges into the streaming manager.
4998
+ * Coalesces rapid streaming bursts and triggers smooth layout gliding.
4999
+ */
5000
+ pushStreamUpdate(nodes, edges, currentPositions) {
5001
+ if (this.pendingTimer) {
5002
+ clearTimeout(this.pendingTimer);
5003
+ }
5004
+ this.pendingTimer = setTimeout(() => {
5005
+ this.pendingTimer = null;
5006
+ this.recalculateAndGlide(nodes, edges, currentPositions);
5007
+ }, this.throttleMs);
5008
+ }
5009
+ /**
5010
+ * Forces immediate calculation and cancels pending throttled timers.
5011
+ */
5012
+ flush(nodes, edges, currentPositions) {
5013
+ if (this.pendingTimer) {
5014
+ clearTimeout(this.pendingTimer);
5015
+ this.pendingTimer = null;
5016
+ }
5017
+ this.recalculateAndGlide(nodes, edges, currentPositions);
5018
+ }
5019
+ recalculateAndGlide(nodes, edges, currentPositions) {
5020
+ if (nodes.length === 0) return;
5021
+ if (this.activeTransitionCancel) {
5022
+ this.activeTransitionCancel();
5023
+ this.activeTransitionCancel = null;
5024
+ }
5025
+ const stored = {};
5026
+ for (const [id, pos] of currentPositions.entries()) {
5027
+ stored[id] = pos;
5028
+ }
5029
+ const layouted = ensureLayout(nodes, edges, {
5030
+ ...this.layoutOptions,
5031
+ storedPositions: stored
5032
+ });
5033
+ const nextPositions = /* @__PURE__ */ new Map();
5034
+ const transitions = [];
5035
+ for (const node of layouted) {
5036
+ const target = node.position;
5037
+ nextPositions.set(node.id, target);
5038
+ const current = currentPositions.get(node.id) ?? target;
5039
+ const distance = Math.hypot(target.x - current.x, target.y - current.y);
5040
+ if (distance > 2) {
5041
+ transitions.push({
5042
+ node,
5043
+ from: current,
5044
+ to: target,
5045
+ trajectory: linearPositionTrajectory,
5046
+ duration: this.glideDurationMs
5047
+ });
5048
+ }
5049
+ }
5050
+ this.targetPositions = nextPositions;
5051
+ if (transitions.length > 0) {
5052
+ this.activeTransitionCancel = runMultiNodeTransition(transitions, {
5053
+ onUpdate: () => {
5054
+ const liveMap = new Map(currentPositions);
5055
+ for (const trans of transitions) {
5056
+ liveMap.set(trans.node.id, { ...trans.node.position });
5057
+ }
5058
+ this.onLayoutUpdated?.(liveMap);
5059
+ },
5060
+ onComplete: () => {
5061
+ this.activeTransitionCancel = null;
5062
+ this.onLayoutUpdated?.(nextPositions);
5063
+ }
5064
+ });
5065
+ } else {
5066
+ this.onLayoutUpdated?.(nextPositions);
5067
+ }
5068
+ }
5069
+ destroy() {
5070
+ if (this.pendingTimer) {
5071
+ clearTimeout(this.pendingTimer);
5072
+ this.pendingTimer = null;
5073
+ }
5074
+ if (this.activeTransitionCancel) {
5075
+ this.activeTransitionCancel();
5076
+ this.activeTransitionCancel = null;
5077
+ }
5078
+ }
5079
+ };
5080
+
5081
+ // src/agent/planner.ts
5082
+ function planAgentCommand(command, snapshot, options = {}) {
5083
+ const defaultNodeSize = options.defaultNodeSize ?? { width: 200, height: 60 };
5084
+ const idGen = options.idGenerator ?? (() => `node-${Math.random().toString(36).substring(2, 9)}`);
5085
+ const edgeIdGen = () => `edge-${Math.random().toString(36).substring(2, 9)}`;
5086
+ const nodes = snapshot.canvas?.nodes ?? [];
5087
+ const edges = snapshot.canvas?.edges ?? [];
5088
+ switch (command.type) {
5089
+ case "canvas:add_node": {
5090
+ let pos = command.node.position;
5091
+ if (!pos) {
5092
+ if (command.nearNodeId) {
5093
+ const rects = {};
5094
+ for (const n of nodes) {
5095
+ rects[n.id] = {
5096
+ x: n.position.x,
5097
+ y: n.position.y,
5098
+ width: n.measured?.width ?? n.width ?? defaultNodeSize.width,
5099
+ height: n.measured?.height ?? n.height ?? defaultNodeSize.height
5100
+ };
5101
+ }
5102
+ pos = placeBeside(
5103
+ rects,
5104
+ defaultNodeSize,
5105
+ command.side ?? "right",
5106
+ command.nearNodeId
5107
+ );
5108
+ } else if (nodes.length > 0) {
5109
+ const lastNode = nodes[nodes.length - 1];
5110
+ const rects = {};
5111
+ for (const n of nodes) {
5112
+ rects[n.id] = {
5113
+ x: n.position.x,
5114
+ y: n.position.y,
5115
+ width: n.measured?.width ?? n.width ?? defaultNodeSize.width,
5116
+ height: n.measured?.height ?? n.height ?? defaultNodeSize.height
5117
+ };
5118
+ }
5119
+ pos = placeBeside(rects, defaultNodeSize, "right", lastNode.id);
5120
+ } else {
5121
+ pos = { x: 100, y: 100 };
5122
+ }
5123
+ }
5124
+ const fullNode = {
5125
+ ...command.node,
5126
+ id: command.node.id || idGen(),
5127
+ position: pos
5128
+ };
5129
+ const steps = [
5130
+ {
5131
+ type: "step:canvas_add_node",
5132
+ node: fullNode
5133
+ }
5134
+ ];
5135
+ if (command.nearNodeId) {
5136
+ steps.push({
5137
+ type: "step:canvas_add_edge",
5138
+ edge: {
5139
+ id: edgeIdGen(),
5140
+ source: command.nearNodeId,
5141
+ target: fullNode.id
5142
+ }
5143
+ });
5144
+ }
5145
+ return steps;
5146
+ }
5147
+ case "canvas:remove_nodes": {
5148
+ return [
5149
+ {
5150
+ type: "step:canvas_remove_nodes",
5151
+ nodeIds: command.nodeIds
5152
+ }
5153
+ ];
5154
+ }
5155
+ case "canvas:connect": {
5156
+ return [
5157
+ {
5158
+ type: "step:canvas_add_edge",
5159
+ edge: {
5160
+ id: edgeIdGen(),
5161
+ source: command.source,
5162
+ target: command.target,
5163
+ label: command.label
5164
+ }
5165
+ }
5166
+ ];
5167
+ }
5168
+ case "canvas:update_node": {
5169
+ return [
5170
+ {
5171
+ type: "step:canvas_update_node",
5172
+ id: command.id,
5173
+ data: command.data
5174
+ }
5175
+ ];
5176
+ }
5177
+ case "canvas:layout": {
5178
+ const dir = command.direction ?? "LR";
5179
+ let layouted;
5180
+ if (command.incremental) {
5181
+ const stored = {};
5182
+ for (const n of nodes) {
5183
+ if (n.position && (n.position.x !== 0 || n.position.y !== 0)) {
5184
+ stored[n.id] = { ...n.position };
5185
+ }
5186
+ }
5187
+ layouted = ensureLayout(nodes, edges, {
5188
+ direction: dir,
5189
+ storedPositions: stored
5190
+ });
5191
+ } else {
5192
+ layouted = getLayoutedNodes(nodes, edges, void 0, { direction: dir });
5193
+ }
5194
+ const positions = {};
5195
+ for (const node of layouted) {
5196
+ positions[node.id] = { ...node.position };
5197
+ }
5198
+ return [
5199
+ {
5200
+ type: "step:canvas_move_nodes",
5201
+ positions
5202
+ }
5203
+ ];
5204
+ }
5205
+ case "canvas:select": {
5206
+ return [
5207
+ {
5208
+ type: "step:canvas_select",
5209
+ nodeIds: command.nodeIds
5210
+ }
5211
+ ];
5212
+ }
5213
+ case "canvas:highlight": {
5214
+ return [
5215
+ {
5216
+ type: "step:canvas_highlight",
5217
+ nodeIds: command.nodeIds,
5218
+ color: command.color ?? "#3b82f6",
5219
+ durationMs: command.durationMs ?? 2e3
5220
+ }
5221
+ ];
5222
+ }
5223
+ case "prompt:insert_text": {
5224
+ return [
5225
+ {
5226
+ type: "step:prompt_insert",
5227
+ text: command.text,
5228
+ at: command.at
5229
+ }
5230
+ ];
5231
+ }
5232
+ case "prompt:replace_range": {
5233
+ return [
5234
+ {
5235
+ type: "step:prompt_replace",
5236
+ from: command.from,
5237
+ to: command.to,
5238
+ text: command.text
5239
+ }
5240
+ ];
5241
+ }
5242
+ case "prompt:set_variable": {
5243
+ return [
5244
+ {
5245
+ type: "step:prompt_set_variable",
5246
+ name: command.name,
5247
+ value: command.value
5248
+ }
5249
+ ];
5250
+ }
5251
+ case "prompt:select": {
5252
+ return [
5253
+ {
5254
+ type: "step:prompt_select",
5255
+ from: command.from,
5256
+ to: command.to
5257
+ }
5258
+ ];
5259
+ }
5260
+ default: {
5261
+ return [];
5262
+ }
5263
+ }
5264
+ }
5265
+
5266
+ // src/agent/tools.ts
5267
+ var MCP_TOOL_GET_EDITOR_STATE = {
5268
+ name: "get_editor_state",
5269
+ description: "Retrieves the current state of the document, variables, selection, and canvas graph (nodes and edges).",
5270
+ inputSchema: {
5271
+ type: "object",
5272
+ properties: {
5273
+ includeDocument: {
5274
+ type: "boolean",
5275
+ description: "Whether to include the prompt document text and variables (default: true)."
5276
+ },
5277
+ includeCanvas: {
5278
+ type: "boolean",
5279
+ description: "Whether to include the canvas nodes and edges (default: true)."
5280
+ }
5281
+ }
5282
+ }
5283
+ };
5284
+ var MCP_TOOL_EDIT_CANVAS = {
5285
+ name: "edit_canvas",
5286
+ description: "Applies high-level structural operations to the visual canvas (adding nodes, connecting edges, organizing layout, or moving elements).",
5287
+ inputSchema: {
5288
+ type: "object",
5289
+ properties: {
5290
+ operation: {
5291
+ type: "string",
5292
+ enum: [
5293
+ "add_node",
5294
+ "remove_nodes",
5295
+ "connect",
5296
+ "update_node",
5297
+ "layout",
5298
+ "select",
5299
+ "highlight"
5300
+ ],
5301
+ description: "The canvas operation to execute."
5302
+ },
5303
+ node: {
5304
+ type: "object",
5305
+ description: "Node definition when operation is add_node. Can omit position to auto-place."
5306
+ },
5307
+ nearNodeId: {
5308
+ type: "string",
5309
+ description: "Anchor node ID to position beside using collision-aware placement."
5310
+ },
5311
+ side: {
5312
+ type: "string",
5313
+ enum: ["top", "right", "bottom", "left"],
5314
+ description: "Side of the anchor node to place the new node beside."
5315
+ },
5316
+ nodeIds: {
5317
+ type: "array",
5318
+ items: { type: "string" },
5319
+ description: "Array of node IDs for remove_nodes, select, or highlight."
5320
+ },
5321
+ source: {
5322
+ type: "string",
5323
+ description: "Source node ID for connect operation."
5324
+ },
5325
+ target: {
5326
+ type: "string",
5327
+ description: "Target node ID for connect operation."
5328
+ },
5329
+ label: {
5330
+ type: "string",
5331
+ description: "Optional edge label for connect operation."
5332
+ },
5333
+ direction: {
5334
+ type: "string",
5335
+ enum: ["LR", "TB", "RL", "BT"],
5336
+ description: "Layout direction (default LR)."
5337
+ },
5338
+ incremental: {
5339
+ type: "boolean",
5340
+ description: "Preserve existing node coordinates and blend layout (default true)."
5341
+ }
5342
+ },
5343
+ required: ["operation"]
5344
+ }
5345
+ };
5346
+ var MCP_TOOL_EDIT_PROMPT = {
5347
+ name: "edit_prompt",
5348
+ description: "Applies edits to the prompt template document, including inserting text, replacing ranges, and setting variables.",
5349
+ inputSchema: {
5350
+ type: "object",
5351
+ properties: {
5352
+ operation: {
5353
+ type: "string",
5354
+ enum: ["insert_text", "replace_range", "set_variable", "select"],
5355
+ description: "The prompt operation to execute."
5356
+ },
5357
+ text: {
5358
+ type: "string",
5359
+ description: "Text to insert or replace."
5360
+ },
5361
+ at: {
5362
+ type: "number",
5363
+ description: "Position to insert text at."
5364
+ },
5365
+ from: {
5366
+ type: "number",
5367
+ description: "Start offset for replace_range or select."
5368
+ },
5369
+ to: {
5370
+ type: "number",
5371
+ description: "End offset for replace_range or select."
5372
+ },
5373
+ name: {
5374
+ type: "string",
5375
+ description: "Variable name for set_variable."
5376
+ },
5377
+ value: {
5378
+ description: "Variable value for set_variable."
5379
+ }
5380
+ },
5381
+ required: ["operation"]
5382
+ }
5383
+ };
5384
+ var EDITOR_MCP_TOOLS = [
5385
+ MCP_TOOL_GET_EDITOR_STATE,
5386
+ MCP_TOOL_EDIT_CANVAS,
5387
+ MCP_TOOL_EDIT_PROMPT
5388
+ ];
4163
5389
  // Annotate the CommonJS export names for ESM import in node:
4164
5390
  0 && (module.exports = {
4165
5391
  ColorHighlighter,
5392
+ DEFAULT_NODE_HEIGHT,
5393
+ DEFAULT_NODE_WIDTH,
5394
+ DEFAULT_PLACEMENT_GAP,
4166
5395
  DataSourceBinder,
4167
5396
  DependencyGraph,
4168
5397
  DirtyTracker,
5398
+ EDITOR_MCP_TOOLS,
4169
5399
  EditorController,
4170
5400
  FontSize,
4171
5401
  FormulaEvaluator,
@@ -4175,48 +5405,86 @@ function createHistoryManager(options = {}) {
4175
5405
  INSTRUCTION_REGEX,
4176
5406
  Image,
4177
5407
  LoadingNode,
5408
+ MARKDOWN_MENTION_SPLIT_REGEX,
5409
+ MCP_TOOL_EDIT_CANVAS,
5410
+ MCP_TOOL_EDIT_PROMPT,
5411
+ MCP_TOOL_GET_EDITOR_STATE,
5412
+ MENTION_REGEX,
4178
5413
  Markdown,
4179
5414
  Mention,
4180
5415
  SlashCommands,
4181
5416
  SmilieReplacer,
4182
5417
  SpreadsheetController,
5418
+ StreamingLayoutManager,
4183
5419
  TEMPLATE_REGEX,
4184
5420
  TemplateSuggestions,
4185
5421
  VARIABLE_REGEX,
5422
+ boundsOf,
5423
+ calculateDagreReflowDisplacement,
5424
+ calculateDirectionalDisplacement,
5425
+ centerNodes,
5426
+ centre,
4186
5427
  colNameToIndex,
5428
+ computeNodesBoundingBox,
5429
+ createBezierTrajectory,
4187
5430
  createDirtyTracker,
4188
5431
  createEditor,
5432
+ createFanOutTrajectory,
4189
5433
  createHistoryManager,
4190
5434
  createMentionExtension,
4191
5435
  createRichTextPreset,
5436
+ defaultEasing,
4192
5437
  defaultIsEqual,
5438
+ ensureLayout,
4193
5439
  escapeHtml,
5440
+ estimateNodeSize,
4194
5441
  extractCellDependencies,
4195
5442
  extractInstructionNames,
5443
+ extractMentions,
4196
5444
  extractMentionsFromDoc,
4197
5445
  extractTemplateNames,
4198
5446
  extractVariableNames,
4199
5447
  findHexColors,
5448
+ flowSide,
5449
+ getCenteredNodePosition,
4200
5450
  getEditorMarkdown,
4201
5451
  getEditorText,
4202
5452
  getLatestVersion,
5453
+ getLayoutedNodes,
4203
5454
  handleImageInsertion,
4204
5455
  htmlToPlainText,
4205
5456
  imagePastePluginKey,
4206
5457
  indexToColName,
4207
5458
  insertImageNode,
4208
5459
  instructionRegex,
5460
+ isEventFromTextInput,
5461
+ isValidCanvasNode,
5462
+ isValidUrl,
5463
+ linearPositionTrajectory,
5464
+ linearTrajectory,
4209
5465
  mapRawOffsetToResolved,
4210
5466
  mapResolvedOffsetToRaw,
4211
5467
  markdownToTipTapHtml,
5468
+ overlaps,
5469
+ parseCanvasClipboardData,
4212
5470
  parseCellReference,
4213
5471
  parseFormula,
5472
+ parseMentionSegments,
4214
5473
  parseRange,
5474
+ placeBeside,
4215
5475
  plainTextToTipTapHtml,
5476
+ planAgentCommand,
5477
+ preprocessMarkdownFileLinks,
5478
+ preprocessMarkdownMentions,
5479
+ remapPastedNodes,
4216
5480
  removeImageNode,
5481
+ repositionNodesTo,
4217
5482
  resolveTemplates,
4218
5483
  resolveTemplatesWithMapping,
4219
5484
  resolveVariables,
5485
+ runMultiNodeTransition,
5486
+ serializeCanvasNodes,
5487
+ stripMarkdownCode,
4220
5488
  templateRegex,
4221
5489
  tokenizeFormula,
4222
5490
  tokenizePrompt,