@pixerate/editor-svelte 0.17.4 → 0.17.5

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.
@@ -7,7 +7,8 @@ export declare function isEventFromTextInput(event: Event | {
7
7
  target: EventTarget | null;
8
8
  }): boolean;
9
9
  /**
10
- * Validates that an object conforms to minimal CanvasNode shape.
10
+ * Validates that an object conforms to minimal CanvasNode shape:
11
+ * a string `id` and a `position` whose `x` / `y` are finite numbers.
11
12
  */
12
13
  export declare function isValidCanvasNode(obj: unknown): obj is CanvasNode;
13
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"createCanvasClipboard.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasClipboard.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAA0B,MAAM,YAAY,CAAC;AAEjF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG;IAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAc3F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU,CAYjE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO/C;AAED,MAAM,WAAW,4BAA4B,CAC5C,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU;IAErC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,CAAC;CAC5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU,EACpC,OAAO,GAAE,4BAA4B,CAAC,KAAK,EAAE,KAAK,CAAM;uCAEzC,KAAK,EAAE,mBACL,KAAK,EAAE,UAChB,cAAc,KACpB,OAAO;sCA2BM,KAAK,EAAE,mBACL,KAAK,EAAE,aACb,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,UAC3B,cAAc,KACpB,OAAO;8BAY6B,cAAc,KAAG,OAAO,CAAC,OAAO,CAAC;EAqGxE"}
1
+ {"version":3,"file":"createCanvasClipboard.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasClipboard.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAA0B,MAAM,YAAY,CAAC;AAEjF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG;IAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAc3F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU,CAajE;AAWD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO/C;AAED,MAAM,WAAW,4BAA4B,CAC5C,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU;IAErC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,CAAC;CAC5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU,EACpC,OAAO,GAAE,4BAA4B,CAAC,KAAK,EAAE,KAAK,CAAM;uCAEzC,KAAK,EAAE,mBACL,KAAK,EAAE,UAChB,cAAc,KACpB,OAAO;sCA2BM,KAAK,EAAE,mBACL,KAAK,EAAE,aACb,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,UAC3B,cAAc,KACpB,OAAO;8BAY6B,cAAc,KAAG,OAAO,CAAC,OAAO,CAAC;EAkHxE"}
@@ -17,18 +17,28 @@ export function isEventFromTextInput(event) {
17
17
  return false;
18
18
  }
19
19
  /**
20
- * Validates that an object conforms to minimal CanvasNode shape.
20
+ * Validates that an object conforms to minimal CanvasNode shape:
21
+ * a string `id` and a `position` whose `x` / `y` are finite numbers.
21
22
  */
22
23
  export function isValidCanvasNode(obj) {
24
+ if (typeof obj !== 'object' || obj === null)
25
+ return false;
26
+ const candidate = obj;
27
+ if (typeof candidate.id !== 'string')
28
+ return false;
29
+ const position = candidate.position;
30
+ return (typeof position === 'object' &&
31
+ position !== null &&
32
+ typeof position.x === 'number' &&
33
+ Number.isFinite(position.x) &&
34
+ typeof position.y === 'number' &&
35
+ Number.isFinite(position.y));
36
+ }
37
+ function isValidCanvasEdgeShape(obj) {
23
38
  return (typeof obj === 'object' &&
24
39
  obj !== null &&
25
- 'id' in obj &&
26
- typeof obj.id === 'string' &&
27
- 'position' in obj &&
28
- typeof obj.position === 'object' &&
29
- obj.position !== null &&
30
- 'x' in obj.position &&
31
- 'y' in obj.position);
40
+ typeof obj.source === 'string' &&
41
+ typeof obj.target === 'string');
32
42
  }
33
43
  /**
34
44
  * Checks if a string is a valid HTTP/HTTPS URL.
@@ -110,24 +120,37 @@ export function createCanvasClipboard(options = {}) {
110
120
  Array.isArray(parsed.nodes) &&
111
121
  parsed.nodes.every(isValidCanvasNode)) {
112
122
  rawNodes = parsed.nodes;
113
- rawEdges = parsed.edges;
123
+ const edgesField = parsed.edges;
124
+ // Untrusted input: ignore a non-array `edges` and malformed edge entries.
125
+ rawEdges = Array.isArray(edgesField)
126
+ ? edgesField.filter(isValidCanvasEdgeShape)
127
+ : undefined;
114
128
  }
115
129
  if (rawNodes && rawNodes.length > 0) {
116
130
  event.preventDefault();
117
131
  // Create ID mapping for cloned nodes
118
132
  const idMap = new Map();
133
+ for (const node of rawNodes) {
134
+ idMap.set(node.id, crypto.randomUUID());
135
+ }
119
136
  const newNodes = rawNodes.map((node) => {
120
- const newId = crypto.randomUUID();
121
- idMap.set(node.id, newId);
122
- return {
137
+ const next = {
123
138
  ...node,
124
- id: newId,
139
+ id: idMap.get(node.id),
125
140
  selected: true,
126
141
  position: {
127
142
  x: node.position.x + 40,
128
143
  y: node.position.y + 40
129
144
  }
130
145
  };
146
+ // Children of a pasted parent point at the new parent and keep their
147
+ // parent-relative position; other parent references are left unchanged.
148
+ const parentId = node.parentId;
149
+ if (typeof parentId === 'string' && idMap.has(parentId)) {
150
+ next.parentId = idMap.get(parentId);
151
+ next.position = { x: node.position.x, y: node.position.y };
152
+ }
153
+ return next;
131
154
  });
132
155
  // Clone edges mapping them to the newly generated node IDs
133
156
  const newEdges = rawEdges?.map((edge) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"createCanvasExplosion.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasExplosion.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EACX,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,YAAY,CAAC;AAYpB,MAAM,WAAW,iBAAiB,CACjC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU;IAErC,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3F,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACtD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACtD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC;IAC1E,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU;IAClF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU,EAErC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,EACtC,OAAO,GAAE,4BAA4B,CAAC,KAAK,CAAM;;yBAMrB,MAAM,KAAG,OAAO;4BAKjC,MAAM,kBACA,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,KAC9C,OAAO;6BA2OC,MAAM,oBACC,mBAAmB,KAClC,OAAO;8BAmIC,MAAM,kBACA,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,oBAC9B,mBAAmB,KACnC,OAAO;4BAOqB,MAAM;EAcrC"}
1
+ {"version":3,"file":"createCanvasExplosion.svelte.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasExplosion.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EACX,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,YAAY,CAAC;AAYpB,MAAM,WAAW,iBAAiB,CACjC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU;IAErC,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3F,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACtD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACtD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC;IAC1E,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU;IAClF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,KAAK,SAAS,UAAU,GAAG,UAAU,EAErC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,EACtC,OAAO,GAAE,4BAA4B,CAAC,KAAK,CAAM;;yBA2BrB,MAAM,KAAG,OAAO;4BAKjC,MAAM,kBACA,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,KAC9C,OAAO;6BAiOC,MAAM,oBACC,mBAAmB,KAClC,OAAO;8BAyHC,MAAM,kBACA,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,oBAC9B,mBAAmB,KACnC,OAAO;4BAOqB,MAAM;EAcrC"}
@@ -9,6 +9,29 @@ export function createCanvasExplosion(graph, options = {}) {
9
9
  let explodedNodeIds = $state([]);
10
10
  const snapshots = new Map();
11
11
  let activeAnimationCancel = null;
12
+ /**
13
+ * Applies one animation frame (node copies emitted by runMultiNodeTransition) onto the
14
+ * graph's live node objects and publishes the new node array.
15
+ */
16
+ function syncTransitionFrame(byId) {
17
+ for (const graphNode of graph.nodes) {
18
+ const frameNode = byId.get(graphNode.id);
19
+ if (!frameNode || frameNode === graphNode)
20
+ continue;
21
+ graphNode.position = { ...frameNode.position };
22
+ graphNode.style = frameNode.style;
23
+ const frameData = frameNode.data;
24
+ if (frameData && ('scale' in frameData || 'opacity' in frameData)) {
25
+ const data = { ...(graphNode.data ?? {}) };
26
+ if ('scale' in frameData)
27
+ data.scale = frameData.scale;
28
+ if ('opacity' in frameData)
29
+ data.opacity = frameData.opacity;
30
+ graphNode.data = data;
31
+ }
32
+ }
33
+ graph.setNodes([...graph.nodes]);
34
+ }
12
35
  function isExploded(nodeId) {
13
36
  return explodedNodeIds.includes(nodeId);
14
37
  }
@@ -156,13 +179,15 @@ export function createCanvasExplosion(graph, options = {}) {
156
179
  activeAnimationCancel = runMultiNodeTransition(transitions, {
157
180
  defaultDuration: duration,
158
181
  defaultEasing: easing,
159
- onUpdate: () => {
182
+ onUpdate: (updated) => {
183
+ // runMultiNodeTransition never mutates inputs; it hands us per-frame copies.
184
+ const byId = new Map(updated.map((n) => [n.id, n]));
160
185
  if (edgesToAdd.length > 0) {
161
186
  const childIdSet = new Set(childNodes.map((c) => c.id));
162
187
  const childEdgesInGraph = graph.edges.filter((e) => childIdSet.has(e.target));
163
188
  if (childEdgesInGraph.length > 0) {
164
189
  childEdgesInGraph.forEach((edge) => {
165
- const child = childNodes.find((c) => c.id === edge.target);
190
+ const child = byId.get(edge.target);
166
191
  const childOpacity = child?.data?.opacity ?? 1;
167
192
  const currentStyle = (edge.style || '').replace(/opacity:\s*[^;]+;?/g, '').trim();
168
193
  edge.style = `${currentStyle ? currentStyle + '; ' : ''}opacity: ${childOpacity};`;
@@ -170,18 +195,7 @@ export function createCanvasExplosion(graph, options = {}) {
170
195
  graph.setEdges([...graph.edges]);
171
196
  }
172
197
  }
173
- // Sync any mismatched node references in graph.nodes from transitions
174
- transitions.forEach((t) => {
175
- const graphNode = graph.nodes.find((n) => n.id === t.node.id);
176
- if (graphNode && graphNode !== t.node) {
177
- graphNode.position = { ...t.node.position };
178
- graphNode.style = t.node.style;
179
- if (t.node.data) {
180
- graphNode.data = { ...graphNode.data, ...t.node.data };
181
- }
182
- }
183
- });
184
- graph.setNodes([...graph.nodes]);
198
+ syncTransitionFrame(byId);
185
199
  },
186
200
  onComplete: () => {
187
201
  childNodes.forEach((child, i) => {
@@ -274,30 +288,21 @@ export function createCanvasExplosion(graph, options = {}) {
274
288
  activeAnimationCancel = runMultiNodeTransition(transitions, {
275
289
  defaultDuration: duration,
276
290
  defaultEasing: easing,
277
- onUpdate: () => {
291
+ onUpdate: (updated) => {
292
+ // runMultiNodeTransition never mutates inputs; it hands us per-frame copies.
293
+ const byId = new Map(updated.map((n) => [n.id, n]));
278
294
  const edgeIdSet = new Set(snapshot.childEdgeIds);
279
295
  const childEdgesInGraph = graph.edges.filter((e) => edgeIdSet.has(e.id));
280
296
  if (childEdgesInGraph.length > 0) {
281
297
  childEdgesInGraph.forEach((edge) => {
282
- const child = activeChildNodes.find((c) => c.id === edge.target);
298
+ const child = byId.get(edge.target);
283
299
  const childOpacity = child?.data?.opacity ?? 0;
284
300
  const currentStyle = (edge.style || '').replace(/opacity:\s*[^;]+;?/g, '').trim();
285
301
  edge.style = `${currentStyle ? currentStyle + '; ' : ''}opacity: ${childOpacity};`;
286
302
  });
287
303
  graph.setEdges([...graph.edges]);
288
304
  }
289
- // Sync any mismatched node references in graph.nodes from transitions
290
- transitions.forEach((t) => {
291
- const graphNode = graph.nodes.find((n) => n.id === t.node.id);
292
- if (graphNode && graphNode !== t.node) {
293
- graphNode.position = { ...t.node.position };
294
- graphNode.style = t.node.style;
295
- if (t.node.data) {
296
- graphNode.data = { ...graphNode.data, ...t.node.data };
297
- }
298
- }
299
- });
300
- graph.setNodes([...graph.nodes]);
305
+ syncTransitionFrame(byId);
301
306
  },
302
307
  onComplete: () => {
303
308
  // Animation completed: prune child nodes and edges
@@ -19,13 +19,17 @@ export declare function createFanOutTrajectory(options?: FanOutTrajectoryOptions
19
19
  */
20
20
  export declare function createBezierTrajectory(getControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition): TrajectoryFunction;
21
21
  /**
22
- * Coordinates and animates multiple node transitions simultaneously with zero per-frame array allocations.
23
- * Directly mutates node positions and data (scale, opacity) in-place during the animation loop.
22
+ * Coordinates and animates multiple node transitions simultaneously.
23
+ *
24
+ * Input nodes are never mutated: every frame produces updated copies (position,
25
+ * `data.scale` / `data.opacity`, `style` opacity) which are passed to `onUpdate`
26
+ * (and finally `onComplete`) in the same order as `transitions`. Both CSS-string and
27
+ * object styles are supported. Merge the copies into your graph state by `id`.
24
28
  */
25
29
  export declare function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(transitions: NodeTransition<TNode>[], options?: {
26
30
  defaultDuration?: number;
27
31
  defaultEasing?: (t: number) => number;
28
- onUpdate?: () => void;
29
- onComplete?: () => void;
32
+ onUpdate?: (nodes: TNode[]) => void;
33
+ onComplete?: (nodes: TNode[]) => void;
30
34
  }): () => void;
31
35
  //# sourceMappingURL=createCanvasTrajectory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createCanvasTrajectory.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasTrajectory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EACX,UAAU,EACV,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAElB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,kBAa9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,kBAWtC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,kBAAkB,CA0ChG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACrC,eAAe,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,KAAK,UAAU,GACrE,kBAAkB,CAepB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EAC3E,WAAW,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EACpC,OAAO,GAAE;IACR,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACnB,GACJ,MAAM,IAAI,CA+EZ"}
1
+ {"version":3,"file":"createCanvasTrajectory.d.ts","sourceRoot":"","sources":["../../src/canvas/createCanvasTrajectory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EACX,UAAU,EACV,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAElB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,kBAa9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,kBAWtC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,kBAAkB,CA0ChG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACrC,eAAe,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,KAAK,UAAU,GACrE,kBAAkB,CAepB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EAC3E,WAAW,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EACpC,OAAO,GAAE;IACR,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;CACjC,GACJ,MAAM,IAAI,CAiFZ"}
@@ -1,4 +1,5 @@
1
1
  import { cubicOut } from 'svelte/easing';
2
+ import { withStyleOpacity } from '@pixerate/editor/canvas';
2
3
  /**
3
4
  * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.
4
5
  */
@@ -70,12 +71,16 @@ export function createBezierTrajectory(getControlPoint) {
70
71
  };
71
72
  }
72
73
  /**
73
- * Coordinates and animates multiple node transitions simultaneously with zero per-frame array allocations.
74
- * Directly mutates node positions and data (scale, opacity) in-place during the animation loop.
74
+ * Coordinates and animates multiple node transitions simultaneously.
75
+ *
76
+ * Input nodes are never mutated: every frame produces updated copies (position,
77
+ * `data.scale` / `data.opacity`, `style` opacity) which are passed to `onUpdate`
78
+ * (and finally `onComplete`) in the same order as `transitions`. Both CSS-string and
79
+ * object styles are supported. Merge the copies into your graph state by `id`.
75
80
  */
76
81
  export function runMultiNodeTransition(transitions, options = {}) {
77
82
  if (transitions.length === 0) {
78
- options.onComplete?.();
83
+ options.onComplete?.([]);
79
84
  return () => { };
80
85
  }
81
86
  const { defaultDuration = 400, defaultEasing = cubicOut, onUpdate, onComplete } = options;
@@ -90,6 +95,8 @@ export function runMultiNodeTransition(transitions, options = {}) {
90
95
  delay: tr.delay ?? 0,
91
96
  trajectory: tr.trajectory ?? linearTrajectory
92
97
  }));
98
+ // Latest copy of every node; starts out as the untouched inputs.
99
+ const current = items.map((item) => item.node);
93
100
  function frame() {
94
101
  if (isCancelled)
95
102
  return;
@@ -105,33 +112,31 @@ export function runMultiNodeTransition(transitions, options = {}) {
105
112
  const rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);
106
113
  const progress = item.duration <= 0 ? 1 : item.easing(rawProgress);
107
114
  const point = item.trajectory(item.from, item.to, progress, item.index, item.total);
108
- // Direct in-place mutation avoids array reconstruction during RAF
109
- item.node.position = { ...point.position };
115
+ const next = { ...item.node, position: { ...point.position } };
110
116
  if (point.scale !== undefined || point.opacity !== undefined) {
111
- if (!item.node.data) {
112
- item.node.data = {};
113
- }
117
+ const data = { ...(item.node.data ?? {}) };
114
118
  if (point.scale !== undefined) {
115
- item.node.data.scale = point.scale;
119
+ data.scale = point.scale;
116
120
  }
117
121
  if (point.opacity !== undefined) {
118
- item.node.data.opacity = point.opacity;
119
- const currentStyle = (item.node.style || '').replace(/opacity:\s*[^;]+;?/g, '').trim();
120
- item.node.style = `${currentStyle ? currentStyle + '; ' : ''}opacity: ${point.opacity};`;
122
+ data.opacity = point.opacity;
123
+ next.style = withStyleOpacity(item.node.style, point.opacity);
121
124
  }
125
+ next.data = data;
122
126
  }
127
+ current[i] = next;
123
128
  if (rawProgress < 1) {
124
129
  allCompleted = false;
125
130
  }
126
131
  }
127
- onUpdate?.();
132
+ onUpdate?.(current.slice());
128
133
  if (!allCompleted) {
129
134
  if (typeof requestAnimationFrame !== 'undefined') {
130
135
  requestAnimationFrame(frame);
131
136
  }
132
137
  }
133
138
  else {
134
- onComplete?.();
139
+ onComplete?.(current.slice());
135
140
  }
136
141
  }
137
142
  frame();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.17.4",
3
+ "version": "0.17.5",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  "@tiptap/extension-bubble-menu": "^2.11.5",
53
53
  "@tiptap/extension-placeholder": "^2.11.5",
54
54
  "@tiptap/starter-kit": "^2.11.5",
55
- "@pixerate/editor": "0.17.0"
55
+ "@pixerate/editor": "0.18.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@tiptap/core": "^2.11.5",