@pixerate/editor 0.163.0 → 0.165.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.
Files changed (159) hide show
  1. package/dist/agent/index.cjs +54 -39
  2. package/dist/agent/index.js +24 -9
  3. package/dist/animation/index.cjs +6 -5
  4. package/dist/animation/index.js +5 -4
  5. package/dist/animation-editor/index.cjs +65 -64
  6. package/dist/animation-editor/index.js +8 -7
  7. package/dist/bridge/cli.cjs +3 -3
  8. package/dist/bridge/cli.js +2 -2
  9. package/dist/bridge/index.cjs +3 -3
  10. package/dist/bridge/index.js +2 -2
  11. package/dist/canvas/displacement.d.cts +34 -2
  12. package/dist/canvas/displacement.d.ts +34 -2
  13. package/dist/canvas/explosion-tools.d.cts +43 -0
  14. package/dist/canvas/explosion-tools.d.ts +43 -0
  15. package/dist/canvas/explosionLayout.d.cts +50 -0
  16. package/dist/canvas/explosionLayout.d.ts +50 -0
  17. package/dist/canvas/explosionState.d.cts +52 -0
  18. package/dist/canvas/explosionState.d.ts +52 -0
  19. package/dist/canvas/index.cjs +512 -168
  20. package/dist/canvas/index.d.cts +3 -0
  21. package/dist/canvas/index.d.ts +3 -0
  22. package/dist/canvas/index.js +434 -90
  23. package/dist/canvas/types.d.cts +27 -2
  24. package/dist/canvas/types.d.ts +27 -2
  25. package/dist/chart/controller.d.cts +31 -2
  26. package/dist/chart/controller.d.ts +31 -2
  27. package/dist/chart/index.cjs +504 -132
  28. package/dist/chart/index.d.cts +2 -0
  29. package/dist/chart/index.d.ts +2 -0
  30. package/dist/chart/index.js +386 -14
  31. package/dist/chart/layout-families.d.cts +7 -0
  32. package/dist/chart/layout-families.d.ts +7 -0
  33. package/dist/chart/links.d.cts +34 -0
  34. package/dist/chart/links.d.ts +34 -0
  35. package/dist/chart/mermaid.d.cts +9 -6
  36. package/dist/chart/mermaid.d.ts +9 -6
  37. package/dist/chart/schemas.d.cts +3 -0
  38. package/dist/chart/schemas.d.ts +3 -0
  39. package/dist/chart/spec.d.cts +3 -2
  40. package/dist/chart/spec.d.ts +3 -2
  41. package/dist/chart/style.d.cts +33 -0
  42. package/dist/chart/style.d.ts +33 -0
  43. package/dist/chart/types.d.cts +24 -2
  44. package/dist/chart/types.d.ts +24 -2
  45. package/dist/{chunk-TALQONZ5.js → chunk-2J5VLJHC.js} +3 -3
  46. package/dist/{chunk-3NEZJCBY.js → chunk-2ZRN43IN.js} +340 -201
  47. package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
  48. package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
  49. package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
  50. package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
  51. package/dist/{chunk-YSAGBIRW.cjs → chunk-64VWWR5U.cjs} +16 -16
  52. package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
  53. package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
  54. package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
  55. package/dist/{chunk-POR54B55.js → chunk-CRFSQWKQ.js} +4 -1
  56. package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
  57. package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
  58. package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
  59. package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
  60. package/dist/{chunk-5FNGLV2D.js → chunk-FEU2XVQH.js} +15 -3
  61. package/dist/{chunk-SZA42R4N.js → chunk-FRBRLKNQ.js} +154 -16
  62. package/dist/chunk-HIDFL5LQ.js +484 -0
  63. package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
  64. package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
  65. package/dist/{chunk-GKNEJ6EN.cjs → chunk-IMRXC7OA.cjs} +241 -103
  66. package/dist/chunk-KGRZMHZB.js +204 -0
  67. package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
  68. package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
  69. package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
  70. package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
  71. package/dist/{chunk-D54JHQK2.cjs → chunk-MKXK6E4O.cjs} +51 -51
  72. package/dist/{chunk-DAPQGA55.js → chunk-N7NBGT2Y.js} +2 -2
  73. package/dist/chunk-O6OGNDND.js +164 -0
  74. package/dist/{chunk-22KLZZGH.cjs → chunk-ODFCS7HF.cjs} +8 -8
  75. package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
  76. package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
  77. package/dist/{chunk-YGG7WDWT.cjs → chunk-Q25WAFTD.cjs} +21 -18
  78. package/dist/{chunk-SBQVEBSU.cjs → chunk-QO2T6KA5.cjs} +165 -67
  79. package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
  80. package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
  81. package/dist/chunk-TFF2GTGN.cjs +153 -0
  82. package/dist/chunk-TTCJPX4U.cjs +204 -0
  83. package/dist/{chunk-NZD2MZJC.cjs → chunk-TVQW26AV.cjs} +35 -23
  84. package/dist/{chunk-R3P2M7HU.cjs → chunk-UVXTOZ2B.cjs} +346 -207
  85. package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
  86. package/dist/{chunk-Z4P2RBMJ.js → chunk-V5GSY3NC.js} +101 -3
  87. package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
  88. package/dist/chunk-VU6WQSMB.cjs +164 -0
  89. package/dist/{chunk-OXERWBKE.js → chunk-X5W645RI.js} +3 -3
  90. package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
  91. package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
  92. package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
  93. package/dist/chunk-YBAPT7NL.js +153 -0
  94. package/dist/chunk-Z2UQYMPS.cjs +484 -0
  95. package/dist/color/index.cjs +5 -3
  96. package/dist/color/index.js +12 -10
  97. package/dist/diagram/index.cjs +55 -243
  98. package/dist/diagram/index.js +12 -200
  99. package/dist/export/index.cjs +7 -6
  100. package/dist/export/index.js +6 -5
  101. package/dist/extensions/index.cjs +12 -9
  102. package/dist/extensions/index.js +11 -8
  103. package/dist/gradient/index.cjs +29 -27
  104. package/dist/gradient/index.js +6 -4
  105. package/dist/image-editor/index.cjs +644 -643
  106. package/dist/image-editor/index.js +7 -6
  107. package/dist/index.cjs +21 -18
  108. package/dist/index.js +14 -11
  109. package/dist/paint-editor/index.cjs +19 -18
  110. package/dist/paint-editor/index.js +7 -6
  111. package/dist/parameters/index.cjs +2 -2
  112. package/dist/parameters/index.js +1 -1
  113. package/dist/persistence/assetWrites.d.cts +22 -0
  114. package/dist/persistence/assetWrites.d.ts +22 -0
  115. package/dist/persistence/assets.d.cts +71 -0
  116. package/dist/persistence/assets.d.ts +71 -0
  117. package/dist/persistence/documentStore.d.cts +17 -1
  118. package/dist/persistence/documentStore.d.ts +17 -1
  119. package/dist/persistence/documentTools.d.cts +5 -1
  120. package/dist/persistence/documentTools.d.ts +5 -1
  121. package/dist/persistence/editorAutosave.d.cts +7 -0
  122. package/dist/persistence/editorAutosave.d.ts +7 -0
  123. package/dist/persistence/index.cjs +141 -21
  124. package/dist/persistence/index.js +130 -10
  125. package/dist/persistence/storage.d.cts +13 -1
  126. package/dist/persistence/storage.d.ts +13 -1
  127. package/dist/spreadsheet/index.cjs +3 -3
  128. package/dist/spreadsheet/index.js +2 -2
  129. package/dist/tokens/index.cjs +11 -3
  130. package/dist/tokens/index.d.cts +1 -0
  131. package/dist/tokens/index.d.ts +1 -0
  132. package/dist/tokens/index.js +10 -2
  133. package/dist/tokens/style.d.cts +36 -0
  134. package/dist/tokens/style.d.ts +36 -0
  135. package/dist/tools/index.cjs +15 -3
  136. package/dist/tools/index.d.cts +2 -0
  137. package/dist/tools/index.d.ts +2 -0
  138. package/dist/tools/index.js +14 -2
  139. package/dist/tools/style-tools.d.cts +19 -0
  140. package/dist/tools/style-tools.d.ts +19 -0
  141. package/dist/tools/style.d.cts +212 -0
  142. package/dist/tools/style.d.ts +212 -0
  143. package/dist/tools/view.d.cts +6 -0
  144. package/dist/tools/view.d.ts +6 -0
  145. package/dist/typography/index.cjs +8 -6
  146. package/dist/typography/index.js +5 -3
  147. package/dist/vector/index.cjs +953 -952
  148. package/dist/vector/index.js +7 -6
  149. package/dist/video-editor/index.cjs +8 -7
  150. package/dist/video-editor/index.js +6 -5
  151. package/package.json +1 -1
  152. package/skills/README.md +12 -0
  153. package/skills/pixerate-agent-tools/SKILL.md +34 -1
  154. package/skills/pixerate-chart/SKILL.md +41 -9
  155. package/skills/pixerate-image-editor/SKILL.md +2 -0
  156. package/skills/pixerate-paint-editor/SKILL.md +2 -0
  157. package/skills/pixerate-tokens/SKILL.md +5 -0
  158. package/dist/chunk-ERAGQL2N.cjs +0 -68
  159. package/dist/chunk-KMU6QJB7.js +0 -68
@@ -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';
@@ -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';
@@ -53,14 +53,14 @@ import {
53
53
  shiftEndpoint,
54
54
  textWidth,
55
55
  timelineGeometry
56
- } from "../chunk-GZ2FM3ZB.js";
56
+ } from "../chunk-DKNEQX7T.js";
57
57
  import {
58
58
  TokenError,
59
59
  getTokenCollection,
60
60
  getTokenVariable,
61
61
  isTokenInScope,
62
62
  resolveToken
63
- } from "../chunk-SDHZG5TF.js";
63
+ } from "../chunk-ILP77UD6.js";
64
64
  import {
65
65
  DEFAULT_NODE_HEIGHT,
66
66
  DEFAULT_NODE_WIDTH,
@@ -113,10 +113,11 @@ import {
113
113
  } from "../chunk-5ED73EC6.js";
114
114
  import {
115
115
  layoutChart
116
- } from "../chunk-Z4P2RBMJ.js";
116
+ } from "../chunk-V5GSY3NC.js";
117
117
  import {
118
118
  createChartSpec
119
- } from "../chunk-3NEZJCBY.js";
119
+ } from "../chunk-2ZRN43IN.js";
120
+ import "../chunk-O6OGNDND.js";
120
121
  import "../chunk-II2WIF5Z.js";
121
122
  import {
122
123
  PathOpsError,
@@ -215,7 +216,7 @@ import {
215
216
  } from "../chunk-ODBT55Q7.js";
216
217
  import {
217
218
  defineEditorDocument
218
- } from "../chunk-NHLYK3AX.js";
219
+ } from "../chunk-XBQX76CT.js";
219
220
  import "../chunk-HF5QZS7Z.js";
220
221
  import {
221
222
  ANIMATION_TIME_EPSILON,
@@ -229,7 +230,7 @@ import {
229
230
  pageHidden,
230
231
  realClock,
231
232
  wrapAnimationTime
232
- } from "../chunk-H3OEM3Q2.js";
233
+ } from "../chunk-F6R67I3A.js";
233
234
  import {
234
235
  ANIMATION_EASING_LABELS,
235
236
  ANIMATION_EASING_PRESETS,
@@ -254,12 +255,13 @@ import {
254
255
  oklabToRgb,
255
256
  parseOklab,
256
257
  rgbToOklab
257
- } from "../chunk-BQWGN5WH.js";
258
+ } from "../chunk-M2TPMYJE.js";
258
259
  import {
259
260
  normalizeTokenColor
260
261
  } from "../chunk-2WPNAGHI.js";
261
- import "../chunk-QYZGBVNC.js";
262
- import "../chunk-4VU4CP7R.js";
262
+ import "../chunk-7272D3K2.js";
263
+ import "../chunk-YBAPT7NL.js";
264
+ import "../chunk-MHLU6GRJ.js";
263
265
  import "../chunk-6ONMQ6WF.js";
264
266
 
265
267
  // src/canvas/trajectory.ts
@@ -428,109 +430,162 @@ function computeNodesBoundingBox(nodes, defaultWidth = 200, defaultHeight = 60)
428
430
  height: maxY - minY
429
431
  };
430
432
  }
433
+ function nodeSizeOf(node, defaultWidth = 200, defaultHeight = 60) {
434
+ return {
435
+ width: node.measured?.width ?? node.width ?? defaultWidth,
436
+ height: node.measured?.height ?? node.height ?? defaultHeight
437
+ };
438
+ }
431
439
  function calculateDirectionalDisplacement(params) {
432
- const {
433
- existingNodes,
434
- originNode,
435
- childNodes,
436
- getNodesBounds,
437
- options = {}
438
- } = params;
440
+ const { existingNodes, originNode, childNodes, getNodesBounds, options = {} } = params;
439
441
  const {
440
442
  direction = "right",
441
443
  gap = 40,
442
444
  spreadSiblingLanes = false,
443
445
  siblingTolerance = 50,
444
- minY
446
+ minY,
447
+ maxY,
448
+ layoutMode = "freeform",
449
+ rowSpacing
445
450
  } = options;
451
+ const finite2 = (name, value) => {
452
+ if (value !== void 0 && !Number.isFinite(value)) {
453
+ throw new RangeError(`${name} must be a finite number (got ${value}).`);
454
+ }
455
+ };
456
+ finite2("gap", gap);
457
+ finite2("minY", minY);
458
+ finite2("maxY", maxY);
459
+ finite2("rowSpacing", rowSpacing);
460
+ if (rowSpacing !== void 0 && rowSpacing <= 0) {
461
+ throw new RangeError(`rowSpacing must be greater than 0 (got ${rowSpacing}).`);
462
+ }
463
+ if (minY !== void 0 && maxY !== void 0 && minY > maxY) {
464
+ throw new RangeError(`minY (${minY}) must not be greater than maxY (${maxY}).`);
465
+ }
446
466
  const displacedMap = /* @__PURE__ */ new Map();
447
467
  if (childNodes.length === 0 || existingNodes.length === 0) {
448
468
  return displacedMap;
449
469
  }
450
470
  const childBounds = getNodesBounds ? getNodesBounds(childNodes) : computeNodesBoundingBox(childNodes);
451
- const originWidth = originNode.measured?.width ?? originNode.width ?? 200;
452
- const originHeight = originNode.measured?.height ?? originNode.height ?? 60;
453
- const deltaPrimary = direction === "right" || direction === "left" ? childBounds.width + gap : childBounds.height + gap;
471
+ const { width: originWidth, height: originHeight } = nodeSizeOf(originNode);
472
+ const rowLane = layoutMode === "row-lane";
473
+ const lane = rowSpacing ?? originHeight;
474
+ const quantize = (delta) => rowLane && delta > 0 && lane > 0 ? Math.ceil(delta / lane) * lane : delta;
475
+ const laneOf = (node) => lane > 0 ? Math.round((node.position.y - originNode.position.y) / lane) : 0;
476
+ const isAbove = (node) => rowLane ? laneOf(node) < 0 : node.position.y < originNode.position.y;
477
+ const isBelow = (node) => rowLane ? laneOf(node) > 0 : node.position.y >= originNode.position.y + originHeight * 0.5;
478
+ const horizontal = direction === "right" || direction === "left";
479
+ const deltaPrimary = horizontal ? childBounds.width + gap : quantize(childBounds.height + gap);
454
480
  const deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);
455
- const deltaDown = Math.max(
456
- 0,
457
- childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap
458
- );
459
- const equalDelta = Math.max(deltaUp, deltaDown);
481
+ const deltaDown = Math.max(0, childBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap);
482
+ const equalDelta = quantize(Math.max(deltaUp, deltaDown));
460
483
  for (const node of existingNodes) {
461
484
  if (node.id === originNode.id) continue;
462
- const isSiblingRow = Math.abs(node.position.y - originNode.position.y) < siblingTolerance;
463
- switch (direction) {
464
- case "right": {
465
- if (node.position.x > originNode.position.x) {
466
- displacedMap.set(node.id, {
467
- x: node.position.x + deltaPrimary,
468
- y: node.position.y
469
- });
470
- }
471
- break;
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
- });
485
+ let newX = node.position.x;
486
+ let newY = node.position.y;
487
+ if (direction === "right") {
488
+ if (node.position.x >= originNode.position.x + originWidth * 0.5) {
489
+ newX += deltaPrimary;
490
+ } else if (spreadSiblingLanes) {
491
+ const { height } = nodeSizeOf(node);
492
+ const overlapsCluster = node.position.y + height >= childBounds.y - gap && node.position.y <= childBounds.y + childBounds.height + gap;
493
+ if (overlapsCluster && Math.abs(node.position.x - originNode.position.x) <= siblingTolerance) {
494
+ const push2 = node.position.y >= originNode.position.y ? 1 : -1;
495
+ newY += push2 * (childBounds.height * 0.5 + gap);
479
496
  }
480
- break;
481
497
  }
482
- case "down": {
483
- if (node.position.y > originNode.position.y) {
484
- displacedMap.set(node.id, {
485
- x: node.position.x,
486
- y: node.position.y + deltaPrimary
487
- });
488
- }
489
- break;
490
- }
491
- case "up": {
492
- if (node.position.y < originNode.position.y) {
493
- displacedMap.set(node.id, {
494
- x: node.position.x,
495
- y: node.position.y - deltaPrimary
496
- });
497
- }
498
- break;
498
+ } else if (direction === "left") {
499
+ if (node.position.x < originNode.position.x) newX -= deltaPrimary;
500
+ } else if (direction === "down") {
501
+ if (isBelow(node)) newY += deltaPrimary;
502
+ } else if (direction === "up") {
503
+ if (isAbove(node)) newY -= deltaPrimary;
504
+ } else if (direction === "vertical") {
505
+ if (isAbove(node)) newY -= equalDelta;
506
+ else if (isBelow(node)) newY += equalDelta;
507
+ }
508
+ if (newX !== node.position.x || newY !== node.position.y) {
509
+ displacedMap.set(node.id, { x: newX, y: newY });
510
+ }
511
+ }
512
+ if (minY !== void 0 || maxY !== void 0) {
513
+ const byId = new Map(existingNodes.map((n3) => [n3.id, n3]));
514
+ const rowsOf = (moved2) => {
515
+ const rows = /* @__PURE__ */ new Map();
516
+ for (const node of moved2) {
517
+ const key = rowLane ? laneOf(node) : node.position.y;
518
+ rows.set(key, [...rows.get(key) ?? [], node]);
499
519
  }
500
- case "vertical": {
501
- if (isSiblingRow) break;
502
- if (node.position.y < originNode.position.y) {
503
- displacedMap.set(node.id, {
504
- x: node.position.x,
505
- y: node.position.y - equalDelta
506
- });
507
- } else if (node.position.y > originNode.position.y) {
508
- displacedMap.set(node.id, {
509
- x: node.position.x,
510
- y: node.position.y + equalDelta
511
- });
512
- }
513
- break;
520
+ return [...rows.entries()].sort((a, b) => a[0] - b[0]).map(([, nodes]) => nodes);
521
+ };
522
+ const spacingFor = (nodes) => rowSpacing ?? Math.max(...nodes.map((n3) => nodeSizeOf(n3).height));
523
+ const moved = (sign) => [...displacedMap.entries()].filter(([id, pos]) => Math.sign(pos.y - byId.get(id).position.y) === sign).map(([id]) => byId.get(id));
524
+ if (minY !== void 0) {
525
+ const up = moved(-1);
526
+ if (up.length > 0) {
527
+ const spacing = spacingFor(up);
528
+ rowsOf(up).forEach((row, index) => {
529
+ const floor = minY + index * spacing;
530
+ for (const node of row) {
531
+ const pos = displacedMap.get(node.id);
532
+ displacedMap.set(node.id, { x: pos.x, y: Math.min(node.position.y, Math.max(floor, pos.y)) });
533
+ }
534
+ });
514
535
  }
515
536
  }
516
- }
517
- if (spreadSiblingLanes && (direction === "right" || direction === "left")) {
518
- for (const [nodeId, pos] of displacedMap.entries()) {
519
- const node = existingNodes.find((n3) => n3.id === nodeId);
520
- if (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {
521
- const isAbove = node.position.y < originNode.position.y;
522
- displacedMap.set(nodeId, {
523
- x: pos.x,
524
- y: pos.y + (isAbove ? -deltaUp : deltaDown)
537
+ if (maxY !== void 0) {
538
+ const down = moved(1);
539
+ if (down.length > 0) {
540
+ const spacing = spacingFor(down);
541
+ rowsOf(down).reverse().forEach((row, index) => {
542
+ for (const node of row) {
543
+ const pos = displacedMap.get(node.id);
544
+ const ceiling = maxY - index * spacing - nodeSizeOf(node).height;
545
+ displacedMap.set(node.id, { x: pos.x, y: Math.max(node.position.y, Math.min(ceiling, pos.y)) });
546
+ }
525
547
  });
526
548
  }
527
549
  }
550
+ for (const [id, pos] of [...displacedMap.entries()]) {
551
+ const node = byId.get(id);
552
+ if (pos.x === node.position.x && pos.y === node.position.y) displacedMap.delete(id);
553
+ }
528
554
  }
529
- if (minY !== void 0) {
530
- for (const [nodeId, pos] of displacedMap.entries()) {
531
- if (pos.y < minY) {
532
- displacedMap.set(nodeId, { x: pos.x, y: minY });
533
- }
555
+ return displacedMap;
556
+ }
557
+ function calculateReflowDisplacement(params) {
558
+ const { existingNodes, existingEdges, originNode, childNodes, childEdges = [], options = {} } = params;
559
+ const { direction = "LR", ranksep = 80, nodesep = 30, defaultNodeWidth = 200, defaultNodeHeight = 60 } = options;
560
+ const displacedMap = /* @__PURE__ */ new Map();
561
+ if (existingNodes.length === 0) return displacedMap;
562
+ const allNodes = [...existingNodes, ...childNodes];
563
+ const allEdges = [...existingEdges, ...childEdges];
564
+ const g = new dagre.graphlib.Graph();
565
+ g.setDefaultEdgeLabel(() => ({}));
566
+ g.setGraph({ rankdir: direction, ranksep, nodesep });
567
+ const allNodeIds = new Set(allNodes.map((n3) => n3.id));
568
+ for (const node of allNodes) {
569
+ g.setNode(node.id, nodeSizeOf(node, defaultNodeWidth, defaultNodeHeight));
570
+ }
571
+ for (const edge of allEdges) {
572
+ if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) g.setEdge(edge.source, edge.target);
573
+ }
574
+ dagre.layout(g);
575
+ const dagreOrigin = g.node(originNode.id);
576
+ if (!dagreOrigin) return displacedMap;
577
+ const origin = nodeSizeOf(originNode, defaultNodeWidth, defaultNodeHeight);
578
+ const anchorOffsetX = originNode.position.x - (dagreOrigin.x - origin.width / 2);
579
+ const anchorOffsetY = originNode.position.y - (dagreOrigin.y - origin.height / 2);
580
+ for (const node of existingNodes) {
581
+ if (node.id === originNode.id) continue;
582
+ const laidOut = g.node(node.id);
583
+ if (!laidOut) continue;
584
+ const { width, height } = nodeSizeOf(node, defaultNodeWidth, defaultNodeHeight);
585
+ const targetX = laidOut.x - width / 2 + anchorOffsetX;
586
+ const targetY = laidOut.y - height / 2 + anchorOffsetY;
587
+ if (Math.abs(targetX - node.position.x) > 0.5 || Math.abs(targetY - node.position.y) > 0.5) {
588
+ displacedMap.set(node.id, { x: targetX, y: targetY });
534
589
  }
535
590
  }
536
591
  return displacedMap;
@@ -5142,7 +5197,7 @@ var defaultLabel = (node) => typeof node.data?.label === "string" && node.data.l
5142
5197
  function getTimelineRows(nodes, ids, options = {}) {
5143
5198
  const byId = new Map(nodes.map((n3) => [n3.id, n3]));
5144
5199
  const selection = options.selection ?? [];
5145
- const labelOf = options.labelOf ?? defaultLabel;
5200
+ const labelOf2 = options.labelOf ?? defaultLabel;
5146
5201
  const rows = [];
5147
5202
  for (const id of new Set(ids)) {
5148
5203
  const node = byId.get(id);
@@ -5153,7 +5208,7 @@ function getTimelineRows(nodes, ids, options = {}) {
5153
5208
  rows.push({
5154
5209
  id: `${id}:${property}`,
5155
5210
  nodeId: id,
5156
- nodeLabel: labelOf(node),
5211
+ nodeLabel: labelOf2(node),
5157
5212
  property,
5158
5213
  label,
5159
5214
  enabled: track.enabled !== false,
@@ -11801,6 +11856,288 @@ function createCanvasGestureRecorder(options) {
11801
11856
  }
11802
11857
  };
11803
11858
  }
11859
+
11860
+ // src/canvas/explosionLayout.ts
11861
+ var explosionEdgeId = (sourceId, targetId) => `explode:${sourceId}:${targetId}`;
11862
+ function computeCanvasExplosionLayout(params) {
11863
+ const {
11864
+ existingNodes,
11865
+ existingEdges = [],
11866
+ originNode,
11867
+ childNodes,
11868
+ childEdges = [],
11869
+ strategy = "directional",
11870
+ options,
11871
+ reflowOptions,
11872
+ connectParentToChildren = false,
11873
+ createEdge,
11874
+ getNodesBounds
11875
+ } = params;
11876
+ const existingIds = new Set(existingNodes.map((n3) => n3.id));
11877
+ if (!existingIds.has(originNode.id)) {
11878
+ throw new Error(`The origin node "${originNode.id}" is not in existingNodes; pass the canvas nodes including the origin.`);
11879
+ }
11880
+ const childIds = /* @__PURE__ */ new Set();
11881
+ for (const child of childNodes) {
11882
+ if (existingIds.has(child.id)) {
11883
+ throw new Error(`Child node id "${child.id}" is already on the canvas; give exploded children unique ids.`);
11884
+ }
11885
+ if (childIds.has(child.id)) throw new Error(`Child node id "${child.id}" is repeated; give exploded children unique ids.`);
11886
+ childIds.add(child.id);
11887
+ }
11888
+ const children = childNodes.map((child) => ({ ...child, position: { ...child.position } }));
11889
+ const edges = [...childEdges];
11890
+ const makeEdge = createEdge ?? ((source, target) => ({ id: explosionEdgeId(source, target), type: "default", source, target }));
11891
+ const connect = (child) => {
11892
+ if (!edges.some((e) => e.source === originNode.id && e.target === child.id)) edges.push(makeEdge(originNode.id, child.id));
11893
+ };
11894
+ if (connectParentToChildren === true || connectParentToChildren === "all") children.forEach(connect);
11895
+ else if (connectParentToChildren === "first" && children.length > 0) connect(children[0]);
11896
+ let displacedPositions;
11897
+ if (children.length === 0 || strategy === "none") {
11898
+ displacedPositions = /* @__PURE__ */ new Map();
11899
+ } else if (strategy === "dagre") {
11900
+ displacedPositions = calculateReflowDisplacement({
11901
+ existingNodes,
11902
+ existingEdges,
11903
+ originNode,
11904
+ childNodes: children,
11905
+ childEdges: edges,
11906
+ options: reflowOptions
11907
+ });
11908
+ } else {
11909
+ displacedPositions = calculateDirectionalDisplacement({
11910
+ existingNodes,
11911
+ originNode,
11912
+ childNodes: children,
11913
+ getNodesBounds,
11914
+ options
11915
+ });
11916
+ }
11917
+ const byId = new Map(existingNodes.map((n3) => [n3.id, n3]));
11918
+ const originalPositions = /* @__PURE__ */ new Map();
11919
+ let totalDisplacementDelta = 0;
11920
+ for (const [id, to] of displacedPositions) {
11921
+ const from = byId.get(id).position;
11922
+ originalPositions.set(id, { ...from });
11923
+ totalDisplacementDelta = Math.max(totalDisplacementDelta, Math.abs(to.x - from.x), Math.abs(to.y - from.y));
11924
+ }
11925
+ return {
11926
+ displacedPositions,
11927
+ originalPositions,
11928
+ childNodes: children,
11929
+ childEdges: edges,
11930
+ childBounds: getNodesBounds ? getNodesBounds(children) : computeNodesBoundingBox(children),
11931
+ totalDisplacementDelta
11932
+ };
11933
+ }
11934
+
11935
+ // src/canvas/explosionState.ts
11936
+ function createCanvasExplosionStateController(options = {}) {
11937
+ const duration = options.collapseDurationMs ?? 380;
11938
+ if (!Number.isFinite(duration) || duration < 0) {
11939
+ throw new RangeError(`collapseDurationMs must be a finite number >= 0 (got ${duration}).`);
11940
+ }
11941
+ let snapshot = { activeId: options.initialActiveId ?? null, collapsingId: null };
11942
+ let timer = null;
11943
+ let destroyed = false;
11944
+ const listeners = /* @__PURE__ */ new Set();
11945
+ const clearTimer = () => {
11946
+ if (timer !== null) clearTimeout(timer);
11947
+ timer = null;
11948
+ };
11949
+ const set = (next) => {
11950
+ if (next.activeId === snapshot.activeId && next.collapsingId === snapshot.collapsingId) return;
11951
+ snapshot = next;
11952
+ for (const listener of [...listeners]) listener();
11953
+ };
11954
+ const finishCollapse = () => {
11955
+ clearTimer();
11956
+ if (snapshot.collapsingId !== null) set({ activeId: snapshot.activeId, collapsingId: null });
11957
+ };
11958
+ const explode = (nodeId) => {
11959
+ if (destroyed) return false;
11960
+ if (typeof nodeId !== "string" || nodeId === "") throw new TypeError("explode needs a node id.");
11961
+ if (snapshot.activeId === nodeId) return false;
11962
+ clearTimer();
11963
+ const previous = snapshot.activeId;
11964
+ set({ activeId: nodeId, collapsingId: null });
11965
+ if (previous !== null) options.onCollapse?.(previous);
11966
+ options.onExplode?.(nodeId);
11967
+ return true;
11968
+ };
11969
+ const collapse = (nodeId) => {
11970
+ if (destroyed) return false;
11971
+ const active = snapshot.activeId;
11972
+ if (active === null || nodeId !== void 0 && nodeId !== active) return false;
11973
+ clearTimer();
11974
+ set({ activeId: null, collapsingId: active });
11975
+ options.onCollapse?.(active);
11976
+ if (duration === 0) finishCollapse();
11977
+ else timer = setTimeout(() => {
11978
+ timer = null;
11979
+ finishCollapse();
11980
+ }, duration);
11981
+ return true;
11982
+ };
11983
+ return {
11984
+ get activeId() {
11985
+ return snapshot.activeId;
11986
+ },
11987
+ get collapsingId() {
11988
+ return snapshot.collapsingId;
11989
+ },
11990
+ get isExplodingOrCollapsing() {
11991
+ return snapshot.activeId !== null || snapshot.collapsingId !== null;
11992
+ },
11993
+ get destroyed() {
11994
+ return destroyed;
11995
+ },
11996
+ getSnapshot: () => snapshot,
11997
+ subscribe(listener) {
11998
+ if (destroyed) return () => {
11999
+ };
12000
+ listeners.add(listener);
12001
+ return () => listeners.delete(listener);
12002
+ },
12003
+ isExploded: (nodeId) => snapshot.activeId === nodeId,
12004
+ isCollapsing: (nodeId) => snapshot.collapsingId === nodeId,
12005
+ shouldRenderChildren: (nodeId) => snapshot.activeId === nodeId || snapshot.collapsingId === nodeId,
12006
+ explode,
12007
+ collapse,
12008
+ toggle: (nodeId) => snapshot.activeId === nodeId ? (collapse(nodeId), false) : explode(nodeId),
12009
+ finishCollapse: () => {
12010
+ if (!destroyed) finishCollapse();
12011
+ },
12012
+ destroy() {
12013
+ clearTimer();
12014
+ destroyed = true;
12015
+ listeners.clear();
12016
+ }
12017
+ };
12018
+ }
12019
+
12020
+ // src/canvas/explosion-tools.ts
12021
+ function canvasExplosionToolTarget(state, options) {
12022
+ return {
12023
+ getNodes: options.getNodes,
12024
+ canExplode: options.canExplode,
12025
+ exploded: () => state.activeId ? [state.activeId] : [],
12026
+ collapsing: () => state.collapsingId ? [state.collapsingId] : [],
12027
+ explode: (id) => {
12028
+ state.explode(id);
12029
+ },
12030
+ collapse: (id) => state.collapse(id)
12031
+ };
12032
+ }
12033
+ var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
12034
+ var WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
12035
+ var labelOf = (node) => {
12036
+ const label = node.data?.label ?? node.data?.name;
12037
+ return typeof label === "string" && label !== "" ? label : void 0;
12038
+ };
12039
+ function createCanvasExplosionToolProvider(target, options = {}) {
12040
+ const readOnlyReason = () => {
12041
+ const ro = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
12042
+ return ro ? typeof ro === "string" ? ro : "Exploding nodes is disabled on this canvas." : null;
12043
+ };
12044
+ const reasonNotExplodable = (node) => {
12045
+ const ok = target.canExplode ? target.canExplode(node) : true;
12046
+ if (ok === true) return null;
12047
+ return typeof ok === "string" && ok ? ok : `Node "${node.id}" has nothing to explode into.`;
12048
+ };
12049
+ const state = () => ({ exploded: [...target.exploded()], collapsing: [...target.collapsing?.() ?? []] });
12050
+ const findNode = (id) => {
12051
+ const node = target.getNodes().find((n3) => n3.id === id);
12052
+ if (!node) throw new Error(`No node "${id}" on the canvas. Call list_explodable_nodes for node ids.`);
12053
+ return node;
12054
+ };
12055
+ const refuseIfReadOnly = () => {
12056
+ const reason = readOnlyReason();
12057
+ if (reason) throw new Error(reason);
12058
+ };
12059
+ const listTool = {
12060
+ name: "list_explodable_nodes",
12061
+ title: "List explodable nodes",
12062
+ 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.",
12063
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
12064
+ annotations: { title: "List explodable nodes", ...READ },
12065
+ handler: () => {
12066
+ const { exploded, collapsing } = state();
12067
+ const nodes = target.getNodes().filter((n3) => exploded.includes(n3.id) || reasonNotExplodable(n3) === null).map((n3) => ({
12068
+ id: n3.id,
12069
+ ...labelOf(n3) ? { label: labelOf(n3) } : {},
12070
+ exploded: exploded.includes(n3.id),
12071
+ collapsing: collapsing.includes(n3.id)
12072
+ }));
12073
+ return { nodes, exploded, collapsing, multiple: target.multiple === true, readOnly: readOnlyReason() };
12074
+ }
12075
+ };
12076
+ const explodeTool = {
12077
+ name: "explode_node",
12078
+ title: "Explode a node",
12079
+ 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.",
12080
+ inputSchema: {
12081
+ type: "object",
12082
+ properties: {
12083
+ node: { type: "string", minLength: 1, description: "Id of the node to explode, from list_explodable_nodes." }
12084
+ },
12085
+ required: ["node"],
12086
+ additionalProperties: false
12087
+ },
12088
+ annotations: { title: "Explode a node", ...WRITE_IDEMPOTENT },
12089
+ handler: async (args) => {
12090
+ refuseIfReadOnly();
12091
+ const node = findNode(args.node);
12092
+ const before = state().exploded;
12093
+ if (before.includes(node.id)) return { node: node.id, changed: false, exploded: before, collapsed: [] };
12094
+ const reason = reasonNotExplodable(node);
12095
+ if (reason) throw new Error(reason);
12096
+ if (await target.explode(node.id) === false) {
12097
+ 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.`);
12098
+ }
12099
+ const after = state().exploded;
12100
+ return { node: node.id, changed: true, exploded: after, collapsed: before.filter((id) => !after.includes(id)) };
12101
+ }
12102
+ };
12103
+ const collapseTool = {
12104
+ name: "collapse_node",
12105
+ title: "Collapse a node",
12106
+ 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.",
12107
+ inputSchema: {
12108
+ type: "object",
12109
+ properties: {
12110
+ node: { type: "string", minLength: 1, description: "Id of an exploded node, from list_explodable_nodes (exploded: true)." }
12111
+ },
12112
+ required: ["node"],
12113
+ additionalProperties: false
12114
+ },
12115
+ annotations: { title: "Collapse a node", ...WRITE_IDEMPOTENT },
12116
+ handler: async (args) => {
12117
+ refuseIfReadOnly();
12118
+ const before = state().exploded;
12119
+ if (!before.includes(args.node)) {
12120
+ findNode(args.node);
12121
+ throw new Error(
12122
+ `Node "${args.node}" is not exploded${before.length ? ` (exploded: ${before.join(", ")})` : " (nothing is exploded)"}. Call list_explodable_nodes.`
12123
+ );
12124
+ }
12125
+ if (await target.collapse(args.node) === false) {
12126
+ throw new Error(`Node "${args.node}" could not collapse (the canvas refused). Try again or call list_explodable_nodes.`);
12127
+ }
12128
+ return { node: args.node, exploded: state().exploded, collapsing: state().collapsing };
12129
+ }
12130
+ };
12131
+ return {
12132
+ id: options.id ?? "canvas-explosion",
12133
+ namespace: options.namespace,
12134
+ title: "Canvas explosion",
12135
+ description: "Explode canvas nodes into their child nodes and collapse them again.",
12136
+ skill: "pixerate-canvas",
12137
+ tools: () => readOnlyReason() ? [listTool] : [listTool, explodeTool, collapseTool],
12138
+ subscribe: options.subscribe
12139
+ };
12140
+ }
11804
12141
  export {
11805
12142
  ANIMATABLE_PROPERTIES,
11806
12143
  ANIMATION_DATA_KEY,
@@ -11896,10 +12233,12 @@ export {
11896
12233
  calculateCurvedPath,
11897
12234
  calculateDagreReflowDisplacement,
11898
12235
  calculateDirectionalDisplacement,
12236
+ calculateReflowDisplacement,
11899
12237
  canCombineShapeNodes,
11900
12238
  canOffsetShapeNodes,
11901
12239
  canOutlineShapeNodeStrokes,
11902
12240
  canvasEffectsPool,
12241
+ canvasExplosionToolTarget,
11903
12242
  canvasHeight,
11904
12243
  canvasNodeCaptureRect,
11905
12244
  canvasNodeEffect,
@@ -11912,6 +12251,7 @@ export {
11912
12251
  collectDescendantIds,
11913
12252
  combineCanvasComponentsAsVariants,
11914
12253
  combineShapeNodes,
12254
+ computeCanvasExplosionLayout,
11915
12255
  computeDragBounds,
11916
12256
  computeDragBoundsFromPoints,
11917
12257
  computeNodesBoundingBox,
@@ -11936,6 +12276,8 @@ export {
11936
12276
  createCanvasEditorDocument,
11937
12277
  createCanvasEffectsController,
11938
12278
  createCanvasEffectsToolProvider,
12279
+ createCanvasExplosionStateController,
12280
+ createCanvasExplosionToolProvider,
11939
12281
  createCanvasGestureRecorder,
11940
12282
  createCanvasInstance,
11941
12283
  createCanvasPlaybackLocator,
@@ -11986,6 +12328,7 @@ export {
11986
12328
  evaluateEasing,
11987
12329
  evaluateTrack,
11988
12330
  excludePaths,
12331
+ explosionEdgeId,
11989
12332
  exportCssAnimation,
11990
12333
  familySceneFromLayout,
11991
12334
  findConnectorSnap,
@@ -12145,6 +12488,7 @@ export {
12145
12488
  multiPolygonArea,
12146
12489
  multiPolygonBounds,
12147
12490
  nearestHandlePositions,
12491
+ nodeSizeOf,
12148
12492
  normalizeBezierPath,
12149
12493
  normalizeKeyframes,
12150
12494
  offsetPath,