@pixerate/editor 0.9.0 → 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.
@@ -0,0 +1,797 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/canvas/index.ts
31
+ var canvas_exports = {};
32
+ __export(canvas_exports, {
33
+ DEFAULT_NODE_HEIGHT: () => DEFAULT_NODE_HEIGHT,
34
+ DEFAULT_NODE_WIDTH: () => DEFAULT_NODE_WIDTH,
35
+ DEFAULT_PLACEMENT_GAP: () => DEFAULT_PLACEMENT_GAP,
36
+ StreamingLayoutManager: () => StreamingLayoutManager,
37
+ boundsOf: () => boundsOf,
38
+ calculateDagreReflowDisplacement: () => calculateDagreReflowDisplacement,
39
+ calculateDirectionalDisplacement: () => calculateDirectionalDisplacement,
40
+ centerNodes: () => centerNodes,
41
+ centre: () => centre,
42
+ computeNodesBoundingBox: () => computeNodesBoundingBox,
43
+ createBezierTrajectory: () => createBezierTrajectory,
44
+ createFanOutTrajectory: () => createFanOutTrajectory,
45
+ defaultEasing: () => defaultEasing,
46
+ ensureLayout: () => ensureLayout,
47
+ estimateNodeSize: () => estimateNodeSize,
48
+ flowSide: () => flowSide,
49
+ getCenteredNodePosition: () => getCenteredNodePosition,
50
+ getLayoutedNodes: () => getLayoutedNodes,
51
+ isEventFromTextInput: () => isEventFromTextInput,
52
+ isValidCanvasNode: () => isValidCanvasNode,
53
+ isValidUrl: () => isValidUrl,
54
+ linearPositionTrajectory: () => linearPositionTrajectory,
55
+ linearTrajectory: () => linearTrajectory,
56
+ overlaps: () => overlaps,
57
+ parseCanvasClipboardData: () => parseCanvasClipboardData,
58
+ placeBeside: () => placeBeside,
59
+ remapPastedNodes: () => remapPastedNodes,
60
+ repositionNodesTo: () => repositionNodesTo,
61
+ runMultiNodeTransition: () => runMultiNodeTransition,
62
+ serializeCanvasNodes: () => serializeCanvasNodes
63
+ });
64
+ module.exports = __toCommonJS(canvas_exports);
65
+
66
+ // src/canvas/layout.ts
67
+ var import_dagre = __toESM(require("@dagrejs/dagre"), 1);
68
+ var DEFAULT_NODE_WIDTH = 200;
69
+ var DEFAULT_NODE_HEIGHT = 60;
70
+ var DEFAULT_PLACEMENT_GAP = 48;
71
+ function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
72
+ const rect = screenDimensions ?? (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 });
73
+ const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
74
+ return {
75
+ x: flowPos.x - width / 2,
76
+ y: flowPos.y - height / 2
77
+ };
78
+ }
79
+ function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
80
+ if (nodes.length === 0) {
81
+ return [];
82
+ }
83
+ const bounds = getNodesBounds(nodes);
84
+ const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
85
+ const centeredPosition = getCenteredNodePosition(
86
+ bounds.width,
87
+ bounds.height,
88
+ screenToFlowPosition,
89
+ screenDimensions
90
+ );
91
+ const deltaX = centeredPosition.x - boundingBoxOrigin.x;
92
+ const deltaY = centeredPosition.y - boundingBoxOrigin.y;
93
+ return nodes.map((node) => ({
94
+ ...node,
95
+ position: {
96
+ x: node.position.x + deltaX,
97
+ y: node.position.y + deltaY
98
+ }
99
+ }));
100
+ }
101
+ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
102
+ if (nodes.length === 0) return [];
103
+ const {
104
+ direction = "LR",
105
+ ranksep = 100,
106
+ nodesep = 25,
107
+ defaultNodeWidth = DEFAULT_NODE_WIDTH,
108
+ defaultNodeHeight = DEFAULT_NODE_HEIGHT
109
+ } = options;
110
+ const dagreGraph = new import_dagre.default.graphlib.Graph();
111
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
112
+ dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
113
+ const nodeIds = new Set(nodes.map((node) => node.id));
114
+ nodes.forEach((node) => {
115
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
116
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
117
+ dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
118
+ });
119
+ edges.forEach((edge) => {
120
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
121
+ dagreGraph.setEdge(edge.source, edge.target);
122
+ }
123
+ });
124
+ import_dagre.default.layout(dagreGraph);
125
+ return nodes.map((node) => {
126
+ const nodeWithPosition = dagreGraph.node(node.id);
127
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
128
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
129
+ return {
130
+ ...node,
131
+ position: {
132
+ x: nodeWithPosition.x - nodeWidth / 2,
133
+ y: nodeWithPosition.y - nodeHeight / 2
134
+ }
135
+ };
136
+ });
137
+ }
138
+ function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
139
+ const label = String(node.data?.label ?? node.data?.title ?? node.id ?? "");
140
+ const lines = label.split(/\n|<br\s*\/?>/);
141
+ const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
142
+ const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
143
+ const height = Math.max(defaultHeight, lines.length * 20 + 24);
144
+ return { width, height };
145
+ }
146
+ function boundsOf(rects) {
147
+ if (!rects.length) return null;
148
+ const minX = Math.min(...rects.map((r) => r.x));
149
+ const minY = Math.min(...rects.map((r) => r.y));
150
+ const maxX = Math.max(...rects.map((r) => r.x + r.width));
151
+ const maxY = Math.max(...rects.map((r) => r.y + r.height));
152
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
153
+ }
154
+ function centre(r) {
155
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
156
+ }
157
+ function overlaps(a, b, pad = 16) {
158
+ 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;
159
+ }
160
+ function flowSide(direction = "LR") {
161
+ switch (direction) {
162
+ case "LR":
163
+ return "right";
164
+ case "RL":
165
+ return "left";
166
+ case "BT":
167
+ return "above";
168
+ case "TB":
169
+ default:
170
+ return "below";
171
+ }
172
+ }
173
+ function ensureLayout(nodes, edges, options = {}) {
174
+ if (nodes.length === 0) return [];
175
+ const stored = options.storedPositions ?? {};
176
+ const missing = nodes.filter((n) => !stored[n.id]);
177
+ if (missing.length === 0) {
178
+ return nodes.map((n) => ({
179
+ ...n,
180
+ position: { ...stored[n.id] }
181
+ }));
182
+ }
183
+ const autoLayouted = getLayoutedNodes(nodes, edges, void 0, options);
184
+ const autoMap = new Map(
185
+ autoLayouted.map((n) => [n.id, n.position])
186
+ );
187
+ const both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));
188
+ if (both.length === 0) {
189
+ return autoLayouted;
190
+ }
191
+ let dx = 0;
192
+ let dy = 0;
193
+ for (const n of both) {
194
+ const s = stored[n.id];
195
+ const a = autoMap.get(n.id);
196
+ dx += s.x - a.x;
197
+ dy += s.y - a.y;
198
+ }
199
+ dx /= both.length;
200
+ dy /= both.length;
201
+ return nodes.map((node) => {
202
+ if (stored[node.id]) {
203
+ return {
204
+ ...node,
205
+ position: { ...stored[node.id] }
206
+ };
207
+ }
208
+ const autoPos = autoMap.get(node.id) ?? { x: 0, y: 0 };
209
+ return {
210
+ ...node,
211
+ position: {
212
+ x: Math.round(autoPos.x + dx),
213
+ y: Math.round(autoPos.y + dy)
214
+ }
215
+ };
216
+ });
217
+ }
218
+ function placeBeside(rects, size, side, nearNodeId, options = {}) {
219
+ const {
220
+ gap = DEFAULT_PLACEMENT_GAP,
221
+ ignore = [],
222
+ maxAttempts = 32
223
+ } = options;
224
+ const skip = new Set([nearNodeId, ...ignore].filter(Boolean));
225
+ const obstacles = Object.entries(rects).filter(([id]) => !skip.has(id)).map(([, r]) => r);
226
+ const anchor = nearNodeId && rects[nearNodeId] || boundsOf(Object.values(rects)) || { x: 0, y: 0, width: 0, height: 0 };
227
+ const anchorCenter = centre(anchor);
228
+ let baseX;
229
+ let baseY;
230
+ switch (side) {
231
+ case "right":
232
+ baseX = anchor.x + anchor.width + gap;
233
+ baseY = anchorCenter.y - size.height / 2;
234
+ break;
235
+ case "left":
236
+ baseX = anchor.x - gap - size.width;
237
+ baseY = anchorCenter.y - size.height / 2;
238
+ break;
239
+ case "above":
240
+ baseX = anchorCenter.x - size.width / 2;
241
+ baseY = anchor.y - gap - size.height;
242
+ break;
243
+ case "below":
244
+ default:
245
+ baseX = anchorCenter.x - size.width / 2;
246
+ baseY = anchor.y + anchor.height + gap;
247
+ break;
248
+ }
249
+ const isHorizontal = side === "right" || side === "left";
250
+ const step = (isHorizontal ? size.height : size.width) + 20;
251
+ for (let i = 0; i < maxAttempts; i++) {
252
+ const offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);
253
+ const candidateBox = {
254
+ x: isHorizontal ? baseX : baseX + offset,
255
+ y: isHorizontal ? baseY + offset : baseY,
256
+ width: size.width,
257
+ height: size.height
258
+ };
259
+ if (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {
260
+ return {
261
+ x: Math.round(candidateBox.x),
262
+ y: Math.round(candidateBox.y)
263
+ };
264
+ }
265
+ }
266
+ return {
267
+ x: Math.round(baseX),
268
+ y: Math.round(baseY)
269
+ };
270
+ }
271
+
272
+ // src/canvas/trajectory.ts
273
+ var defaultEasing = (t) => --t * t * t + 1;
274
+ var linearTrajectory = (origin, target, progress) => {
275
+ return {
276
+ position: {
277
+ x: origin.x + (target.x - origin.x) * progress,
278
+ y: origin.y + (target.y - origin.y) * progress
279
+ },
280
+ scale: progress,
281
+ opacity: Math.min(1, progress * 1.5)
282
+ };
283
+ };
284
+ var linearPositionTrajectory = (origin, target, progress) => {
285
+ return {
286
+ position: {
287
+ x: origin.x + (target.x - origin.x) * progress,
288
+ y: origin.y + (target.y - origin.y) * progress
289
+ }
290
+ };
291
+ };
292
+ function createFanOutTrajectory(options = {}) {
293
+ const {
294
+ curvature = 0.4,
295
+ startScale = 0,
296
+ endScale = 1,
297
+ startOpacity = 0,
298
+ endOpacity = 1
299
+ } = options;
300
+ return (origin, target, progress, index, total) => {
301
+ const dx = target.x - origin.x;
302
+ const dy = target.y - origin.y;
303
+ const normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;
304
+ const midX = origin.x + dx * 0.4;
305
+ const midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;
306
+ const t = progress;
307
+ const oneMinusT = 1 - t;
308
+ const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;
309
+ const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;
310
+ const scale = startScale + (endScale - startScale) * progress;
311
+ const opacity = startOpacity + (endOpacity - startOpacity) * progress;
312
+ return {
313
+ position: { x, y },
314
+ scale,
315
+ opacity
316
+ };
317
+ };
318
+ }
319
+ function createBezierTrajectory(getControlPoint) {
320
+ return (origin, target, progress) => {
321
+ const cp = getControlPoint(origin, target);
322
+ const t = progress;
323
+ const oneMinusT = 1 - t;
324
+ const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;
325
+ const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;
326
+ return {
327
+ position: { x, y },
328
+ scale: progress,
329
+ opacity: progress
330
+ };
331
+ };
332
+ }
333
+ function runMultiNodeTransition(transitions, options = {}) {
334
+ if (transitions.length === 0) {
335
+ options.onComplete?.();
336
+ return () => {
337
+ };
338
+ }
339
+ const { defaultDuration = 400, defaultEasing: easingFunc = defaultEasing, onUpdate, onComplete } = options;
340
+ let isCancelled = false;
341
+ const startTime = Date.now();
342
+ const items = transitions.map((tr, i) => ({
343
+ ...tr,
344
+ index: tr.index ?? i,
345
+ total: tr.total ?? transitions.length,
346
+ duration: tr.duration ?? defaultDuration,
347
+ easing: tr.easing ?? easingFunc,
348
+ delay: tr.delay ?? 0,
349
+ trajectory: tr.trajectory ?? linearTrajectory
350
+ }));
351
+ function frame() {
352
+ if (isCancelled) return;
353
+ const now = Date.now();
354
+ let allCompleted = true;
355
+ for (let i = 0; i < items.length; i++) {
356
+ const item = items[i];
357
+ const elapsed = now - (startTime + item.delay);
358
+ if (elapsed < 0) {
359
+ allCompleted = false;
360
+ continue;
361
+ }
362
+ const rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);
363
+ const progress = item.duration <= 0 ? 1 : item.easing(rawProgress);
364
+ const point = item.trajectory(item.from, item.to, progress, item.index, item.total);
365
+ item.node.position = { ...point.position };
366
+ if (point.scale !== void 0 || point.opacity !== void 0) {
367
+ if (!item.node.data) {
368
+ item.node.data = {};
369
+ }
370
+ if (point.scale !== void 0) {
371
+ item.node.data.scale = point.scale;
372
+ }
373
+ if (point.opacity !== void 0) {
374
+ item.node.data.opacity = point.opacity;
375
+ const currentStyle = (item.node.style || "").replace(/opacity:\s*[^;]+;?/g, "").trim();
376
+ item.node.style = `${currentStyle ? currentStyle + "; " : ""}opacity: ${point.opacity};`;
377
+ }
378
+ }
379
+ if (rawProgress < 1) {
380
+ allCompleted = false;
381
+ }
382
+ }
383
+ onUpdate?.();
384
+ if (!allCompleted) {
385
+ if (typeof requestAnimationFrame !== "undefined") {
386
+ requestAnimationFrame(frame);
387
+ }
388
+ } else {
389
+ onComplete?.();
390
+ }
391
+ }
392
+ frame();
393
+ return () => {
394
+ isCancelled = true;
395
+ };
396
+ }
397
+
398
+ // src/canvas/displacement.ts
399
+ var import_dagre2 = __toESM(require("@dagrejs/dagre"), 1);
400
+ function computeNodesBoundingBox(nodes, defaultWidth = 200, defaultHeight = 60) {
401
+ if (nodes.length === 0) {
402
+ return { x: 0, y: 0, width: 0, height: 0 };
403
+ }
404
+ let minX = Infinity;
405
+ let minY = Infinity;
406
+ let maxX = -Infinity;
407
+ let maxY = -Infinity;
408
+ for (const node of nodes) {
409
+ const width = node.measured?.width ?? node.width ?? defaultWidth;
410
+ const height = node.measured?.height ?? node.height ?? defaultHeight;
411
+ minX = Math.min(minX, node.position.x);
412
+ minY = Math.min(minY, node.position.y);
413
+ maxX = Math.max(maxX, node.position.x + width);
414
+ maxY = Math.max(maxY, node.position.y + height);
415
+ }
416
+ return {
417
+ x: minX,
418
+ y: minY,
419
+ width: maxX - minX,
420
+ height: maxY - minY
421
+ };
422
+ }
423
+ function calculateDirectionalDisplacement(params) {
424
+ const {
425
+ existingNodes,
426
+ originNode,
427
+ childNodes,
428
+ getNodesBounds,
429
+ options = {}
430
+ } = params;
431
+ const {
432
+ direction = "right",
433
+ gap = 40,
434
+ spreadSiblingLanes = false,
435
+ siblingTolerance = 50,
436
+ minY
437
+ } = options;
438
+ const displacedMap = /* @__PURE__ */ new Map();
439
+ if (childNodes.length === 0 || existingNodes.length === 0) {
440
+ return displacedMap;
441
+ }
442
+ const childBounds = getNodesBounds ? getNodesBounds(childNodes) : computeNodesBoundingBox(childNodes);
443
+ const originWidth = originNode.measured?.width ?? originNode.width ?? 200;
444
+ const originHeight = originNode.measured?.height ?? originNode.height ?? 60;
445
+ const deltaPrimary = direction === "right" || direction === "left" ? childBounds.width + gap : childBounds.height + gap;
446
+ const deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);
447
+ const deltaDown = Math.max(
448
+ 0,
449
+ childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap
450
+ );
451
+ const equalDelta = Math.max(deltaUp, deltaDown);
452
+ for (const node of existingNodes) {
453
+ if (node.id === originNode.id) continue;
454
+ const isSiblingRow = Math.abs(node.position.y - originNode.position.y) < siblingTolerance;
455
+ switch (direction) {
456
+ case "right": {
457
+ if (node.position.x > originNode.position.x) {
458
+ displacedMap.set(node.id, {
459
+ x: node.position.x + deltaPrimary,
460
+ y: node.position.y
461
+ });
462
+ }
463
+ break;
464
+ }
465
+ case "left": {
466
+ if (node.position.x < originNode.position.x) {
467
+ displacedMap.set(node.id, {
468
+ x: node.position.x - deltaPrimary,
469
+ y: node.position.y
470
+ });
471
+ }
472
+ break;
473
+ }
474
+ case "down": {
475
+ if (node.position.y > originNode.position.y) {
476
+ displacedMap.set(node.id, {
477
+ x: node.position.x,
478
+ y: node.position.y + deltaPrimary
479
+ });
480
+ }
481
+ break;
482
+ }
483
+ case "up": {
484
+ if (node.position.y < originNode.position.y) {
485
+ displacedMap.set(node.id, {
486
+ x: node.position.x,
487
+ y: node.position.y - deltaPrimary
488
+ });
489
+ }
490
+ break;
491
+ }
492
+ case "vertical": {
493
+ if (isSiblingRow) break;
494
+ if (node.position.y < originNode.position.y) {
495
+ displacedMap.set(node.id, {
496
+ x: node.position.x,
497
+ y: node.position.y - equalDelta
498
+ });
499
+ } else if (node.position.y > originNode.position.y) {
500
+ displacedMap.set(node.id, {
501
+ x: node.position.x,
502
+ y: node.position.y + equalDelta
503
+ });
504
+ }
505
+ break;
506
+ }
507
+ }
508
+ }
509
+ if (spreadSiblingLanes && (direction === "right" || direction === "left")) {
510
+ for (const [nodeId, pos] of displacedMap.entries()) {
511
+ const node = existingNodes.find((n) => n.id === nodeId);
512
+ if (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {
513
+ const isAbove = node.position.y < originNode.position.y;
514
+ displacedMap.set(nodeId, {
515
+ x: pos.x,
516
+ y: pos.y + (isAbove ? -deltaUp : deltaDown)
517
+ });
518
+ }
519
+ }
520
+ }
521
+ if (minY !== void 0) {
522
+ for (const [nodeId, pos] of displacedMap.entries()) {
523
+ if (pos.y < minY) {
524
+ displacedMap.set(nodeId, { x: pos.x, y: minY });
525
+ }
526
+ }
527
+ }
528
+ return displacedMap;
529
+ }
530
+ function calculateDagreReflowDisplacement(params) {
531
+ const {
532
+ currentNodes,
533
+ futureNodes,
534
+ futureEdges,
535
+ options
536
+ } = params;
537
+ const {
538
+ anchorNodeId,
539
+ direction = "LR",
540
+ ranksep = 100,
541
+ nodesep = 25,
542
+ defaultNodeWidth = 200,
543
+ defaultNodeHeight = 60
544
+ } = options;
545
+ const anchorNode = currentNodes.find((n) => n.id === anchorNodeId);
546
+ if (!anchorNode) return /* @__PURE__ */ new Map();
547
+ const g = new import_dagre2.default.graphlib.Graph();
548
+ g.setDefaultEdgeLabel(() => ({}));
549
+ g.setGraph({ rankdir: direction, ranksep, nodesep });
550
+ const nodeIds = new Set(futureNodes.map((n) => n.id));
551
+ futureNodes.forEach((node) => {
552
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
553
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
554
+ g.setNode(node.id, { width: nodeWidth, height: nodeHeight });
555
+ });
556
+ futureEdges.forEach((edge) => {
557
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
558
+ g.setEdge(edge.source, edge.target);
559
+ }
560
+ });
561
+ import_dagre2.default.layout(g);
562
+ const anchorLayoutPos = g.node(anchorNodeId);
563
+ if (!anchorLayoutPos) return /* @__PURE__ */ new Map();
564
+ const anchorWidth = anchorNode.measured?.width ?? anchorNode.width ?? defaultNodeWidth;
565
+ const anchorHeight = anchorNode.measured?.height ?? anchorNode.height ?? defaultNodeHeight;
566
+ const anchorLayoutUpperLeft = {
567
+ x: anchorLayoutPos.x - anchorWidth / 2,
568
+ y: anchorLayoutPos.y - anchorHeight / 2
569
+ };
570
+ const deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;
571
+ const deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;
572
+ const displacementMap = /* @__PURE__ */ new Map();
573
+ currentNodes.forEach((node) => {
574
+ if (node.id === anchorNodeId) return;
575
+ const layoutNode = g.node(node.id);
576
+ if (!layoutNode) return;
577
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
578
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
579
+ const upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;
580
+ const upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;
581
+ if (Math.abs(upperLeftX - node.position.x) > 1 || Math.abs(upperLeftY - node.position.y) > 1) {
582
+ displacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });
583
+ }
584
+ });
585
+ return displacementMap;
586
+ }
587
+
588
+ // src/canvas/clipboard.ts
589
+ function isEventFromTextInput(event) {
590
+ const target = event.target;
591
+ if (!target) return false;
592
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable || target.closest?.('[contenteditable="true"]') || target.closest?.(".ProseMirror")) {
593
+ return true;
594
+ }
595
+ return false;
596
+ }
597
+ function isValidCanvasNode(obj) {
598
+ 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;
599
+ }
600
+ function isValidUrl(url) {
601
+ try {
602
+ const parsed = new URL(url);
603
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
604
+ } catch {
605
+ return false;
606
+ }
607
+ }
608
+ function serializeCanvasNodes(nodes, edges) {
609
+ const payload = {
610
+ version: 1,
611
+ nodes,
612
+ edges
613
+ };
614
+ return JSON.stringify(payload);
615
+ }
616
+ function parseCanvasClipboardData(raw) {
617
+ try {
618
+ const parsed = JSON.parse(raw);
619
+ if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.nodes) && parsed.nodes.length > 0 && parsed.nodes.every(isValidCanvasNode)) {
620
+ return parsed;
621
+ }
622
+ } catch {
623
+ }
624
+ return null;
625
+ }
626
+ function remapPastedNodes(nodes, edges = [], offset = { x: 30, y: 30 }, idGenerator = () => `node-${Math.random().toString(36).substring(2, 9)}`) {
627
+ const idMap = /* @__PURE__ */ new Map();
628
+ const newNodes = nodes.map((node) => {
629
+ const newId = idGenerator();
630
+ idMap.set(node.id, newId);
631
+ return {
632
+ ...node,
633
+ id: newId,
634
+ position: {
635
+ x: node.position.x + offset.x,
636
+ y: node.position.y + offset.y
637
+ },
638
+ selected: true
639
+ };
640
+ });
641
+ const newEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((edge) => ({
642
+ ...edge,
643
+ id: `edge-${Math.random().toString(36).substring(2, 9)}`,
644
+ source: idMap.get(edge.source),
645
+ target: idMap.get(edge.target),
646
+ selected: false
647
+ }));
648
+ return { nodes: newNodes, edges: newEdges };
649
+ }
650
+ function repositionNodesTo(nodes, targetOrigin) {
651
+ if (nodes.length === 0) return [];
652
+ const minX = Math.min(...nodes.map((n) => n.position.x));
653
+ const minY = Math.min(...nodes.map((n) => n.position.y));
654
+ const deltaX = targetOrigin.x - minX;
655
+ const deltaY = targetOrigin.y - minY;
656
+ return nodes.map((n) => ({
657
+ ...n,
658
+ position: {
659
+ x: n.position.x + deltaX,
660
+ y: n.position.y + deltaY
661
+ }
662
+ }));
663
+ }
664
+
665
+ // src/canvas/streaming.ts
666
+ var StreamingLayoutManager = class {
667
+ throttleMs;
668
+ glideDurationMs;
669
+ layoutOptions;
670
+ onLayoutUpdated;
671
+ pendingTimer = null;
672
+ activeTransitionCancel = null;
673
+ targetPositions = /* @__PURE__ */ new Map();
674
+ constructor(options = {}) {
675
+ this.throttleMs = options.throttleMs ?? 200;
676
+ this.glideDurationMs = options.glideDurationMs ?? 300;
677
+ this.layoutOptions = options.layoutOptions ?? { direction: "LR" };
678
+ this.onLayoutUpdated = options.onLayoutUpdated;
679
+ }
680
+ /**
681
+ * Feed incoming nodes and edges into the streaming manager.
682
+ * Coalesces rapid streaming bursts and triggers smooth layout gliding.
683
+ */
684
+ pushStreamUpdate(nodes, edges, currentPositions) {
685
+ if (this.pendingTimer) {
686
+ clearTimeout(this.pendingTimer);
687
+ }
688
+ this.pendingTimer = setTimeout(() => {
689
+ this.pendingTimer = null;
690
+ this.recalculateAndGlide(nodes, edges, currentPositions);
691
+ }, this.throttleMs);
692
+ }
693
+ /**
694
+ * Forces immediate calculation and cancels pending throttled timers.
695
+ */
696
+ flush(nodes, edges, currentPositions) {
697
+ if (this.pendingTimer) {
698
+ clearTimeout(this.pendingTimer);
699
+ this.pendingTimer = null;
700
+ }
701
+ this.recalculateAndGlide(nodes, edges, currentPositions);
702
+ }
703
+ recalculateAndGlide(nodes, edges, currentPositions) {
704
+ if (nodes.length === 0) return;
705
+ if (this.activeTransitionCancel) {
706
+ this.activeTransitionCancel();
707
+ this.activeTransitionCancel = null;
708
+ }
709
+ const stored = {};
710
+ for (const [id, pos] of currentPositions.entries()) {
711
+ stored[id] = pos;
712
+ }
713
+ const layouted = ensureLayout(nodes, edges, {
714
+ ...this.layoutOptions,
715
+ storedPositions: stored
716
+ });
717
+ const nextPositions = /* @__PURE__ */ new Map();
718
+ const transitions = [];
719
+ for (const node of layouted) {
720
+ const target = node.position;
721
+ nextPositions.set(node.id, target);
722
+ const current = currentPositions.get(node.id) ?? target;
723
+ const distance = Math.hypot(target.x - current.x, target.y - current.y);
724
+ if (distance > 2) {
725
+ transitions.push({
726
+ node,
727
+ from: current,
728
+ to: target,
729
+ trajectory: linearPositionTrajectory,
730
+ duration: this.glideDurationMs
731
+ });
732
+ }
733
+ }
734
+ this.targetPositions = nextPositions;
735
+ if (transitions.length > 0) {
736
+ this.activeTransitionCancel = runMultiNodeTransition(transitions, {
737
+ onUpdate: () => {
738
+ const liveMap = new Map(currentPositions);
739
+ for (const trans of transitions) {
740
+ liveMap.set(trans.node.id, { ...trans.node.position });
741
+ }
742
+ this.onLayoutUpdated?.(liveMap);
743
+ },
744
+ onComplete: () => {
745
+ this.activeTransitionCancel = null;
746
+ this.onLayoutUpdated?.(nextPositions);
747
+ }
748
+ });
749
+ } else {
750
+ this.onLayoutUpdated?.(nextPositions);
751
+ }
752
+ }
753
+ destroy() {
754
+ if (this.pendingTimer) {
755
+ clearTimeout(this.pendingTimer);
756
+ this.pendingTimer = null;
757
+ }
758
+ if (this.activeTransitionCancel) {
759
+ this.activeTransitionCancel();
760
+ this.activeTransitionCancel = null;
761
+ }
762
+ }
763
+ };
764
+ // Annotate the CommonJS export names for ESM import in node:
765
+ 0 && (module.exports = {
766
+ DEFAULT_NODE_HEIGHT,
767
+ DEFAULT_NODE_WIDTH,
768
+ DEFAULT_PLACEMENT_GAP,
769
+ StreamingLayoutManager,
770
+ boundsOf,
771
+ calculateDagreReflowDisplacement,
772
+ calculateDirectionalDisplacement,
773
+ centerNodes,
774
+ centre,
775
+ computeNodesBoundingBox,
776
+ createBezierTrajectory,
777
+ createFanOutTrajectory,
778
+ defaultEasing,
779
+ ensureLayout,
780
+ estimateNodeSize,
781
+ flowSide,
782
+ getCenteredNodePosition,
783
+ getLayoutedNodes,
784
+ isEventFromTextInput,
785
+ isValidCanvasNode,
786
+ isValidUrl,
787
+ linearPositionTrajectory,
788
+ linearTrajectory,
789
+ overlaps,
790
+ parseCanvasClipboardData,
791
+ placeBeside,
792
+ remapPastedNodes,
793
+ repositionNodesTo,
794
+ runMultiNodeTransition,
795
+ serializeCanvasNodes
796
+ });
797
+ //# sourceMappingURL=index.cjs.map