@pixerate/editor 0.162.1 → 0.164.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/README.md +1 -1
- package/dist/agent/index.cjs +34 -34
- package/dist/agent/index.js +5 -5
- package/dist/canvas/displacement.d.cts +34 -2
- package/dist/canvas/displacement.d.ts +34 -2
- package/dist/canvas/explosion-tools.d.cts +43 -0
- package/dist/canvas/explosion-tools.d.ts +43 -0
- package/dist/canvas/explosionLayout.d.cts +50 -0
- package/dist/canvas/explosionLayout.d.ts +50 -0
- package/dist/canvas/explosionState.d.cts +52 -0
- package/dist/canvas/explosionState.d.ts +52 -0
- package/dist/canvas/index.cjs +430 -88
- package/dist/canvas/index.d.cts +3 -0
- package/dist/canvas/index.d.ts +3 -0
- package/dist/canvas/index.js +425 -83
- package/dist/canvas/types.d.cts +27 -2
- package/dist/canvas/types.d.ts +27 -2
- package/dist/chart/commands.d.cts +2 -1
- package/dist/chart/commands.d.ts +2 -1
- package/dist/chart/controller.d.cts +109 -1
- package/dist/chart/controller.d.ts +109 -1
- package/dist/chart/index.cjs +605 -130
- package/dist/chart/index.d.cts +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chart/index.js +490 -15
- package/dist/chart/layout-annotations.d.cts +29 -0
- package/dist/chart/layout-annotations.d.ts +29 -0
- package/dist/chart/layout-families.d.cts +23 -0
- package/dist/chart/layout-families.d.ts +23 -0
- package/dist/chart/mermaid.d.cts +2 -1
- package/dist/chart/mermaid.d.ts +2 -1
- package/dist/chart/schemas.d.cts +13 -0
- package/dist/chart/schemas.d.ts +13 -0
- package/dist/chart/spec.d.cts +30 -5
- package/dist/chart/spec.d.ts +30 -5
- package/dist/chart/stats.d.cts +9 -1
- package/dist/chart/stats.d.ts +9 -1
- package/dist/chart/tools.d.cts +1 -1
- package/dist/chart/tools.d.ts +1 -1
- package/dist/chart/tree.d.cts +62 -0
- package/dist/chart/tree.d.ts +62 -0
- package/dist/chart/types.d.cts +77 -3
- package/dist/chart/types.d.ts +77 -3
- package/dist/{chunk-7YBDRJXT.cjs → chunk-22KLZZGH.cjs} +8 -8
- package/dist/{chunk-HLOKB7WW.js → chunk-3NEZJCBY.js} +366 -6
- package/dist/{chunk-EVPZ5IKN.js → chunk-5FNGLV2D.js} +65 -3
- package/dist/{chunk-EQ46F75O.cjs → chunk-D54JHQK2.cjs} +51 -51
- package/dist/{chunk-NO5QQDHZ.js → chunk-DAPQGA55.js} +2 -2
- package/dist/{chunk-3IHNXRUV.cjs → chunk-GKNEJ6EN.cjs} +399 -67
- package/dist/{chunk-4NGVHHJB.cjs → chunk-NZD2MZJC.cjs} +80 -18
- package/dist/{chunk-CX6JLDFC.js → chunk-OXERWBKE.js} +3 -3
- package/dist/{chunk-7CKPUUN4.js → chunk-POR54B55.js} +61 -10
- package/dist/{chunk-LL3H5XUH.cjs → chunk-R3P2M7HU.cjs} +367 -7
- package/dist/{chunk-HDGBM7EA.cjs → chunk-SBQVEBSU.cjs} +580 -53
- package/dist/{chunk-XDCFJEU7.js → chunk-SZA42R4N.js} +346 -14
- package/dist/{chunk-GLMP22FT.js → chunk-TALQONZ5.js} +3 -3
- package/dist/{chunk-NA4UVVJ4.cjs → chunk-YGG7WDWT.cjs} +76 -25
- package/dist/{chunk-EY22AY6R.cjs → chunk-YSAGBIRW.cjs} +16 -16
- package/dist/{chunk-DTSTNRPH.js → chunk-Z4P2RBMJ.js} +536 -9
- package/dist/export/index.cjs +4 -4
- package/dist/export/index.js +3 -3
- package/dist/extensions/index.cjs +7 -7
- package/dist/extensions/index.js +6 -6
- package/dist/index.cjs +13 -13
- package/dist/index.js +7 -7
- package/dist/paint-editor/index.cjs +0 -20
- package/dist/paint-editor/index.js +0 -20
- package/package.json +1 -1
- package/skills/README.md +12 -0
- package/skills/pixerate-agent-tools/SKILL.md +34 -1
- package/skills/pixerate-canvas/SKILL.md +16 -0
- package/skills/pixerate-chart/SKILL.md +41 -9
- package/skills/pixerate-image-editor/SKILL.md +2 -0
- package/skills/pixerate-paint-editor/SKILL.md +2 -0
- package/skills/pixerate-tokens/SKILL.md +5 -0
package/dist/canvas/index.d.cts
CHANGED
|
@@ -37,3 +37,6 @@ export * from './effects.cjs';
|
|
|
37
37
|
export * from './effect-player.cjs';
|
|
38
38
|
export * from './effect-tools.cjs';
|
|
39
39
|
export * from './editorDocument.cjs';
|
|
40
|
+
export * from './explosionLayout.cjs';
|
|
41
|
+
export * from './explosionState.cjs';
|
|
42
|
+
export * from './explosion-tools.cjs';
|
package/dist/canvas/index.d.ts
CHANGED
|
@@ -37,3 +37,6 @@ export * from './effects.js';
|
|
|
37
37
|
export * from './effect-player.js';
|
|
38
38
|
export * from './effect-tools.js';
|
|
39
39
|
export * from './editorDocument.js';
|
|
40
|
+
export * from './explosionLayout.js';
|
|
41
|
+
export * from './explosionState.js';
|
|
42
|
+
export * from './explosion-tools.js';
|
package/dist/canvas/index.js
CHANGED
|
@@ -113,10 +113,10 @@ import {
|
|
|
113
113
|
} from "../chunk-5ED73EC6.js";
|
|
114
114
|
import {
|
|
115
115
|
layoutChart
|
|
116
|
-
} from "../chunk-
|
|
116
|
+
} from "../chunk-Z4P2RBMJ.js";
|
|
117
117
|
import {
|
|
118
118
|
createChartSpec
|
|
119
|
-
} from "../chunk-
|
|
119
|
+
} from "../chunk-3NEZJCBY.js";
|
|
120
120
|
import "../chunk-II2WIF5Z.js";
|
|
121
121
|
import {
|
|
122
122
|
PathOpsError,
|
|
@@ -428,109 +428,162 @@ function computeNodesBoundingBox(nodes, defaultWidth = 200, defaultHeight = 60)
|
|
|
428
428
|
height: maxY - minY
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
|
+
function nodeSizeOf(node, defaultWidth = 200, defaultHeight = 60) {
|
|
432
|
+
return {
|
|
433
|
+
width: node.measured?.width ?? node.width ?? defaultWidth,
|
|
434
|
+
height: node.measured?.height ?? node.height ?? defaultHeight
|
|
435
|
+
};
|
|
436
|
+
}
|
|
431
437
|
function calculateDirectionalDisplacement(params) {
|
|
432
|
-
const {
|
|
433
|
-
existingNodes,
|
|
434
|
-
originNode,
|
|
435
|
-
childNodes,
|
|
436
|
-
getNodesBounds,
|
|
437
|
-
options = {}
|
|
438
|
-
} = params;
|
|
438
|
+
const { existingNodes, originNode, childNodes, getNodesBounds, options = {} } = params;
|
|
439
439
|
const {
|
|
440
440
|
direction = "right",
|
|
441
441
|
gap = 40,
|
|
442
442
|
spreadSiblingLanes = false,
|
|
443
443
|
siblingTolerance = 50,
|
|
444
|
-
minY
|
|
444
|
+
minY,
|
|
445
|
+
maxY,
|
|
446
|
+
layoutMode = "freeform",
|
|
447
|
+
rowSpacing
|
|
445
448
|
} = options;
|
|
449
|
+
const finite2 = (name, value) => {
|
|
450
|
+
if (value !== void 0 && !Number.isFinite(value)) {
|
|
451
|
+
throw new RangeError(`${name} must be a finite number (got ${value}).`);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
finite2("gap", gap);
|
|
455
|
+
finite2("minY", minY);
|
|
456
|
+
finite2("maxY", maxY);
|
|
457
|
+
finite2("rowSpacing", rowSpacing);
|
|
458
|
+
if (rowSpacing !== void 0 && rowSpacing <= 0) {
|
|
459
|
+
throw new RangeError(`rowSpacing must be greater than 0 (got ${rowSpacing}).`);
|
|
460
|
+
}
|
|
461
|
+
if (minY !== void 0 && maxY !== void 0 && minY > maxY) {
|
|
462
|
+
throw new RangeError(`minY (${minY}) must not be greater than maxY (${maxY}).`);
|
|
463
|
+
}
|
|
446
464
|
const displacedMap = /* @__PURE__ */ new Map();
|
|
447
465
|
if (childNodes.length === 0 || existingNodes.length === 0) {
|
|
448
466
|
return displacedMap;
|
|
449
467
|
}
|
|
450
468
|
const childBounds = getNodesBounds ? getNodesBounds(childNodes) : computeNodesBoundingBox(childNodes);
|
|
451
|
-
const originWidth
|
|
452
|
-
const
|
|
453
|
-
const
|
|
469
|
+
const { width: originWidth, height: originHeight } = nodeSizeOf(originNode);
|
|
470
|
+
const rowLane = layoutMode === "row-lane";
|
|
471
|
+
const lane = rowSpacing ?? originHeight;
|
|
472
|
+
const quantize = (delta) => rowLane && delta > 0 && lane > 0 ? Math.ceil(delta / lane) * lane : delta;
|
|
473
|
+
const laneOf = (node) => lane > 0 ? Math.round((node.position.y - originNode.position.y) / lane) : 0;
|
|
474
|
+
const isAbove = (node) => rowLane ? laneOf(node) < 0 : node.position.y < originNode.position.y;
|
|
475
|
+
const isBelow = (node) => rowLane ? laneOf(node) > 0 : node.position.y >= originNode.position.y + originHeight * 0.5;
|
|
476
|
+
const horizontal = direction === "right" || direction === "left";
|
|
477
|
+
const deltaPrimary = horizontal ? childBounds.width + gap : quantize(childBounds.height + gap);
|
|
454
478
|
const deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);
|
|
455
|
-
const deltaDown = Math.max(
|
|
456
|
-
|
|
457
|
-
childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap
|
|
458
|
-
);
|
|
459
|
-
const equalDelta = Math.max(deltaUp, deltaDown);
|
|
479
|
+
const deltaDown = Math.max(0, childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap);
|
|
480
|
+
const equalDelta = quantize(Math.max(deltaUp, deltaDown));
|
|
460
481
|
for (const node of existingNodes) {
|
|
461
482
|
if (node.id === originNode.id) continue;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
case "left": {
|
|
474
|
-
if (node.position.x < originNode.position.x) {
|
|
475
|
-
displacedMap.set(node.id, {
|
|
476
|
-
x: node.position.x - deltaPrimary,
|
|
477
|
-
y: node.position.y
|
|
478
|
-
});
|
|
483
|
+
let newX = node.position.x;
|
|
484
|
+
let newY = node.position.y;
|
|
485
|
+
if (direction === "right") {
|
|
486
|
+
if (node.position.x >= originNode.position.x + originWidth * 0.5) {
|
|
487
|
+
newX += deltaPrimary;
|
|
488
|
+
} else if (spreadSiblingLanes) {
|
|
489
|
+
const { height } = nodeSizeOf(node);
|
|
490
|
+
const overlapsCluster = node.position.y + height >= childBounds.y - gap && node.position.y <= childBounds.y + childBounds.height + gap;
|
|
491
|
+
if (overlapsCluster && Math.abs(node.position.x - originNode.position.x) <= siblingTolerance) {
|
|
492
|
+
const push2 = node.position.y >= originNode.position.y ? 1 : -1;
|
|
493
|
+
newY += push2 * (childBounds.height * 0.5 + gap);
|
|
479
494
|
}
|
|
480
|
-
break;
|
|
481
495
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
496
|
+
} else if (direction === "left") {
|
|
497
|
+
if (node.position.x < originNode.position.x) newX -= deltaPrimary;
|
|
498
|
+
} else if (direction === "down") {
|
|
499
|
+
if (isBelow(node)) newY += deltaPrimary;
|
|
500
|
+
} else if (direction === "up") {
|
|
501
|
+
if (isAbove(node)) newY -= deltaPrimary;
|
|
502
|
+
} else if (direction === "vertical") {
|
|
503
|
+
if (isAbove(node)) newY -= equalDelta;
|
|
504
|
+
else if (isBelow(node)) newY += equalDelta;
|
|
505
|
+
}
|
|
506
|
+
if (newX !== node.position.x || newY !== node.position.y) {
|
|
507
|
+
displacedMap.set(node.id, { x: newX, y: newY });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (minY !== void 0 || maxY !== void 0) {
|
|
511
|
+
const byId = new Map(existingNodes.map((n3) => [n3.id, n3]));
|
|
512
|
+
const rowsOf = (moved2) => {
|
|
513
|
+
const rows = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const node of moved2) {
|
|
515
|
+
const key = rowLane ? laneOf(node) : node.position.y;
|
|
516
|
+
rows.set(key, [...rows.get(key) ?? [], node]);
|
|
499
517
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
518
|
+
return [...rows.entries()].sort((a, b) => a[0] - b[0]).map(([, nodes]) => nodes);
|
|
519
|
+
};
|
|
520
|
+
const spacingFor = (nodes) => rowSpacing ?? Math.max(...nodes.map((n3) => nodeSizeOf(n3).height));
|
|
521
|
+
const moved = (sign) => [...displacedMap.entries()].filter(([id, pos]) => Math.sign(pos.y - byId.get(id).position.y) === sign).map(([id]) => byId.get(id));
|
|
522
|
+
if (minY !== void 0) {
|
|
523
|
+
const up = moved(-1);
|
|
524
|
+
if (up.length > 0) {
|
|
525
|
+
const spacing = spacingFor(up);
|
|
526
|
+
rowsOf(up).forEach((row, index) => {
|
|
527
|
+
const floor = minY + index * spacing;
|
|
528
|
+
for (const node of row) {
|
|
529
|
+
const pos = displacedMap.get(node.id);
|
|
530
|
+
displacedMap.set(node.id, { x: pos.x, y: Math.min(node.position.y, Math.max(floor, pos.y)) });
|
|
531
|
+
}
|
|
532
|
+
});
|
|
514
533
|
}
|
|
515
534
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
535
|
+
if (maxY !== void 0) {
|
|
536
|
+
const down = moved(1);
|
|
537
|
+
if (down.length > 0) {
|
|
538
|
+
const spacing = spacingFor(down);
|
|
539
|
+
rowsOf(down).reverse().forEach((row, index) => {
|
|
540
|
+
for (const node of row) {
|
|
541
|
+
const pos = displacedMap.get(node.id);
|
|
542
|
+
const ceiling = maxY - index * spacing - nodeSizeOf(node).height;
|
|
543
|
+
displacedMap.set(node.id, { x: pos.x, y: Math.max(node.position.y, Math.min(ceiling, pos.y)) });
|
|
544
|
+
}
|
|
525
545
|
});
|
|
526
546
|
}
|
|
527
547
|
}
|
|
548
|
+
for (const [id, pos] of [...displacedMap.entries()]) {
|
|
549
|
+
const node = byId.get(id);
|
|
550
|
+
if (pos.x === node.position.x && pos.y === node.position.y) displacedMap.delete(id);
|
|
551
|
+
}
|
|
528
552
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
553
|
+
return displacedMap;
|
|
554
|
+
}
|
|
555
|
+
function calculateReflowDisplacement(params) {
|
|
556
|
+
const { existingNodes, existingEdges, originNode, childNodes, childEdges = [], options = {} } = params;
|
|
557
|
+
const { direction = "LR", ranksep = 80, nodesep = 30, defaultNodeWidth = 200, defaultNodeHeight = 60 } = options;
|
|
558
|
+
const displacedMap = /* @__PURE__ */ new Map();
|
|
559
|
+
if (existingNodes.length === 0) return displacedMap;
|
|
560
|
+
const allNodes = [...existingNodes, ...childNodes];
|
|
561
|
+
const allEdges = [...existingEdges, ...childEdges];
|
|
562
|
+
const g = new dagre.graphlib.Graph();
|
|
563
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
564
|
+
g.setGraph({ rankdir: direction, ranksep, nodesep });
|
|
565
|
+
const allNodeIds = new Set(allNodes.map((n3) => n3.id));
|
|
566
|
+
for (const node of allNodes) {
|
|
567
|
+
g.setNode(node.id, nodeSizeOf(node, defaultNodeWidth, defaultNodeHeight));
|
|
568
|
+
}
|
|
569
|
+
for (const edge of allEdges) {
|
|
570
|
+
if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) g.setEdge(edge.source, edge.target);
|
|
571
|
+
}
|
|
572
|
+
dagre.layout(g);
|
|
573
|
+
const dagreOrigin = g.node(originNode.id);
|
|
574
|
+
if (!dagreOrigin) return displacedMap;
|
|
575
|
+
const origin = nodeSizeOf(originNode, defaultNodeWidth, defaultNodeHeight);
|
|
576
|
+
const anchorOffsetX = originNode.position.x - (dagreOrigin.x - origin.width / 2);
|
|
577
|
+
const anchorOffsetY = originNode.position.y - (dagreOrigin.y - origin.height / 2);
|
|
578
|
+
for (const node of existingNodes) {
|
|
579
|
+
if (node.id === originNode.id) continue;
|
|
580
|
+
const laidOut = g.node(node.id);
|
|
581
|
+
if (!laidOut) continue;
|
|
582
|
+
const { width, height } = nodeSizeOf(node, defaultNodeWidth, defaultNodeHeight);
|
|
583
|
+
const targetX = laidOut.x - width / 2 + anchorOffsetX;
|
|
584
|
+
const targetY = laidOut.y - height / 2 + anchorOffsetY;
|
|
585
|
+
if (Math.abs(targetX - node.position.x) > 0.5 || Math.abs(targetY - node.position.y) > 0.5) {
|
|
586
|
+
displacedMap.set(node.id, { x: targetX, y: targetY });
|
|
534
587
|
}
|
|
535
588
|
}
|
|
536
589
|
return displacedMap;
|
|
@@ -5142,7 +5195,7 @@ var defaultLabel = (node) => typeof node.data?.label === "string" && node.data.l
|
|
|
5142
5195
|
function getTimelineRows(nodes, ids, options = {}) {
|
|
5143
5196
|
const byId = new Map(nodes.map((n3) => [n3.id, n3]));
|
|
5144
5197
|
const selection = options.selection ?? [];
|
|
5145
|
-
const
|
|
5198
|
+
const labelOf2 = options.labelOf ?? defaultLabel;
|
|
5146
5199
|
const rows = [];
|
|
5147
5200
|
for (const id of new Set(ids)) {
|
|
5148
5201
|
const node = byId.get(id);
|
|
@@ -5153,7 +5206,7 @@ function getTimelineRows(nodes, ids, options = {}) {
|
|
|
5153
5206
|
rows.push({
|
|
5154
5207
|
id: `${id}:${property}`,
|
|
5155
5208
|
nodeId: id,
|
|
5156
|
-
nodeLabel:
|
|
5209
|
+
nodeLabel: labelOf2(node),
|
|
5157
5210
|
property,
|
|
5158
5211
|
label,
|
|
5159
5212
|
enabled: track.enabled !== false,
|
|
@@ -11801,6 +11854,288 @@ function createCanvasGestureRecorder(options) {
|
|
|
11801
11854
|
}
|
|
11802
11855
|
};
|
|
11803
11856
|
}
|
|
11857
|
+
|
|
11858
|
+
// src/canvas/explosionLayout.ts
|
|
11859
|
+
var explosionEdgeId = (sourceId, targetId) => `explode:${sourceId}:${targetId}`;
|
|
11860
|
+
function computeCanvasExplosionLayout(params) {
|
|
11861
|
+
const {
|
|
11862
|
+
existingNodes,
|
|
11863
|
+
existingEdges = [],
|
|
11864
|
+
originNode,
|
|
11865
|
+
childNodes,
|
|
11866
|
+
childEdges = [],
|
|
11867
|
+
strategy = "directional",
|
|
11868
|
+
options,
|
|
11869
|
+
reflowOptions,
|
|
11870
|
+
connectParentToChildren = false,
|
|
11871
|
+
createEdge,
|
|
11872
|
+
getNodesBounds
|
|
11873
|
+
} = params;
|
|
11874
|
+
const existingIds = new Set(existingNodes.map((n3) => n3.id));
|
|
11875
|
+
if (!existingIds.has(originNode.id)) {
|
|
11876
|
+
throw new Error(`The origin node "${originNode.id}" is not in existingNodes; pass the canvas nodes including the origin.`);
|
|
11877
|
+
}
|
|
11878
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
11879
|
+
for (const child of childNodes) {
|
|
11880
|
+
if (existingIds.has(child.id)) {
|
|
11881
|
+
throw new Error(`Child node id "${child.id}" is already on the canvas; give exploded children unique ids.`);
|
|
11882
|
+
}
|
|
11883
|
+
if (childIds.has(child.id)) throw new Error(`Child node id "${child.id}" is repeated; give exploded children unique ids.`);
|
|
11884
|
+
childIds.add(child.id);
|
|
11885
|
+
}
|
|
11886
|
+
const children = childNodes.map((child) => ({ ...child, position: { ...child.position } }));
|
|
11887
|
+
const edges = [...childEdges];
|
|
11888
|
+
const makeEdge = createEdge ?? ((source, target) => ({ id: explosionEdgeId(source, target), type: "default", source, target }));
|
|
11889
|
+
const connect = (child) => {
|
|
11890
|
+
if (!edges.some((e) => e.source === originNode.id && e.target === child.id)) edges.push(makeEdge(originNode.id, child.id));
|
|
11891
|
+
};
|
|
11892
|
+
if (connectParentToChildren === true || connectParentToChildren === "all") children.forEach(connect);
|
|
11893
|
+
else if (connectParentToChildren === "first" && children.length > 0) connect(children[0]);
|
|
11894
|
+
let displacedPositions;
|
|
11895
|
+
if (children.length === 0 || strategy === "none") {
|
|
11896
|
+
displacedPositions = /* @__PURE__ */ new Map();
|
|
11897
|
+
} else if (strategy === "dagre") {
|
|
11898
|
+
displacedPositions = calculateReflowDisplacement({
|
|
11899
|
+
existingNodes,
|
|
11900
|
+
existingEdges,
|
|
11901
|
+
originNode,
|
|
11902
|
+
childNodes: children,
|
|
11903
|
+
childEdges: edges,
|
|
11904
|
+
options: reflowOptions
|
|
11905
|
+
});
|
|
11906
|
+
} else {
|
|
11907
|
+
displacedPositions = calculateDirectionalDisplacement({
|
|
11908
|
+
existingNodes,
|
|
11909
|
+
originNode,
|
|
11910
|
+
childNodes: children,
|
|
11911
|
+
getNodesBounds,
|
|
11912
|
+
options
|
|
11913
|
+
});
|
|
11914
|
+
}
|
|
11915
|
+
const byId = new Map(existingNodes.map((n3) => [n3.id, n3]));
|
|
11916
|
+
const originalPositions = /* @__PURE__ */ new Map();
|
|
11917
|
+
let totalDisplacementDelta = 0;
|
|
11918
|
+
for (const [id, to] of displacedPositions) {
|
|
11919
|
+
const from = byId.get(id).position;
|
|
11920
|
+
originalPositions.set(id, { ...from });
|
|
11921
|
+
totalDisplacementDelta = Math.max(totalDisplacementDelta, Math.abs(to.x - from.x), Math.abs(to.y - from.y));
|
|
11922
|
+
}
|
|
11923
|
+
return {
|
|
11924
|
+
displacedPositions,
|
|
11925
|
+
originalPositions,
|
|
11926
|
+
childNodes: children,
|
|
11927
|
+
childEdges: edges,
|
|
11928
|
+
childBounds: getNodesBounds ? getNodesBounds(children) : computeNodesBoundingBox(children),
|
|
11929
|
+
totalDisplacementDelta
|
|
11930
|
+
};
|
|
11931
|
+
}
|
|
11932
|
+
|
|
11933
|
+
// src/canvas/explosionState.ts
|
|
11934
|
+
function createCanvasExplosionStateController(options = {}) {
|
|
11935
|
+
const duration = options.collapseDurationMs ?? 380;
|
|
11936
|
+
if (!Number.isFinite(duration) || duration < 0) {
|
|
11937
|
+
throw new RangeError(`collapseDurationMs must be a finite number >= 0 (got ${duration}).`);
|
|
11938
|
+
}
|
|
11939
|
+
let snapshot = { activeId: options.initialActiveId ?? null, collapsingId: null };
|
|
11940
|
+
let timer = null;
|
|
11941
|
+
let destroyed = false;
|
|
11942
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
11943
|
+
const clearTimer = () => {
|
|
11944
|
+
if (timer !== null) clearTimeout(timer);
|
|
11945
|
+
timer = null;
|
|
11946
|
+
};
|
|
11947
|
+
const set = (next) => {
|
|
11948
|
+
if (next.activeId === snapshot.activeId && next.collapsingId === snapshot.collapsingId) return;
|
|
11949
|
+
snapshot = next;
|
|
11950
|
+
for (const listener of [...listeners]) listener();
|
|
11951
|
+
};
|
|
11952
|
+
const finishCollapse = () => {
|
|
11953
|
+
clearTimer();
|
|
11954
|
+
if (snapshot.collapsingId !== null) set({ activeId: snapshot.activeId, collapsingId: null });
|
|
11955
|
+
};
|
|
11956
|
+
const explode = (nodeId) => {
|
|
11957
|
+
if (destroyed) return false;
|
|
11958
|
+
if (typeof nodeId !== "string" || nodeId === "") throw new TypeError("explode needs a node id.");
|
|
11959
|
+
if (snapshot.activeId === nodeId) return false;
|
|
11960
|
+
clearTimer();
|
|
11961
|
+
const previous = snapshot.activeId;
|
|
11962
|
+
set({ activeId: nodeId, collapsingId: null });
|
|
11963
|
+
if (previous !== null) options.onCollapse?.(previous);
|
|
11964
|
+
options.onExplode?.(nodeId);
|
|
11965
|
+
return true;
|
|
11966
|
+
};
|
|
11967
|
+
const collapse = (nodeId) => {
|
|
11968
|
+
if (destroyed) return false;
|
|
11969
|
+
const active = snapshot.activeId;
|
|
11970
|
+
if (active === null || nodeId !== void 0 && nodeId !== active) return false;
|
|
11971
|
+
clearTimer();
|
|
11972
|
+
set({ activeId: null, collapsingId: active });
|
|
11973
|
+
options.onCollapse?.(active);
|
|
11974
|
+
if (duration === 0) finishCollapse();
|
|
11975
|
+
else timer = setTimeout(() => {
|
|
11976
|
+
timer = null;
|
|
11977
|
+
finishCollapse();
|
|
11978
|
+
}, duration);
|
|
11979
|
+
return true;
|
|
11980
|
+
};
|
|
11981
|
+
return {
|
|
11982
|
+
get activeId() {
|
|
11983
|
+
return snapshot.activeId;
|
|
11984
|
+
},
|
|
11985
|
+
get collapsingId() {
|
|
11986
|
+
return snapshot.collapsingId;
|
|
11987
|
+
},
|
|
11988
|
+
get isExplodingOrCollapsing() {
|
|
11989
|
+
return snapshot.activeId !== null || snapshot.collapsingId !== null;
|
|
11990
|
+
},
|
|
11991
|
+
get destroyed() {
|
|
11992
|
+
return destroyed;
|
|
11993
|
+
},
|
|
11994
|
+
getSnapshot: () => snapshot,
|
|
11995
|
+
subscribe(listener) {
|
|
11996
|
+
if (destroyed) return () => {
|
|
11997
|
+
};
|
|
11998
|
+
listeners.add(listener);
|
|
11999
|
+
return () => listeners.delete(listener);
|
|
12000
|
+
},
|
|
12001
|
+
isExploded: (nodeId) => snapshot.activeId === nodeId,
|
|
12002
|
+
isCollapsing: (nodeId) => snapshot.collapsingId === nodeId,
|
|
12003
|
+
shouldRenderChildren: (nodeId) => snapshot.activeId === nodeId || snapshot.collapsingId === nodeId,
|
|
12004
|
+
explode,
|
|
12005
|
+
collapse,
|
|
12006
|
+
toggle: (nodeId) => snapshot.activeId === nodeId ? (collapse(nodeId), false) : explode(nodeId),
|
|
12007
|
+
finishCollapse: () => {
|
|
12008
|
+
if (!destroyed) finishCollapse();
|
|
12009
|
+
},
|
|
12010
|
+
destroy() {
|
|
12011
|
+
clearTimer();
|
|
12012
|
+
destroyed = true;
|
|
12013
|
+
listeners.clear();
|
|
12014
|
+
}
|
|
12015
|
+
};
|
|
12016
|
+
}
|
|
12017
|
+
|
|
12018
|
+
// src/canvas/explosion-tools.ts
|
|
12019
|
+
function canvasExplosionToolTarget(state, options) {
|
|
12020
|
+
return {
|
|
12021
|
+
getNodes: options.getNodes,
|
|
12022
|
+
canExplode: options.canExplode,
|
|
12023
|
+
exploded: () => state.activeId ? [state.activeId] : [],
|
|
12024
|
+
collapsing: () => state.collapsingId ? [state.collapsingId] : [],
|
|
12025
|
+
explode: (id) => {
|
|
12026
|
+
state.explode(id);
|
|
12027
|
+
},
|
|
12028
|
+
collapse: (id) => state.collapse(id)
|
|
12029
|
+
};
|
|
12030
|
+
}
|
|
12031
|
+
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
12032
|
+
var WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
12033
|
+
var labelOf = (node) => {
|
|
12034
|
+
const label = node.data?.label ?? node.data?.name;
|
|
12035
|
+
return typeof label === "string" && label !== "" ? label : void 0;
|
|
12036
|
+
};
|
|
12037
|
+
function createCanvasExplosionToolProvider(target, options = {}) {
|
|
12038
|
+
const readOnlyReason = () => {
|
|
12039
|
+
const ro = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
12040
|
+
return ro ? typeof ro === "string" ? ro : "Exploding nodes is disabled on this canvas." : null;
|
|
12041
|
+
};
|
|
12042
|
+
const reasonNotExplodable = (node) => {
|
|
12043
|
+
const ok = target.canExplode ? target.canExplode(node) : true;
|
|
12044
|
+
if (ok === true) return null;
|
|
12045
|
+
return typeof ok === "string" && ok ? ok : `Node "${node.id}" has nothing to explode into.`;
|
|
12046
|
+
};
|
|
12047
|
+
const state = () => ({ exploded: [...target.exploded()], collapsing: [...target.collapsing?.() ?? []] });
|
|
12048
|
+
const findNode = (id) => {
|
|
12049
|
+
const node = target.getNodes().find((n3) => n3.id === id);
|
|
12050
|
+
if (!node) throw new Error(`No node "${id}" on the canvas. Call list_explodable_nodes for node ids.`);
|
|
12051
|
+
return node;
|
|
12052
|
+
};
|
|
12053
|
+
const refuseIfReadOnly = () => {
|
|
12054
|
+
const reason = readOnlyReason();
|
|
12055
|
+
if (reason) throw new Error(reason);
|
|
12056
|
+
};
|
|
12057
|
+
const listTool = {
|
|
12058
|
+
name: "list_explodable_nodes",
|
|
12059
|
+
title: "List explodable nodes",
|
|
12060
|
+
description: "Lists the canvas nodes that can explode into child nodes (id, label, exploded, collapsing), plus the ids currently exploded and collapsing, and whether exploding is read-only. Call before explode_node or collapse_node.",
|
|
12061
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
12062
|
+
annotations: { title: "List explodable nodes", ...READ },
|
|
12063
|
+
handler: () => {
|
|
12064
|
+
const { exploded, collapsing } = state();
|
|
12065
|
+
const nodes = target.getNodes().filter((n3) => exploded.includes(n3.id) || reasonNotExplodable(n3) === null).map((n3) => ({
|
|
12066
|
+
id: n3.id,
|
|
12067
|
+
...labelOf(n3) ? { label: labelOf(n3) } : {},
|
|
12068
|
+
exploded: exploded.includes(n3.id),
|
|
12069
|
+
collapsing: collapsing.includes(n3.id)
|
|
12070
|
+
}));
|
|
12071
|
+
return { nodes, exploded, collapsing, multiple: target.multiple === true, readOnly: readOnlyReason() };
|
|
12072
|
+
}
|
|
12073
|
+
};
|
|
12074
|
+
const explodeTool = {
|
|
12075
|
+
name: "explode_node",
|
|
12076
|
+
title: "Explode a node",
|
|
12077
|
+
description: "Expands a node into its child nodes (subtasks, steps, details), moving surrounding nodes to make room. Unless several can be open at once, the node exploded before collapses. Exploding an exploded node changes nothing. Returns the node, the ids now exploded, and the ids that collapsed.",
|
|
12078
|
+
inputSchema: {
|
|
12079
|
+
type: "object",
|
|
12080
|
+
properties: {
|
|
12081
|
+
node: { type: "string", minLength: 1, description: "Id of the node to explode, from list_explodable_nodes." }
|
|
12082
|
+
},
|
|
12083
|
+
required: ["node"],
|
|
12084
|
+
additionalProperties: false
|
|
12085
|
+
},
|
|
12086
|
+
annotations: { title: "Explode a node", ...WRITE_IDEMPOTENT },
|
|
12087
|
+
handler: async (args) => {
|
|
12088
|
+
refuseIfReadOnly();
|
|
12089
|
+
const node = findNode(args.node);
|
|
12090
|
+
const before = state().exploded;
|
|
12091
|
+
if (before.includes(node.id)) return { node: node.id, changed: false, exploded: before, collapsed: [] };
|
|
12092
|
+
const reason = reasonNotExplodable(node);
|
|
12093
|
+
if (reason) throw new Error(reason);
|
|
12094
|
+
if (await target.explode(node.id) === false) {
|
|
12095
|
+
throw new Error(`Node "${node.id}" could not explode (the canvas refused, e.g. an animation is running). Try again or call list_explodable_nodes.`);
|
|
12096
|
+
}
|
|
12097
|
+
const after = state().exploded;
|
|
12098
|
+
return { node: node.id, changed: true, exploded: after, collapsed: before.filter((id) => !after.includes(id)) };
|
|
12099
|
+
}
|
|
12100
|
+
};
|
|
12101
|
+
const collapseTool = {
|
|
12102
|
+
name: "collapse_node",
|
|
12103
|
+
title: "Collapse a node",
|
|
12104
|
+
description: "Collapses an exploded node: its child nodes are removed (after the collapse animation) and moved nodes return to where they were. Returns the node and the ids still exploded.",
|
|
12105
|
+
inputSchema: {
|
|
12106
|
+
type: "object",
|
|
12107
|
+
properties: {
|
|
12108
|
+
node: { type: "string", minLength: 1, description: "Id of an exploded node, from list_explodable_nodes (exploded: true)." }
|
|
12109
|
+
},
|
|
12110
|
+
required: ["node"],
|
|
12111
|
+
additionalProperties: false
|
|
12112
|
+
},
|
|
12113
|
+
annotations: { title: "Collapse a node", ...WRITE_IDEMPOTENT },
|
|
12114
|
+
handler: async (args) => {
|
|
12115
|
+
refuseIfReadOnly();
|
|
12116
|
+
const before = state().exploded;
|
|
12117
|
+
if (!before.includes(args.node)) {
|
|
12118
|
+
findNode(args.node);
|
|
12119
|
+
throw new Error(
|
|
12120
|
+
`Node "${args.node}" is not exploded${before.length ? ` (exploded: ${before.join(", ")})` : " (nothing is exploded)"}. Call list_explodable_nodes.`
|
|
12121
|
+
);
|
|
12122
|
+
}
|
|
12123
|
+
if (await target.collapse(args.node) === false) {
|
|
12124
|
+
throw new Error(`Node "${args.node}" could not collapse (the canvas refused). Try again or call list_explodable_nodes.`);
|
|
12125
|
+
}
|
|
12126
|
+
return { node: args.node, exploded: state().exploded, collapsing: state().collapsing };
|
|
12127
|
+
}
|
|
12128
|
+
};
|
|
12129
|
+
return {
|
|
12130
|
+
id: options.id ?? "canvas-explosion",
|
|
12131
|
+
namespace: options.namespace,
|
|
12132
|
+
title: "Canvas explosion",
|
|
12133
|
+
description: "Explode canvas nodes into their child nodes and collapse them again.",
|
|
12134
|
+
skill: "pixerate-canvas",
|
|
12135
|
+
tools: () => readOnlyReason() ? [listTool] : [listTool, explodeTool, collapseTool],
|
|
12136
|
+
subscribe: options.subscribe
|
|
12137
|
+
};
|
|
12138
|
+
}
|
|
11804
12139
|
export {
|
|
11805
12140
|
ANIMATABLE_PROPERTIES,
|
|
11806
12141
|
ANIMATION_DATA_KEY,
|
|
@@ -11896,10 +12231,12 @@ export {
|
|
|
11896
12231
|
calculateCurvedPath,
|
|
11897
12232
|
calculateDagreReflowDisplacement,
|
|
11898
12233
|
calculateDirectionalDisplacement,
|
|
12234
|
+
calculateReflowDisplacement,
|
|
11899
12235
|
canCombineShapeNodes,
|
|
11900
12236
|
canOffsetShapeNodes,
|
|
11901
12237
|
canOutlineShapeNodeStrokes,
|
|
11902
12238
|
canvasEffectsPool,
|
|
12239
|
+
canvasExplosionToolTarget,
|
|
11903
12240
|
canvasHeight,
|
|
11904
12241
|
canvasNodeCaptureRect,
|
|
11905
12242
|
canvasNodeEffect,
|
|
@@ -11912,6 +12249,7 @@ export {
|
|
|
11912
12249
|
collectDescendantIds,
|
|
11913
12250
|
combineCanvasComponentsAsVariants,
|
|
11914
12251
|
combineShapeNodes,
|
|
12252
|
+
computeCanvasExplosionLayout,
|
|
11915
12253
|
computeDragBounds,
|
|
11916
12254
|
computeDragBoundsFromPoints,
|
|
11917
12255
|
computeNodesBoundingBox,
|
|
@@ -11936,6 +12274,8 @@ export {
|
|
|
11936
12274
|
createCanvasEditorDocument,
|
|
11937
12275
|
createCanvasEffectsController,
|
|
11938
12276
|
createCanvasEffectsToolProvider,
|
|
12277
|
+
createCanvasExplosionStateController,
|
|
12278
|
+
createCanvasExplosionToolProvider,
|
|
11939
12279
|
createCanvasGestureRecorder,
|
|
11940
12280
|
createCanvasInstance,
|
|
11941
12281
|
createCanvasPlaybackLocator,
|
|
@@ -11986,6 +12326,7 @@ export {
|
|
|
11986
12326
|
evaluateEasing,
|
|
11987
12327
|
evaluateTrack,
|
|
11988
12328
|
excludePaths,
|
|
12329
|
+
explosionEdgeId,
|
|
11989
12330
|
exportCssAnimation,
|
|
11990
12331
|
familySceneFromLayout,
|
|
11991
12332
|
findConnectorSnap,
|
|
@@ -12145,6 +12486,7 @@ export {
|
|
|
12145
12486
|
multiPolygonArea,
|
|
12146
12487
|
multiPolygonBounds,
|
|
12147
12488
|
nearestHandlePositions,
|
|
12489
|
+
nodeSizeOf,
|
|
12148
12490
|
normalizeBezierPath,
|
|
12149
12491
|
normalizeKeyframes,
|
|
12150
12492
|
offsetPath,
|
package/dist/canvas/types.d.cts
CHANGED
|
@@ -149,12 +149,38 @@ export type DisplacementDirection = 'right' | 'left' | 'down' | 'up' | 'vertical
|
|
|
149
149
|
* Options for directional displacement calculation.
|
|
150
150
|
*/
|
|
151
151
|
export interface DirectionalDisplacementOptions {
|
|
152
|
+
/** Which nodes move and where: `right` / `left` / `up` / `down` push one side; `vertical` opens the row up and down. Default `right`. */
|
|
152
153
|
direction?: DisplacementDirection;
|
|
154
|
+
/** Clearance between the child cluster and displaced nodes, in canvas units. Default 40. */
|
|
153
155
|
gap?: number;
|
|
156
|
+
/** With `right`, also push nodes in the origin's column that overlap the cluster up or down. Default false. */
|
|
154
157
|
spreadSiblingLanes?: boolean;
|
|
158
|
+
/** Distance in canvas units within which a node counts as a sibling (same column for `right`, same row for `row-lane`). Default 50. */
|
|
155
159
|
siblingTolerance?: number;
|
|
160
|
+
/**
|
|
161
|
+
* Floor for the top edge of nodes moved upward (e.g. below a pinned header). Rows pushed into it
|
|
162
|
+
* stack from `minY` down by `rowSpacing` so they never overlap, and never move below where they started.
|
|
163
|
+
*/
|
|
156
164
|
minY?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Ceiling for the bottom edge of nodes moved downward. Rows pushed into it stack from `maxY` up by
|
|
167
|
+
* `rowSpacing`, and never move above where they started.
|
|
168
|
+
*/
|
|
169
|
+
maxY?: number;
|
|
170
|
+
/**
|
|
171
|
+
* `freeform` (default) moves nodes by the exact clearance. `row-lane` treats the canvas as rows
|
|
172
|
+
* `rowSpacing` apart: nodes are bucketed into lanes relative to the origin row, the origin's lane
|
|
173
|
+
* stays put, and vertical deltas are rounded up to whole lanes so rows glide apart by exact lane heights.
|
|
174
|
+
*/
|
|
175
|
+
layoutMode?: 'freeform' | 'row-lane';
|
|
176
|
+
/**
|
|
177
|
+
* Lane height in canvas units. Required for `row-lane` (default: the origin's height); also the
|
|
178
|
+
* spacing between rows stacked against `minY` / `maxY` (default: the tallest node in those rows).
|
|
179
|
+
*/
|
|
180
|
+
rowSpacing?: number;
|
|
157
181
|
}
|
|
182
|
+
/** How existing nodes make room when a node explodes. */
|
|
183
|
+
export type ExplosionDisplacementStrategy = 'directional' | 'dagre' | 'none';
|
|
158
184
|
/**
|
|
159
185
|
* Options for Dagre reflow displacement calculation.
|
|
160
186
|
*/
|
|
@@ -181,8 +207,7 @@ export interface ExplodeNodeOptions<TNode extends CanvasNode = CanvasNode, TEdge
|
|
|
181
207
|
childNodes: TNode[];
|
|
182
208
|
childEdges?: TEdge[];
|
|
183
209
|
connectParentToChildren?: boolean | 'first' | 'all';
|
|
184
|
-
|
|
185
|
-
displacementStrategy?: 'directional' | 'dagre' | 'none';
|
|
210
|
+
displacementStrategy?: ExplosionDisplacementStrategy;
|
|
186
211
|
directionalOptions?: DirectionalDisplacementOptions;
|
|
187
212
|
reflowOptions?: Partial<ReflowDisplacementOptions>;
|
|
188
213
|
trajectory?: TrajectoryFunction;
|