@pixerate/editor 0.126.0 → 0.128.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 (125) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.d.cts +3 -3
  3. package/dist/agent/index.d.ts +3 -3
  4. package/dist/agent/index.js +1 -1
  5. package/dist/animation/index.d.cts +2 -3
  6. package/dist/animation/index.d.ts +2 -3
  7. package/dist/animation-editor/index.cjs +63 -63
  8. package/dist/animation-editor/index.cjs.map +1 -1
  9. package/dist/animation-editor/index.d.cts +7 -9
  10. package/dist/animation-editor/index.d.ts +7 -9
  11. package/dist/animation-editor/index.js +10 -10
  12. package/dist/{arrange-BlfgJ_wt.d.ts → arrange-BUREmgD9.d.ts} +1 -1
  13. package/dist/{arrange-CD6Gh-pd.d.cts → arrange-CKXK_hf7.d.cts} +1 -1
  14. package/dist/{bezier-CWv6LQal.d.ts → bezier-CvgQiYRg.d.cts} +23 -3
  15. package/dist/{bezier-CBzKKDjl.d.cts → bezier-DP4NnJp3.d.ts} +23 -3
  16. package/dist/{brushTypes-IG7ZZjIN.d.cts → brushTypes-C7N3EBK7.d.cts} +56 -1
  17. package/dist/{brushTypes-IG7ZZjIN.d.ts → brushTypes-C7N3EBK7.d.ts} +56 -1
  18. package/dist/{brushstrokeRender-EELTV7U4.cjs → brushstrokeRender-JNHVEUFG.cjs} +29 -10
  19. package/dist/brushstrokeRender-JNHVEUFG.cjs.map +1 -0
  20. package/dist/{brushstrokeRender-LRV7GXGD.js → brushstrokeRender-SII6MG4R.js} +25 -6
  21. package/dist/brushstrokeRender-SII6MG4R.js.map +1 -0
  22. package/dist/{builtins-DMIsnNst.d.cts → builtins-Cc-bH-3U.d.cts} +3 -2
  23. package/dist/{builtins-B5-ipm5u.d.ts → builtins-ZU1GJnUA.d.ts} +3 -2
  24. package/dist/canvas/index.cjs +435 -38
  25. package/dist/canvas/index.cjs.map +1 -1
  26. package/dist/canvas/index.d.cts +206 -16
  27. package/dist/canvas/index.d.ts +206 -16
  28. package/dist/canvas/index.js +408 -11
  29. package/dist/canvas/index.js.map +1 -1
  30. package/dist/{chunk-MPQAO33O.js → chunk-A6KHMKI4.js} +3 -3
  31. package/dist/{chunk-V5YGPRCZ.cjs → chunk-BEGEWQHD.cjs} +41 -41
  32. package/dist/{chunk-V5YGPRCZ.cjs.map → chunk-BEGEWQHD.cjs.map} +1 -1
  33. package/dist/{chunk-N3FVUOZD.cjs → chunk-BJTCSLF6.cjs} +7 -7
  34. package/dist/{chunk-N3FVUOZD.cjs.map → chunk-BJTCSLF6.cjs.map} +1 -1
  35. package/dist/{chunk-7LQO6OHH.cjs → chunk-DDSO4HS7.cjs} +9 -5
  36. package/dist/chunk-DDSO4HS7.cjs.map +1 -0
  37. package/dist/{chunk-AKU2KQ47.cjs → chunk-DFSZO2B5.cjs} +70 -3
  38. package/dist/chunk-DFSZO2B5.cjs.map +1 -0
  39. package/dist/{chunk-WUDPFWAN.js → chunk-F7Q2NE2Y.js} +3 -3
  40. package/dist/{chunk-GUZ47CJM.cjs → chunk-IZ4WRXZA.cjs} +1 -1
  41. package/dist/{chunk-GUZ47CJM.cjs.map → chunk-IZ4WRXZA.cjs.map} +1 -1
  42. package/dist/{chunk-GLWZXQ4Q.js → chunk-KHNZ6MOU.js} +1 -1
  43. package/dist/chunk-KHNZ6MOU.js.map +1 -0
  44. package/dist/{chunk-GCJWMEYW.cjs → chunk-KSJLDRHK.cjs} +46 -46
  45. package/dist/{chunk-GCJWMEYW.cjs.map → chunk-KSJLDRHK.cjs.map} +1 -1
  46. package/dist/{chunk-2SDGZAJR.js → chunk-LSXXAJG3.js} +70 -3
  47. package/dist/chunk-LSXXAJG3.js.map +1 -0
  48. package/dist/{chunk-QQKTOA4U.cjs → chunk-ORE334I6.cjs} +132 -132
  49. package/dist/{chunk-QQKTOA4U.cjs.map → chunk-ORE334I6.cjs.map} +1 -1
  50. package/dist/{chunk-X6KTSFAE.js → chunk-Q4NBEVV5.js} +2 -2
  51. package/dist/{chunk-FPRIUR4T.js → chunk-ROZFX4QI.js} +3 -3
  52. package/dist/{chunk-J67OLQUQ.js → chunk-SVYE5HIM.js} +7 -3
  53. package/dist/chunk-SVYE5HIM.js.map +1 -0
  54. package/dist/{chunk-O5RVCDEC.cjs → chunk-XLUYXUBD.cjs} +88 -88
  55. package/dist/{chunk-O5RVCDEC.cjs.map → chunk-XLUYXUBD.cjs.map} +1 -1
  56. package/dist/{chunk-FG274FYT.js → chunk-YGJIER2R.js} +2 -2
  57. package/dist/commands/index.cjs +2 -2
  58. package/dist/commands/index.d.cts +3 -3
  59. package/dist/commands/index.d.ts +3 -3
  60. package/dist/commands/index.js +1 -1
  61. package/dist/effects/index.d.cts +6 -38
  62. package/dist/effects/index.d.ts +6 -38
  63. package/dist/evaluate-BbCeF6pt.d.ts +114 -0
  64. package/dist/evaluate-BsxYieLN.d.cts +114 -0
  65. package/dist/{export-Bp7ji2r4.d.cts → export-BkYQvV56.d.cts} +4 -4
  66. package/dist/{export-DVl4fWy5.d.cts → export-Cbr9IqpJ.d.cts} +1 -1
  67. package/dist/{export-C1s4eolZ.d.ts → export-ClPKUB-v.d.ts} +1 -1
  68. package/dist/{export-DAyPY94f.d.ts → export-DHrFJ8r_.d.ts} +4 -4
  69. package/dist/{helpers-i95Ob6Vi.d.cts → helpers-C5aAd44p.d.cts} +67 -4
  70. package/dist/{helpers-BBLlekuM.d.ts → helpers-CPWcK504.d.ts} +67 -4
  71. package/dist/image-editor/index.cjs +606 -606
  72. package/dist/image-editor/index.cjs.map +1 -1
  73. package/dist/image-editor/index.d.cts +12 -14
  74. package/dist/image-editor/index.d.ts +12 -14
  75. package/dist/image-editor/index.js +4 -4
  76. package/dist/{opentype-D80AVmMu.d.cts → opentype-BOewaGQx.d.cts} +1 -1
  77. package/dist/{opentype-14PDI5D2.d.ts → opentype-sQrF3Ry1.d.ts} +1 -1
  78. package/dist/paint/index.d.cts +2 -2
  79. package/dist/paint/index.d.ts +2 -2
  80. package/dist/paint-editor/index.cjs +9 -9
  81. package/dist/paint-editor/index.cjs.map +1 -1
  82. package/dist/paint-editor/index.d.cts +9 -11
  83. package/dist/paint-editor/index.d.ts +9 -11
  84. package/dist/paint-editor/index.js +4 -4
  85. package/dist/{pathfinder-DjISxkUv.d.ts → pathfinder-BwJx0_zM.d.ts} +2 -2
  86. package/dist/{pathfinder-C0UoFypx.d.cts → pathfinder-CnkW7zLI.d.cts} +2 -2
  87. package/dist/{player-cGLrngL_.d.ts → player-BK2kKbfd.d.ts} +1 -1
  88. package/dist/{player-CBtEoU2x.d.cts → player-CmbY3218.d.cts} +1 -1
  89. package/dist/pool-C7GtrL34.d.cts +34 -0
  90. package/dist/pool-CTrtciaN.d.ts +34 -0
  91. package/dist/{rand-CfinE-1c.d.cts → rand-DTXAssW1.d.cts} +4 -4
  92. package/dist/{rand-DtazXGOB.d.ts → rand-pQQ2sJTk.d.ts} +4 -4
  93. package/dist/{svg-DJKriJ4A.d.ts → svg-C4LlFnEw.d.cts} +2 -3
  94. package/dist/{svg-BkRL-fZJ.d.cts → svg-CUzZa0EO.d.ts} +2 -3
  95. package/dist/{tools-CM8Oe8F3.d.ts → tools-AugT22z3.d.ts} +1 -1
  96. package/dist/{tools-DZCv2kX1.d.cts → tools-BpCmUNxb.d.cts} +1 -1
  97. package/dist/vector/index.cjs +918 -918
  98. package/dist/vector/index.cjs.map +1 -1
  99. package/dist/vector/index.d.cts +11 -13
  100. package/dist/vector/index.d.ts +11 -13
  101. package/dist/vector/index.js +11 -11
  102. package/dist/video-editor/index.d.cts +6 -8
  103. package/dist/video-editor/index.d.ts +6 -8
  104. package/dist/{warp-k_PdtXOG.d.ts → warp-CIsO617e.d.ts} +1 -1
  105. package/dist/{warp-CH9MM435.d.cts → warp-D62N_5VK.d.cts} +1 -1
  106. package/package.json +1 -1
  107. package/skills/pixerate-canvas/SKILL.md +30 -2
  108. package/dist/behavior-Ymbld6jL.d.cts +0 -60
  109. package/dist/behavior-Ymbld6jL.d.ts +0 -60
  110. package/dist/brushstrokeRender-EELTV7U4.cjs.map +0 -1
  111. package/dist/brushstrokeRender-LRV7GXGD.js.map +0 -1
  112. package/dist/chunk-2SDGZAJR.js.map +0 -1
  113. package/dist/chunk-7LQO6OHH.cjs.map +0 -1
  114. package/dist/chunk-AKU2KQ47.cjs.map +0 -1
  115. package/dist/chunk-GLWZXQ4Q.js.map +0 -1
  116. package/dist/chunk-J67OLQUQ.js.map +0 -1
  117. package/dist/evaluate-2S3vSPLy.d.cts +0 -55
  118. package/dist/evaluate-CfmT6xA3.d.ts +0 -55
  119. package/dist/gradient-KDfCvhuj.d.cts +0 -66
  120. package/dist/gradient-KDfCvhuj.d.ts +0 -66
  121. /package/dist/{chunk-MPQAO33O.js.map → chunk-A6KHMKI4.js.map} +0 -0
  122. /package/dist/{chunk-WUDPFWAN.js.map → chunk-F7Q2NE2Y.js.map} +0 -0
  123. /package/dist/{chunk-X6KTSFAE.js.map → chunk-Q4NBEVV5.js.map} +0 -0
  124. /package/dist/{chunk-FPRIUR4T.js.map → chunk-ROZFX4QI.js.map} +0 -0
  125. /package/dist/{chunk-FG274FYT.js.map → chunk-YGJIER2R.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  hatchPath,
7
7
  interiorPoint,
8
8
  pathfinder
9
- } from "../chunk-X6KTSFAE.js";
9
+ } from "../chunk-Q4NBEVV5.js";
10
10
  import {
11
11
  ARCH_METRICS,
12
12
  GANTT_METRICS,
@@ -49,8 +49,9 @@ import {
49
49
  pathDataToPolyline,
50
50
  profileAlongPolyline,
51
51
  renderShapeBrushstroke,
52
- validateCanvasBrush
53
- } from "../chunk-J67OLQUQ.js";
52
+ validateCanvasBrush,
53
+ validateCanvasBrushPaint
54
+ } from "../chunk-SVYE5HIM.js";
54
55
  import {
55
56
  TokenError,
56
57
  getTokenCollection,
@@ -82,6 +83,9 @@ import {
82
83
  CAPTURE_OVERLAY_SELECTORS,
83
84
  rasterizeElement
84
85
  } from "../chunk-O2W2SP3C.js";
86
+ import {
87
+ EffectRendererPool
88
+ } from "../chunk-FJ3324TZ.js";
85
89
  import {
86
90
  createCaptureToolProvider,
87
91
  unionCaptureRects
@@ -153,7 +157,7 @@ import {
153
157
  unitePaths,
154
158
  windingNumber,
155
159
  xorPolygons
156
- } from "../chunk-FG274FYT.js";
160
+ } from "../chunk-YGJIER2R.js";
157
161
  import {
158
162
  CANVAS_GRADIENT_PRESETS,
159
163
  PIXERATE_BRAND_GRADIENT,
@@ -180,7 +184,14 @@ import {
180
184
  resolveCanvasGradient,
181
185
  shapeToPolygon,
182
186
  simplifyRing
183
- } from "../chunk-GLWZXQ4Q.js";
187
+ } from "../chunk-KHNZ6MOU.js";
188
+ import {
189
+ EffectUnsupportedError,
190
+ EffectsSlot,
191
+ createEffectToolProvider,
192
+ resolveEffectValues
193
+ } from "../chunk-QFSTQHZY.js";
194
+ import "../chunk-4FX5ACVG.js";
184
195
  import {
185
196
  ANIMATION_TIME_EPSILON,
186
197
  AnimationPlayer,
@@ -1599,6 +1610,8 @@ function cloneScene(scene) {
1599
1610
  viewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }
1600
1611
  };
1601
1612
  if (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];
1613
+ const t = scene.transition;
1614
+ if (t && typeof t.effect === "string" && t.effect) copy.transition = JSON.parse(JSON.stringify(t));
1602
1615
  return copy;
1603
1616
  }
1604
1617
  function parseCanvasScenes(input) {
@@ -7975,11 +7988,11 @@ function createCanvasComponentsController(options) {
7975
7988
  },
7976
7989
  canResetOverrides(id) {
7977
7990
  const at = target(id);
7978
- const info = at ? inspectCanvasInstance(nodes(), at) : void 0;
7979
- if (!info) return "Select an instance or a layer inside one.";
7980
- const changedProps = !info.nested && info.properties.some((p) => p.value !== p.default);
7981
- const own = info.id === at ? info.overrides.length > 0 : getNodeOverrides(nodes().find((n3) => n3.id === at)).length > 0;
7982
- return own || info.id === at && changedProps ? true : "No overrides to reset.";
7991
+ const info2 = at ? inspectCanvasInstance(nodes(), at) : void 0;
7992
+ if (!info2) return "Select an instance or a layer inside one.";
7993
+ const changedProps = !info2.nested && info2.properties.some((p) => p.value !== p.default);
7994
+ const own = info2.id === at ? info2.overrides.length > 0 : getNodeOverrides(nodes().find((n3) => n3.id === at)).length > 0;
7995
+ return own || info2.id === at && changedProps ? true : "No overrides to reset.";
7983
7996
  },
7984
7997
  /** Push the (outermost) instance's overrides to its main component. */
7985
7998
  pushOverrides(id) {
@@ -8263,7 +8276,12 @@ function strokeToShapeNode(stroke, options = {}) {
8263
8276
  strokeLinecap: stroke.style.cap ?? "round",
8264
8277
  strokeLinejoin: stroke.style.join ?? "round",
8265
8278
  ...strokeGradient ? { strokeGradient } : {},
8266
- ...stroke.style.brush !== void 0 ? { brush: freehandBrushstroke(stroke.style.brush, localPoints, stroke.samples, options.profileLength) } : {},
8279
+ ...stroke.style.brush !== void 0 ? {
8280
+ brush: {
8281
+ ...freehandBrushstroke(stroke.style.brush, localPoints, stroke.samples, options.profileLength),
8282
+ ...stroke.style.paint !== void 0 ? { paint: stroke.style.paint } : {}
8283
+ }
8284
+ } : {},
8267
8285
  fill: "none"
8268
8286
  },
8269
8287
  tool: stroke.tool
@@ -8432,7 +8450,11 @@ function createCanvasDrawController(options) {
8432
8450
  if ((input.pressure || input.tiltX || input.tiltY) && style.brush === void 0) {
8433
8451
  throw new Error("pressure and tilt profiles only shape brushed strokes: pass a brush too (see canvas.listBrushes).");
8434
8452
  }
8453
+ if (style.paint !== void 0 && style.brush === void 0) {
8454
+ throw new Error("paint (gradient paint) only colours brushed strokes: pass a brush too (see canvas.listBrushes), or use color for a plain stroke.");
8455
+ }
8435
8456
  if (style.brush !== void 0) await validateCanvasBrush(style.brush);
8457
+ if (style.paint !== void 0) style.paint = (await loadCanvasBrushstrokeRenderer()).resolveCanvasPaint(style.paint);
8436
8458
  const id = options.generateNodeId?.() ?? `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
8437
8459
  const smoothed = input.streamline ? streamlinePoints(points, options.drawOptions?.streamline ?? 0.35) : [...points];
8438
8460
  const node = strokeToShapeNode(
@@ -11601,6 +11623,371 @@ function createCanvasCaptureToolProvider(options) {
11601
11623
  id: options.id ?? "canvas-capture"
11602
11624
  });
11603
11625
  }
11626
+
11627
+ // src/canvas/effects.ts
11628
+ var CANVAS_EFFECT_PLACES = {
11629
+ /** Paints a shape's inside. */
11630
+ fill: ["generator", "interactive"],
11631
+ /** Draws over a node's box. */
11632
+ overlay: ["overlay", "title", "interactive", "generator"],
11633
+ transition: ["transition"]
11634
+ };
11635
+ function canvasNodeEffect(node) {
11636
+ const shape = node.data?.shape;
11637
+ return isShapeDescription(shape) ? shape.effect : void 0;
11638
+ }
11639
+ function createCanvasEffectsController(options) {
11640
+ const slot = new EffectsSlot();
11641
+ if (options.effects) slot.set(options.effects);
11642
+ const describe = async (effect, place2) => {
11643
+ const d = await slot.require().describe(effect);
11644
+ const allowed = CANVAS_EFFECT_PLACES[place2];
11645
+ if (d.kind === "motion" || !d.uses.some((u) => allowed.includes(u))) {
11646
+ const where = place2 === "transition" ? "a scene transition" : place2 === "fill" ? "a fill" : "an overlay";
11647
+ throw new EffectUnsupportedError(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be ${where}, which takes ${allowed.join(" or ")} effects; search_effects with use "${allowed[0]}" lists ones that fit.`, d.id);
11648
+ }
11649
+ return d;
11650
+ };
11651
+ const shapeNode = (id) => {
11652
+ const node = options.getNodes().find((n3) => n3.id === id);
11653
+ if (!node) throw new Error(`There is no node "${id}"; the nodes are ${options.getNodes().map((n3) => `"${n3.id}"`).slice(0, 20).join(", ") || "none"}.`);
11654
+ const shape = node.data?.shape;
11655
+ if (!isShapeDescription(shape)) throw new Error(`Node "${id}" is not a shape; effects draw on shape nodes (data.shape).`);
11656
+ return { node, shape };
11657
+ };
11658
+ const commit = (label, next) => {
11659
+ const before = options.getNodes();
11660
+ if (options.history) options.history.execute({ name: label, execute: () => options.setNodes(next), undo: () => options.setNodes(before), redo: () => options.setNodes(next) });
11661
+ else options.setNodes(next);
11662
+ };
11663
+ const withEffect = (id, effect) => options.getNodes().map((n3) => {
11664
+ if (n3.id !== id) return n3;
11665
+ const shape = { ...n3.data.shape };
11666
+ if (effect) shape.effect = effect;
11667
+ else delete shape.effect;
11668
+ return { ...n3, data: { ...n3.data, shape } };
11669
+ });
11670
+ const mergeValues = (d, was, values) => {
11671
+ const base = { ...was };
11672
+ const given = {};
11673
+ for (const [k, v] of Object.entries(values)) {
11674
+ if (v !== null) given[k] = v;
11675
+ else if (k in d.controls.properties) base[k] = d.defaults[k];
11676
+ else throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
11677
+ }
11678
+ return { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };
11679
+ };
11680
+ return {
11681
+ /** The provider slot. */
11682
+ effects: slot,
11683
+ get effectsProvider() {
11684
+ return slot.provider;
11685
+ },
11686
+ /** Registers (or with `null` removes) the effects provider. */
11687
+ setEffectsProvider(provider) {
11688
+ slot.set(provider);
11689
+ },
11690
+ /** Registers `provider` and returns a function that removes it again. */
11691
+ useEffects(provider) {
11692
+ return slot.use(provider);
11693
+ },
11694
+ /** The shape nodes that carry effects (or one node's). */
11695
+ list(nodeId) {
11696
+ return options.getNodes().filter((n3) => !nodeId || n3.id === nodeId).map((n3) => ({ nodeId: n3.id, effect: canvasNodeEffect(n3) })).filter((e) => !!e.effect);
11697
+ },
11698
+ /**
11699
+ * Draws a provider effect on a shape node: `fill` paints its inside (generators), `overlay`
11700
+ * draws over its box (overlays, titles, interactive effects). Replaces any effect it had. One
11701
+ * undoable step. Returns the effect.
11702
+ */
11703
+ async applyNodeEffect(nodeId, effect, opts = {}) {
11704
+ shapeNode(nodeId);
11705
+ const mode = opts.mode ?? "fill";
11706
+ const d = await describe(effect, mode);
11707
+ if (opts.opacity !== void 0 && !(opts.opacity >= 0 && opts.opacity <= 1)) throw new Error("opacity must be from 0 to 1.");
11708
+ const next = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values), mode, ...opts.opacity !== void 0 && opts.opacity !== 1 ? { opacity: opts.opacity } : {} };
11709
+ commit(`Add effect ${d.name}`, withEffect(nodeId, next));
11710
+ return next;
11711
+ },
11712
+ /** Changes a node's effect: `values` merged (`null` resets), `mode`, `opacity`, `playing`. One undoable step. */
11713
+ async updateNodeEffect(nodeId, changes) {
11714
+ const { shape } = shapeNode(nodeId);
11715
+ const was = shape.effect;
11716
+ if (!was) throw new Error(`Node "${nodeId}" has no effect; add one with apply_effect.`);
11717
+ const next = { ...was };
11718
+ if (changes.values || changes.mode) {
11719
+ const d = await describe(was.effect, changes.mode ?? was.mode);
11720
+ if (changes.values) next.values = mergeValues(d, was.values, changes.values);
11721
+ if (changes.mode) next.mode = changes.mode;
11722
+ } else slot.require();
11723
+ if (changes.opacity !== void 0) {
11724
+ if (!(changes.opacity >= 0 && changes.opacity <= 1)) throw new Error("opacity must be from 0 to 1.");
11725
+ if (changes.opacity === 1) delete next.opacity;
11726
+ else next.opacity = changes.opacity;
11727
+ }
11728
+ if (changes.playing !== void 0) {
11729
+ if (changes.playing) delete next.playing;
11730
+ else next.playing = false;
11731
+ }
11732
+ commit(`Change effect ${was.name ?? was.effect}`, withEffect(nodeId, next));
11733
+ return next;
11734
+ },
11735
+ /** Removes a node's effect (works without a provider). One undoable step. */
11736
+ removeNodeEffect(nodeId) {
11737
+ const { shape } = shapeNode(nodeId);
11738
+ if (!shape.effect) throw new Error(`Node "${nodeId}" has no effect.`);
11739
+ commit(`Remove effect ${shape.effect.name ?? shape.effect.effect}`, withEffect(nodeId, void 0));
11740
+ return shape.effect;
11741
+ },
11742
+ /** The scenes, in order (empty without a Scenes store). */
11743
+ scenes() {
11744
+ return options.scenes?.getState().scenes ?? [];
11745
+ },
11746
+ /** The transition into a scene, if it has one. */
11747
+ sceneTransition(sceneId) {
11748
+ return options.scenes?.getScene(sceneId)?.transition;
11749
+ },
11750
+ /**
11751
+ * Sets (or with `effect` null removes) the provider transition played when the canvas moves to
11752
+ * a scene. Returns the transition.
11753
+ */
11754
+ async setSceneTransition(sceneId, effect, opts = {}) {
11755
+ const scenes2 = options.scenes;
11756
+ if (!scenes2) throw new Error("This canvas has no Scenes store.");
11757
+ if (!scenes2.getScene(sceneId)) throw new Error(`There is no scene "${sceneId}"; the scenes are ${scenes2.getState().scenes.map((s) => `"${s.id}"`).join(", ") || "none"}.`);
11758
+ if (effect === null) {
11759
+ scenes2.update(sceneId, { transition: void 0 });
11760
+ return null;
11761
+ }
11762
+ const d = await describe(effect, "transition");
11763
+ const duration = opts.duration ?? d.duration ?? 600;
11764
+ if (!(duration > 0)) throw new Error("duration must be a number of ms above 0.");
11765
+ const transition = { effect: d.id, provider: slot.require().id, name: d.name, values: resolveEffectValues(d, opts.values), duration };
11766
+ scenes2.update(sceneId, { transition });
11767
+ return transition;
11768
+ }
11769
+ };
11770
+ }
11771
+
11772
+ // src/canvas/effect-player.ts
11773
+ var defaultClock = {
11774
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
11775
+ frame: (cb) => {
11776
+ if (typeof requestAnimationFrame === "undefined") {
11777
+ const t = setTimeout(cb, 16);
11778
+ return () => clearTimeout(t);
11779
+ }
11780
+ const id = requestAnimationFrame(cb);
11781
+ return () => cancelAnimationFrame(id);
11782
+ }
11783
+ };
11784
+ var canvasEffectsPool = new EffectRendererPool({ maxRenderers: 6 });
11785
+ function draw(target, p) {
11786
+ const ctx = target.getContext("2d");
11787
+ if (!ctx || typeof ImageData === "undefined") return;
11788
+ ctx.putImageData(new ImageData(new Uint8ClampedArray(p.data), p.width, p.height), 0, 0);
11789
+ }
11790
+ function runCanvasNodeEffect(options) {
11791
+ const { provider, effect, target } = options;
11792
+ const clock = options.clock ?? defaultClock;
11793
+ const pool = options.pool ?? canvasEffectsPool;
11794
+ const size2 = { width: Math.max(1, Math.round(target.width)), height: Math.max(1, Math.round(target.height)) };
11795
+ const start = clock.now();
11796
+ let stopped = false;
11797
+ let cancel = () => {
11798
+ };
11799
+ const tick = () => {
11800
+ if (stopped) return;
11801
+ const time = options.animate === false || effect.playing === false ? 0 : clock.now() - start;
11802
+ pool.render(provider, effect.effect, size2, { time, values: effect.values }).then(
11803
+ (p) => {
11804
+ if (stopped) return;
11805
+ draw(target, p);
11806
+ if (options.animate !== false && effect.playing !== false) cancel = clock.frame(tick);
11807
+ },
11808
+ (e) => {
11809
+ if (!stopped) options.onError?.(e);
11810
+ }
11811
+ );
11812
+ };
11813
+ tick();
11814
+ return {
11815
+ stop() {
11816
+ stopped = true;
11817
+ cancel();
11818
+ }
11819
+ };
11820
+ }
11821
+ async function playCanvasSceneTransition(options) {
11822
+ const { provider, transition, from, to, target } = options;
11823
+ const clock = options.clock ?? defaultClock;
11824
+ const pool = options.pool ?? canvasEffectsPool;
11825
+ const duration = transition.duration ?? 600;
11826
+ const size2 = { width: from.width, height: from.height };
11827
+ const start = clock.now();
11828
+ for (; ; ) {
11829
+ const time = Math.min(duration, clock.now() - start);
11830
+ const p = await pool.render(provider, transition.effect, size2, { time, progress: time / duration, values: transition.values, inputs: { from, to } });
11831
+ draw(target, p);
11832
+ if (time >= duration) return;
11833
+ await new Promise((resolve) => clock.frame(resolve));
11834
+ }
11835
+ }
11836
+ async function captureCanvasView(element, size2) {
11837
+ const url = await rasterizeElement(element, { pixelWidth: size2.width, pixelHeight: size2.height, exclude: CAPTURE_OVERLAY_SELECTORS });
11838
+ const doc = element.ownerDocument;
11839
+ const image = new doc.defaultView.Image();
11840
+ await new Promise((resolve, reject) => {
11841
+ image.onload = () => resolve();
11842
+ image.onerror = () => reject(new Error("The canvas view could not be captured."));
11843
+ image.src = url;
11844
+ });
11845
+ const canvas = doc.createElement("canvas");
11846
+ canvas.width = size2.width;
11847
+ canvas.height = size2.height;
11848
+ const ctx = canvas.getContext("2d");
11849
+ if (!ctx) throw new Error("The canvas view could not be captured (no 2D canvas).");
11850
+ ctx.drawImage(image, 0, 0, size2.width, size2.height);
11851
+ const data = ctx.getImageData(0, 0, size2.width, size2.height).data;
11852
+ return { width: size2.width, height: size2.height, data };
11853
+ }
11854
+ async function transitionCanvasView(options) {
11855
+ const { element } = options;
11856
+ const clock = options.clock ?? defaultClock;
11857
+ const scale = options.scale ?? Math.min(1, typeof devicePixelRatio === "number" ? devicePixelRatio : 1);
11858
+ const size2 = { width: Math.max(1, Math.round(element.clientWidth * scale)), height: Math.max(1, Math.round(element.clientHeight * scale)) };
11859
+ let from;
11860
+ try {
11861
+ from = await captureCanvasView(element, size2);
11862
+ } catch {
11863
+ options.jump();
11864
+ return;
11865
+ }
11866
+ const overlay = element.ownerDocument.createElement("canvas");
11867
+ overlay.width = size2.width;
11868
+ overlay.height = size2.height;
11869
+ overlay.setAttribute("data-capture", "overlay");
11870
+ overlay.setAttribute("data-canvas-transition", options.transition.effect);
11871
+ Object.assign(overlay.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none", zIndex: "1000" });
11872
+ overlay.getContext("2d")?.putImageData(new ImageData(new Uint8ClampedArray(from.data), size2.width, size2.height), 0, 0);
11873
+ if (getComputedStyle(element).position === "static") element.style.position = "relative";
11874
+ element.appendChild(overlay);
11875
+ try {
11876
+ options.jump();
11877
+ await new Promise((r) => clock.frame(() => clock.frame(r)));
11878
+ const to = await captureCanvasView(element, size2);
11879
+ await playCanvasSceneTransition({ provider: options.provider, transition: options.transition, from, to, target: overlay, clock });
11880
+ } catch {
11881
+ } finally {
11882
+ overlay.remove();
11883
+ }
11884
+ }
11885
+
11886
+ // src/canvas/effect-tools.ts
11887
+ var info = (e) => ({
11888
+ id: "effect",
11889
+ effect: e.effect,
11890
+ ...e.name ? { name: e.name } : {},
11891
+ ...e.provider ? { provider: e.provider } : {},
11892
+ role: e.mode === "fill" ? "fill" : "overlay",
11893
+ enabled: e.playing !== false,
11894
+ opacity: e.opacity ?? 1,
11895
+ values: { ...e.values }
11896
+ });
11897
+ function createCanvasEffectsToolProvider(controller, options = {}) {
11898
+ const readOnlyReason = () => {
11899
+ const ro = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
11900
+ return ro ? typeof ro === "string" ? ro : "The canvas is read-only." : null;
11901
+ };
11902
+ const result = (nodeId, effect) => {
11903
+ const now = controller.list(nodeId)[0]?.effect;
11904
+ return { target: nodeId, effect: info(effect ?? now), effects: now ? [info(now)] : [] };
11905
+ };
11906
+ const once = (instance) => {
11907
+ if (instance !== "effect") throw new Error('A shape node has one effect, "effect".');
11908
+ };
11909
+ return createEffectToolProvider({
11910
+ id: options.id ?? "canvas-effects",
11911
+ namespace: options.namespace,
11912
+ target: { noun: "node", id: "A shape node id on the canvas (a node with data.shape)." },
11913
+ applyUses: ["generator", "overlay", "title", "interactive"],
11914
+ applyDetails: "Generators fill the shape (clipped to its outline); overlays, titles and interactive effects draw over the node's box (set_canvas_effect_mode switches). A node has one effect: this replaces it. enabled false holds the first frame; opacity mixes it. index does not apply.",
11915
+ provider: () => controller.effectsProvider,
11916
+ readOnlyReason,
11917
+ list: (target) => controller.list(target).map((e) => ({ target: e.nodeId, effects: [info(e.effect)] })),
11918
+ apply: async (target, effect, args) => {
11919
+ if (args.index !== void 0) throw new Error("index does not apply: a shape node has one effect.");
11920
+ const provider = controller.effectsProvider;
11921
+ const d = provider ? await provider.describe(effect) : null;
11922
+ const mode = d && !d.uses.includes("generator") ? "overlay" : "fill";
11923
+ const applied = await controller.applyNodeEffect(target, effect, { mode, values: args.values, opacity: args.opacity });
11924
+ if (args.enabled === false) await controller.updateNodeEffect(target, { playing: false });
11925
+ return result(target, args.enabled === false ? { ...applied, playing: false } : applied);
11926
+ },
11927
+ update: async (target, instance, changes) => {
11928
+ once(instance);
11929
+ if (changes.index !== void 0) throw new Error("index does not apply: a shape node has one effect.");
11930
+ const e = await controller.updateNodeEffect(target, { values: changes.values, opacity: changes.opacity, playing: changes.enabled });
11931
+ return result(target, e);
11932
+ },
11933
+ remove: async (target, instance) => {
11934
+ once(instance);
11935
+ const was = controller.removeNodeEffect(target);
11936
+ return { target, effect: info(was), effects: [] };
11937
+ },
11938
+ subscribe: options.subscribe ? (notify) => options.subscribe(notify) : (notify) => controller.effects.subscribe(() => notify()),
11939
+ extraTools: (ctx) => [
11940
+ {
11941
+ name: "set_canvas_effect_mode",
11942
+ title: "Set a node effect's mode",
11943
+ description: "Switches a shape node's effect between fill (paints the inside, clipped to the outline) and overlay (draws over the node's box). One undoable step. Returns the effect.",
11944
+ inputSchema: {
11945
+ type: "object",
11946
+ properties: {
11947
+ node: { type: "string", minLength: 1, description: "A shape node id from list_applied_effects." },
11948
+ mode: { type: "string", enum: ["fill", "overlay"], description: "fill or overlay." }
11949
+ },
11950
+ required: ["node", "mode"],
11951
+ additionalProperties: false
11952
+ },
11953
+ annotations: { title: "Set a node effect's mode", ...ctx.annotations.writeIdempotent },
11954
+ handler: async (args) => {
11955
+ ctx.requireProvider();
11956
+ return { node: args.node, effect: info(await controller.updateNodeEffect(args.node, { mode: args.mode })) };
11957
+ }
11958
+ },
11959
+ {
11960
+ name: "list_scene_transitions",
11961
+ title: "List scene transitions",
11962
+ description: "Lists the canvas Scenes in order with the transition effect played when moving to each (or null). Works without a provider.",
11963
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
11964
+ annotations: { title: "List scene transitions", ...ctx.annotations.read },
11965
+ handler: () => ({ scenes: controller.scenes().map((s) => ({ id: s.id, name: s.name, transition: s.transition ?? null })) })
11966
+ },
11967
+ {
11968
+ name: "set_scene_transition",
11969
+ title: "Set a scene transition",
11970
+ description: "Sets the provider transition effect (search_effects with use transition) played when the canvas moves to a scene: the view before and after are captured and the effect blends between them. effect null removes it. Returns the transition.",
11971
+ inputSchema: {
11972
+ type: "object",
11973
+ properties: {
11974
+ scene: { type: "string", minLength: 1, description: "A scene id from list_scene_transitions." },
11975
+ effect: { anyOf: [ctx.effectIdSchema(["transition"]), { type: "null" }], description: "A transition effect id from search_effects, or null to remove the transition." },
11976
+ values: ctx.valuesSchema("Control values over the effect's defaults"),
11977
+ duration: { type: "number", exclusiveMinimum: 0, description: "Length in ms (default: the effect's own, else 600)." }
11978
+ },
11979
+ required: ["scene", "effect"],
11980
+ additionalProperties: false
11981
+ },
11982
+ annotations: { title: "Set a scene transition", ...ctx.annotations.writeIdempotent },
11983
+ handler: async (args) => {
11984
+ if (args.effect !== null) ctx.requireProvider();
11985
+ return { scene: args.scene, transition: await controller.setSceneTransition(args.scene, args.effect, { values: args.values, duration: args.duration }) };
11986
+ }
11987
+ }
11988
+ ]
11989
+ });
11990
+ }
11604
11991
  export {
11605
11992
  ANIMATABLE_PROPERTIES,
11606
11993
  ANIMATION_DATA_KEY,
@@ -11615,6 +12002,7 @@ export {
11615
12002
  CANVAS_CAPTURE_OVERLAY_SELECTORS,
11616
12003
  CANVAS_CAPTURE_VIEWPORT_SELECTOR,
11617
12004
  CANVAS_COMPONENT_DRAG_TYPE,
12005
+ CANVAS_EFFECT_PLACES,
11618
12006
  CANVAS_GRADIENT_PRESETS,
11619
12007
  CANVAS_TOKEN_PROPERTIES,
11620
12008
  CARD_INSPECTION_ZONES,
@@ -11697,8 +12085,11 @@ export {
11697
12085
  canCombineShapeNodes,
11698
12086
  canOffsetShapeNodes,
11699
12087
  canOutlineShapeNodeStrokes,
12088
+ canvasEffectsPool,
11700
12089
  canvasHeight,
11701
12090
  canvasNodeCaptureRect,
12091
+ canvasNodeEffect,
12092
+ captureCanvasView,
11702
12093
  centerNodes,
11703
12094
  centre,
11704
12095
  clampAnimationValue,
@@ -11728,6 +12119,8 @@ export {
11728
12119
  createCanvasComponent,
11729
12120
  createCanvasComponentsController,
11730
12121
  createCanvasDrawController,
12122
+ createCanvasEffectsController,
12123
+ createCanvasEffectsToolProvider,
11731
12124
  createCanvasInstance,
11732
12125
  createCanvasScenesStore,
11733
12126
  createDefaultStrokeStyle,
@@ -11964,6 +12357,7 @@ export {
11964
12357
  pieScene,
11965
12358
  placeBeside,
11966
12359
  planarSplit,
12360
+ playCanvasSceneTransition,
11967
12361
  playWebAnimations,
11968
12362
  pointInBezierPath,
11969
12363
  pointInMultiPolygon,
@@ -12009,6 +12403,7 @@ export {
12009
12403
  revertShapeCombination,
12010
12404
  rgbToOklab,
12011
12405
  ringSignedArea,
12406
+ runCanvasNodeEffect,
12012
12407
  runMultiNodeTransition,
12013
12408
  sampleAnimationFrames,
12014
12409
  scaleKeyframes,
@@ -12067,12 +12462,14 @@ export {
12067
12462
  toWebAnimations,
12068
12463
  toggleLockNodes,
12069
12464
  transformBezierPath,
12465
+ transitionCanvasView,
12070
12466
  trimSegments,
12071
12467
  unionPolygons,
12072
12468
  unitePaths,
12073
12469
  unlockNodes,
12074
12470
  updateKeyframe,
12075
12471
  validateCanvasBrush,
12472
+ validateCanvasBrushPaint,
12076
12473
  windingNumber,
12077
12474
  withNodeAnimation,
12078
12475
  withStyleOpacity,