@pixerate/editor 0.60.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/animation-editor/index.cjs +113 -36
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +56 -3
- package/dist/animation-editor/index.d.ts +56 -3
- package/dist/animation-editor/index.js +79 -2
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -30,6 +30,7 @@ reproduced at the end of this file. Paths are relative to each source repository
|
|
|
30
30
|
© 2026 ArtCraft Team and the DeckCraft contributors.
|
|
31
31
|
|
|
32
32
|
- `crates/geom/src/morph.rs` (`contours`: every segment as a cubic, lines with handles at thirds; `align`: contours paired in order, one without a partner meeting a point at the same place relative to the other outline's box (`place`); `turn`: a closed contour restarted at another anchor; `match_pair`: the closed side of a pair turned, open paired with closed) → `subpathToMorphContour`, `turnMorphContour` and `correspondMorphContours` in `packages/core/src/vector/blend.ts`; anchors are inserted by arc length rather than split evenly per curve, and the winding of closed shapes follows their areas (effectcraft) instead of a cost comparison; test cases re-expressed → `packages/core/tests/vector-blend.test.ts`
|
|
33
|
+
- `crates/anim/src/morph.rs` (`morph_pairs`: shapes paired in passes, each used once — by name, then by matching content, then by kind — and unpaired shapes faded out) → `matchMotionNodes` and the `match` command in `packages/core/src/animation-editor/match.ts` and `compile.ts`; the passes are name, then id with the groups' own ids taken out, then kind in stacking order, and paired groups pair their own parts
|
|
33
34
|
- `crates/pptx/src/read/chart.rs` (DrawingML chart reading: group → type mapping, secondary value axis by axis id and position rather than file order, cached and literal data, data-label number-format precedence, point fills) → `chartFromOoxml` in `packages/core/src/chart/ooxml.ts`
|
|
34
35
|
|
|
35
36
|
## designcraft
|
|
@@ -55,6 +56,7 @@ reproduced at the end of this file. Paths are relative to each source repository
|
|
|
55
56
|
|
|
56
57
|
© 2026 ArtCraft Team and the FigCraft contributors.
|
|
57
58
|
|
|
59
|
+
- `crates/ui-egui/src/present.rs` (`match_layers`: Smart Animate pairs layers by their name path, nested groups pairing their own layers, and fades out what has no partner) → `matchMotionNodes` in `packages/core/src/animation-editor/match.ts`
|
|
58
60
|
- `crates/engine/src/distance.rs` (`between`, `inside`, `to_guide`, `label`) → `packages/core/src/canvas/measure.ts`
|
|
59
61
|
- `crates/engine/src/measure_tests.rs` (test cases re-expressed) → `packages/core/tests/canvas-measure.test.ts`
|
|
60
62
|
- `crates/engine/src/snap.rs` (`spacings` / `equal_spacing` / `spacing_guides`: centred between neighbours, repeating the gap of the pair before or after, gap labels; threshold in screen pixels divided by the zoom) → `VectorSnapTargets.snapRect` in `packages/core/src/vector/snap.ts`; its equal-spacing cases re-expressed → `packages/core/tests/vector-snap.test.ts`
|
|
@@ -63,6 +63,7 @@ var MOTION_COMMAND_TYPES = [
|
|
|
63
63
|
"fade",
|
|
64
64
|
"style",
|
|
65
65
|
"morph",
|
|
66
|
+
"match",
|
|
66
67
|
"follow",
|
|
67
68
|
"pulse",
|
|
68
69
|
"shake",
|
|
@@ -89,6 +90,7 @@ var MOTION_DEFAULT_DURATIONS = {
|
|
|
89
90
|
fade: 400,
|
|
90
91
|
style: 500,
|
|
91
92
|
morph: 900,
|
|
93
|
+
match: 900,
|
|
92
94
|
follow: 1400,
|
|
93
95
|
pulse: 600,
|
|
94
96
|
shake: 500,
|
|
@@ -133,6 +135,8 @@ var MOTION_ISSUE_CODES = {
|
|
|
133
135
|
"not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
|
|
134
136
|
"reveal-no-box": { severity: "warning", wording: "A wipe, iris or split cannot clip a node with no width or height (a lone straight line), so it fades instead." },
|
|
135
137
|
"unknown-shape": { severity: "error", wording: "A morph names a shape the artwork does not have." },
|
|
138
|
+
"match-self": { severity: "error", wording: "A match turns a group into itself or into a group it holds (or that holds it)." },
|
|
139
|
+
"match-unpaired": { severity: "note", wording: `Parts of a match's "into" group have no partner in the target, so they do not appear.` },
|
|
136
140
|
"unknown-effect": { severity: "error", wording: "An effect command names an effect the node does not have." },
|
|
137
141
|
"unknown-effect-param": { severity: "error", wording: "An effect command names a parameter that is not a number of that effect." },
|
|
138
142
|
"unknown-frame": { severity: "error", wording: "A camera frames a node the artwork does not have." },
|
|
@@ -216,6 +220,7 @@ var COMMAND_KEYS = {
|
|
|
216
220
|
fade: [...BASE_KEYS, "to"],
|
|
217
221
|
style: [...BASE_KEYS, "fill", "stroke", "strokeWidth"],
|
|
218
222
|
morph: [...BASE_KEYS, "into"],
|
|
223
|
+
match: [...BASE_KEYS, "into"],
|
|
219
224
|
follow: [...BASE_KEYS, "path", "rotate", "from", "to"],
|
|
220
225
|
pulse: [...BASE_KEYS, "amount", "count", "blend"],
|
|
221
226
|
shake: [...BASE_KEYS, "amount", "count", "axis", "blend"],
|
|
@@ -311,7 +316,8 @@ function commandShapeProblems(command2, where) {
|
|
|
311
316
|
if (command2.strokeWidth !== void 0 && !(isNum(command2.strokeWidth) && command2.strokeWidth >= 0)) p(`"strokeWidth" must be a number \u2265 0.`);
|
|
312
317
|
break;
|
|
313
318
|
case "morph":
|
|
314
|
-
|
|
319
|
+
case "match":
|
|
320
|
+
if (typeof command2.into !== "string" || !command2.into) p(`"into" must be the id of a ${type === "match" ? "group to become" : "node whose shape to take"}.`);
|
|
315
321
|
break;
|
|
316
322
|
case "follow":
|
|
317
323
|
if (typeof command2.path !== "string" || !command2.path) p(`"path" must be the id of a path node.`);
|
|
@@ -420,6 +426,53 @@ function motionSceneToJson(scene, space = 2) {
|
|
|
420
426
|
var isMotionShot = (shot) => shotShapeProblems(shot, "Shot").length === 0;
|
|
421
427
|
var isMotionCommand = (command2) => commandShapeProblems(command2, "Command").length === 0;
|
|
422
428
|
|
|
429
|
+
// src/animation-editor/match.ts
|
|
430
|
+
var SEPARATOR = /[-_/.:]/;
|
|
431
|
+
function localId(id, groupId) {
|
|
432
|
+
if (groupId && id.includes(groupId)) return id.split(groupId).join("").replace(/^[-_/.:]+|[-_/.:]+$/g, "");
|
|
433
|
+
const parts = id.split(SEPARATOR);
|
|
434
|
+
return _nullishCoalesce(parts[parts.length - 1], () => ( id));
|
|
435
|
+
}
|
|
436
|
+
function matchMotionNodes(from, into) {
|
|
437
|
+
const out = { pairs: [], unpairedFrom: [], unpairedInto: [] };
|
|
438
|
+
const visit = (a, b) => {
|
|
439
|
+
const aKids = a.type === "group" ? a.children : [];
|
|
440
|
+
const bKids = b.type === "group" ? b.children : [];
|
|
441
|
+
if (!aKids.length || !bKids.length) {
|
|
442
|
+
out.pairs.push([a.id, b.id]);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const left = new Set(aKids);
|
|
446
|
+
const right = new Set(bKids);
|
|
447
|
+
const pair = (key) => {
|
|
448
|
+
for (const x of [...left]) {
|
|
449
|
+
const k = key(x, a);
|
|
450
|
+
if (!k) continue;
|
|
451
|
+
const y = [...right].find((n) => key(n, b) === k);
|
|
452
|
+
if (!y) continue;
|
|
453
|
+
left.delete(x);
|
|
454
|
+
right.delete(y);
|
|
455
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
456
|
+
else out.pairs.push([x.id, y.id]);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
pair((n) => _optionalChain([n, 'access', _4 => _4.name, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]) || void 0);
|
|
460
|
+
pair((n, g) => localId(n.id, g.id) || void 0);
|
|
461
|
+
for (const x of [...left]) {
|
|
462
|
+
const y = [...right].find((n) => n.type === x.type);
|
|
463
|
+
if (!y) continue;
|
|
464
|
+
left.delete(x);
|
|
465
|
+
right.delete(y);
|
|
466
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
467
|
+
else out.pairs.push([x.id, y.id]);
|
|
468
|
+
}
|
|
469
|
+
out.unpairedFrom.push(...[...left].map((n) => n.id));
|
|
470
|
+
out.unpairedInto.push(...[...right].map((n) => n.id));
|
|
471
|
+
};
|
|
472
|
+
visit(from, into);
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
475
|
+
|
|
423
476
|
// src/animation-editor/compile.ts
|
|
424
477
|
var indexCache = /* @__PURE__ */ new WeakMap();
|
|
425
478
|
function indexMotionNodes(nodes) {
|
|
@@ -499,10 +552,10 @@ function motionRevealClip(entry, reveal) {
|
|
|
499
552
|
}
|
|
500
553
|
}
|
|
501
554
|
function motionRestValue(entry, property, artwork) {
|
|
502
|
-
const node = _optionalChain([entry, 'optionalAccess',
|
|
555
|
+
const node = _optionalChain([entry, 'optionalAccess', _7 => _7.node]);
|
|
503
556
|
switch (property) {
|
|
504
557
|
case "opacity":
|
|
505
|
-
return _nullishCoalesce(_optionalChain([node, 'optionalAccess',
|
|
558
|
+
return _nullishCoalesce(_optionalChain([node, 'optionalAccess', _8 => _8.opacity]), () => ( 1));
|
|
506
559
|
case "x":
|
|
507
560
|
case "y":
|
|
508
561
|
case "rotation":
|
|
@@ -520,11 +573,11 @@ function motionRestValue(entry, property, artwork) {
|
|
|
520
573
|
case "stroke":
|
|
521
574
|
return _nullishCoalesce((node && firstPaint(node, property)), () => ( "transparent"));
|
|
522
575
|
case "strokeWidth": {
|
|
523
|
-
const path = _optionalChain([node, 'optionalAccess',
|
|
524
|
-
return _nullishCoalesce(_optionalChain([path, 'optionalAccess',
|
|
576
|
+
const path = _optionalChain([node, 'optionalAccess', _9 => _9.type]) === "path" ? node : node ? _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, node)[0] : void 0;
|
|
577
|
+
return _nullishCoalesce(_optionalChain([path, 'optionalAccess', _10 => _10.strokeWidth]), () => ( 1));
|
|
525
578
|
}
|
|
526
579
|
case "shape":
|
|
527
|
-
return _nullishCoalesce(_optionalChain([node, 'optionalAccess',
|
|
580
|
+
return _nullishCoalesce(_optionalChain([node, 'optionalAccess', _11 => _11.id]), () => ( ""));
|
|
528
581
|
case "clipRect":
|
|
529
582
|
case "clipCircle": {
|
|
530
583
|
const pad = entry && motionRevealPadding(entry);
|
|
@@ -534,7 +587,7 @@ function motionRestValue(entry, property, artwork) {
|
|
|
534
587
|
return [0, 0, artwork.width, artwork.height];
|
|
535
588
|
}
|
|
536
589
|
const [, effectId, param] = property.split(":");
|
|
537
|
-
const effect = _optionalChain([node, 'optionalAccess',
|
|
590
|
+
const effect = _optionalChain([node, 'optionalAccess', _12 => _12.effects, 'optionalAccess', _13 => _13.find, 'call', _14 => _14((e) => e.id === effectId)]);
|
|
538
591
|
const v = effect ? effect.params[param] : void 0;
|
|
539
592
|
return typeof v === "number" ? v : 0;
|
|
540
593
|
}
|
|
@@ -583,13 +636,13 @@ function segmentValue(segment, time, raw = false) {
|
|
|
583
636
|
return typeof segment.from === "string" ? interpolateMotionValue(segment.from, segment.to, Math.min(1, Math.max(0, p))) : interpolateMotionValue(segment.from, segment.to, p);
|
|
584
637
|
}
|
|
585
638
|
function activeSegment(track, time) {
|
|
586
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
639
|
+
if (!_optionalChain([track, 'optionalAccess', _15 => _15.length])) return void 0;
|
|
587
640
|
let found;
|
|
588
641
|
for (const s of track) if (s.start <= time && (!found || s.start >= found.start)) found = s;
|
|
589
642
|
return found;
|
|
590
643
|
}
|
|
591
644
|
function trackValue(track, time, raw = false) {
|
|
592
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
645
|
+
if (!_optionalChain([track, 'optionalAccess', _16 => _16.length])) return void 0;
|
|
593
646
|
const s = activeSegment(track, time);
|
|
594
647
|
if (s) return segmentValue(s, time, raw);
|
|
595
648
|
let first = track[0];
|
|
@@ -622,7 +675,7 @@ function fitMotionRect(rect, aspect) {
|
|
|
622
675
|
function resolveMotionCamera(camera, scene) {
|
|
623
676
|
if ("frame" in camera && typeof camera.frame === "string") {
|
|
624
677
|
const entry = indexMotionNodes(scene.artwork.nodes).get(camera.frame);
|
|
625
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
678
|
+
if (!_optionalChain([entry, 'optionalAccess', _17 => _17.bounds])) return null;
|
|
626
679
|
const pad = _nullishCoalesce(camera.padding, () => ( 24));
|
|
627
680
|
const b = entry.bounds;
|
|
628
681
|
const rect = { x: b.x - pad, y: b.y - pad, width: Math.max(1, b.width + pad * 2), height: Math.max(1, b.height + pad * 2) };
|
|
@@ -783,7 +836,7 @@ function compileMotionScene(scene) {
|
|
|
783
836
|
const style = _nullishCoalesce(command2.style, () => ( "fade"));
|
|
784
837
|
const opacity = rest("opacity");
|
|
785
838
|
const reveal = style === "wipe" ? motionRevealClip(entry, { style, direction: command2.direction }) : style === "iris" ? motionRevealClip(entry, { style, shape: command2.shape }) : style === "split" ? motionRevealClip(entry, { style, orientation: command2.orientation }) : null;
|
|
786
|
-
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess',
|
|
839
|
+
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _18 => _18.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
|
|
787
840
|
if (reveal) {
|
|
788
841
|
seg("opacity", opacity, "linear", start, start);
|
|
789
842
|
seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
|
|
@@ -845,8 +898,8 @@ function compileMotionScene(scene) {
|
|
|
845
898
|
}
|
|
846
899
|
case "move": {
|
|
847
900
|
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
848
|
-
let dx = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
849
|
-
let dy = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
901
|
+
let dx = _nullishCoalesce(_optionalChain([command2, 'access', _19 => _19.by, 'optionalAccess', _20 => _20.x]), () => ( 0));
|
|
902
|
+
let dy = _nullishCoalesce(_optionalChain([command2, 'access', _21 => _21.by, 'optionalAccess', _22 => _22.y]), () => ( 0));
|
|
850
903
|
if (command2.to) {
|
|
851
904
|
const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
|
|
852
905
|
const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
|
|
@@ -886,6 +939,15 @@ function compileMotionScene(scene) {
|
|
|
886
939
|
case "morph":
|
|
887
940
|
seg("shape", command2.into, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)));
|
|
888
941
|
break;
|
|
942
|
+
case "match": {
|
|
943
|
+
const into = nodes.get(command2.into);
|
|
944
|
+
if (!into || command2.into === node) break;
|
|
945
|
+
const match = matchMotionNodes(entry.node, into.node);
|
|
946
|
+
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
947
|
+
for (const [a, b] of match.pairs) push({ node: a, property: "shape", start, end, from: valueAt(a, "shape", start), to: b, ease: e, ...where });
|
|
948
|
+
for (const a of match.unpairedFrom) push({ node: a, property: "opacity", start, end, from: valueAt(a, "opacity", start), to: 0, ease: e, ...where });
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
889
951
|
case "follow":
|
|
890
952
|
seg("follow", _nullishCoalesce(command2.to, () => ( 1)), _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), start, end, { from: _nullishCoalesce(command2.from, () => ( 0)), path: command2.path, rotate: _nullishCoalesce(command2.rotate, () => ( false)) });
|
|
891
953
|
break;
|
|
@@ -941,7 +1003,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
|
|
|
941
1003
|
var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
|
|
942
1004
|
var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
|
|
943
1005
|
function applyMotionControlValues(scene, values) {
|
|
944
|
-
if (!values || !_optionalChain([scene, 'access',
|
|
1006
|
+
if (!values || !_optionalChain([scene, 'access', _23 => _23.controls, 'optionalAccess', _24 => _24.length])) return scene;
|
|
945
1007
|
const map = /* @__PURE__ */ new Map();
|
|
946
1008
|
for (const control of scene.controls) {
|
|
947
1009
|
const value = values[control.name];
|
|
@@ -987,7 +1049,7 @@ function shapeIn(entry, shape) {
|
|
|
987
1049
|
}
|
|
988
1050
|
function shapeAt(entry, timeline, nodes, time) {
|
|
989
1051
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
|
|
990
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
1052
|
+
if (!_optionalChain([track, 'optionalAccess', _25 => _25.length])) return null;
|
|
991
1053
|
const s = activeSegment(track, time);
|
|
992
1054
|
const fromId = String(s ? s.from : track[0].from);
|
|
993
1055
|
if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
|
|
@@ -1010,8 +1072,8 @@ function motionFollowRoute(entry, path) {
|
|
|
1010
1072
|
}
|
|
1011
1073
|
function followAt(entry, timeline, nodes, time) {
|
|
1012
1074
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
|
|
1013
|
-
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess',
|
|
1014
|
-
if (!_optionalChain([s, 'optionalAccess',
|
|
1075
|
+
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _26 => _26[0]])));
|
|
1076
|
+
if (!_optionalChain([s, 'optionalAccess', _27 => _27.path])) return null;
|
|
1015
1077
|
const route = motionFollowRoute(entry, nodes.get(s.path));
|
|
1016
1078
|
const progress = Number(trackValue(track, time));
|
|
1017
1079
|
if (!route || !(progress >= 0)) return null;
|
|
@@ -1071,7 +1133,7 @@ function motionFrameAt(scene, time, options = {}) {
|
|
|
1071
1133
|
if (out.visible === void 0) delete out.visible;
|
|
1072
1134
|
}
|
|
1073
1135
|
const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
|
|
1074
|
-
if (effectTracks.length && _optionalChain([out, 'access',
|
|
1136
|
+
if (effectTracks.length && _optionalChain([out, 'access', _28 => _28.effects, 'optionalAccess', _29 => _29.length])) {
|
|
1075
1137
|
out = {
|
|
1076
1138
|
...out,
|
|
1077
1139
|
effects: out.effects.map((effect) => {
|
|
@@ -1149,7 +1211,7 @@ function withViewBox(svg, box) {
|
|
|
1149
1211
|
// src/animation-editor/check.ts
|
|
1150
1212
|
var MOTION_MIN_CHANGE_MS = 120;
|
|
1151
1213
|
var NUMERIC_PARAMS = (node, effectId) => {
|
|
1152
|
-
const effect = _optionalChain([node, 'access',
|
|
1214
|
+
const effect = _optionalChain([node, 'access', _30 => _30.effects, 'optionalAccess', _31 => _31.find, 'call', _32 => _32((e) => e.id === effectId)]);
|
|
1153
1215
|
if (!effect) return null;
|
|
1154
1216
|
return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
|
|
1155
1217
|
};
|
|
@@ -1226,6 +1288,19 @@ function checkMotionScene(scene) {
|
|
|
1226
1288
|
if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
|
|
1227
1289
|
issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
|
|
1228
1290
|
if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
|
|
1291
|
+
if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
|
|
1292
|
+
const into = nodes.get(command2.into);
|
|
1293
|
+
for (const id of targets) {
|
|
1294
|
+
const entry = nodes.get(id);
|
|
1295
|
+
if (id === command2.into || into.ancestors.includes(id) || entry.ancestors.includes(command2.into)) {
|
|
1296
|
+
issues.push(motionIssue("match-self", `"${id}" cannot match "${command2.into}": one is the other or holds it. Match two separate groups (the second usually hidden).`, { ...where, ids: [id, command2.into] }));
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
const m = matchMotionNodes(entry.node, into.node);
|
|
1300
|
+
if (m.unpairedInto.length)
|
|
1301
|
+
issues.push(motionIssue("match-unpaired", `${m.unpairedInto.map((x) => `"${x}"`).join(", ")} in "${command2.into}" ${m.unpairedInto.length > 1 ? "have" : "has"} no partner in "${id}", so ${m.unpairedInto.length > 1 ? "they do" : "it does"} not appear; add a part with the same name or id to "${id}" (it can start hidden or tiny).`, { ...where, ids: m.unpairedInto }));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1229
1304
|
const style = command2.type === "enter" || command2.type === "exit" ? command2.style : void 0;
|
|
1230
1305
|
if (style === "wipe" || style === "iris" || style === "split") {
|
|
1231
1306
|
for (const id of targets)
|
|
@@ -1311,7 +1386,7 @@ function checkMotionScene(scene) {
|
|
|
1311
1386
|
const cam = frame.camera;
|
|
1312
1387
|
const docIndex = indexMotionNodes(frame.document.nodes);
|
|
1313
1388
|
for (const id of command2.targets) {
|
|
1314
|
-
const b = _optionalChain([docIndex, 'access',
|
|
1389
|
+
const b = _optionalChain([docIndex, 'access', _33 => _33.get, 'call', _34 => _34(id), 'optionalAccess', _35 => _35.bounds]);
|
|
1315
1390
|
if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
|
|
1316
1391
|
const outside = b.x + b.width < cam.x || b.y + b.height < cam.y || b.x > cam.x + cam.width || b.y > cam.y + cam.height;
|
|
1317
1392
|
if (outside) {
|
|
@@ -1493,9 +1568,9 @@ function applyMotionEdits(scene, edits) {
|
|
|
1493
1568
|
let next = scene;
|
|
1494
1569
|
for (let i = 0; i < list.length; i++) {
|
|
1495
1570
|
const edit = list[i];
|
|
1496
|
-
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1571
|
+
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _36 => _36.op])}): ` : "";
|
|
1497
1572
|
if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
|
|
1498
|
-
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess',
|
|
1573
|
+
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _37 => _37.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
|
|
1499
1574
|
try {
|
|
1500
1575
|
next = applyOne(next, edit);
|
|
1501
1576
|
} catch (error) {
|
|
@@ -1627,7 +1702,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1627
1702
|
const wrapper = freshId(`${id}--follow`);
|
|
1628
1703
|
const centre = freshId(`${id}--follow-centre`);
|
|
1629
1704
|
const c = entry.center;
|
|
1630
|
-
const at = _optionalChain([motionNodeOffset, 'call',
|
|
1705
|
+
const at = _optionalChain([motionNodeOffset, 'call', _38 => _38(entry, timeline, 0, nodes), 'optionalAccess', _39 => _39.follow]);
|
|
1631
1706
|
const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
|
|
1632
1707
|
(wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
|
|
1633
1708
|
follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
|
|
@@ -1687,7 +1762,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1687
1762
|
exact: () => false,
|
|
1688
1763
|
value: (t) => {
|
|
1689
1764
|
const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
|
|
1690
|
-
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call',
|
|
1765
|
+
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _40 => _40(frame.document.nodes), 'access', _41 => _41.get, 'call', _42 => _42(id), 'optionalAccess', _43 => _43.node]));
|
|
1691
1766
|
if (!now || now.type !== "path") return "";
|
|
1692
1767
|
const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
|
|
1693
1768
|
return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
|
|
@@ -1708,8 +1783,8 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1708
1783
|
...delay > 0 ? { delay: round2(delay) } : {},
|
|
1709
1784
|
ms: round2(ms),
|
|
1710
1785
|
ease: written,
|
|
1711
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1712
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1786
|
+
..._optionalChain([segment, 'optionalAccess', _44 => _44.behavior]) ? { behavior: segment.behavior } : {},
|
|
1787
|
+
..._optionalChain([segment, 'optionalAccess', _45 => _45.steps]) ? { strobe: segment.steps } : {}
|
|
1713
1788
|
};
|
|
1714
1789
|
if (channel.kind === "view") animate.push({ ...base, view: to });
|
|
1715
1790
|
else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
|
|
@@ -1782,7 +1857,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1782
1857
|
if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
|
|
1783
1858
|
const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
|
|
1784
1859
|
let motion = { svg, animate };
|
|
1785
|
-
if (_optionalChain([scene, 'access',
|
|
1860
|
+
if (_optionalChain([scene, 'access', _46 => _46.controls, 'optionalAccess', _47 => _47.length])) {
|
|
1786
1861
|
const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
|
|
1787
1862
|
const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
|
|
1788
1863
|
svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
|
|
@@ -1804,7 +1879,7 @@ function toFlairVectorEffect(scene, options = {}) {
|
|
|
1804
1879
|
tags: _nullishCoalesce(options.tags, () => ( [])),
|
|
1805
1880
|
motion: out.motion,
|
|
1806
1881
|
hold: out.hold,
|
|
1807
|
-
..._optionalChain([scene, 'access',
|
|
1882
|
+
..._optionalChain([scene, 'access', _48 => _48.presets, 'optionalAccess', _49 => _49.length]) ? { presets: scene.presets } : {},
|
|
1808
1883
|
...options.trigger ? { trigger: options.trigger } : {}
|
|
1809
1884
|
};
|
|
1810
1885
|
}
|
|
@@ -1847,7 +1922,7 @@ function resolvePlaceholders(motion, notes) {
|
|
|
1847
1922
|
const filled = text.replace(PLACEHOLDER, (_, name) => {
|
|
1848
1923
|
const d = declared.get(name);
|
|
1849
1924
|
if (!d) unknown.add(name);
|
|
1850
|
-
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess',
|
|
1925
|
+
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _50 => _50.value]), () => ( ""))).replace(/"/g, '\\"');
|
|
1851
1926
|
});
|
|
1852
1927
|
for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
|
|
1853
1928
|
return { motion: JSON.parse(filled), controls };
|
|
@@ -1901,7 +1976,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
1901
1976
|
if (anim.along) {
|
|
1902
1977
|
const path = nodes.get(anim.along);
|
|
1903
1978
|
if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
|
|
1904
|
-
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access',
|
|
1979
|
+
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _51 => _51.at, 'optionalAccess', _52 => _52[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _53 => _53.at, 'optionalAccess', _54 => _54[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
1905
1980
|
}
|
|
1906
1981
|
for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
|
|
1907
1982
|
if (attr === "opacity") {
|
|
@@ -1912,7 +1987,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
1912
1987
|
out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
|
|
1913
1988
|
} else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
1914
1989
|
} else if (attr === "fill" || attr === "stroke") {
|
|
1915
|
-
if (_optionalChain([behavior, 'optionalAccess',
|
|
1990
|
+
if (_optionalChain([behavior, 'optionalAccess', _55 => _55.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
|
|
1916
1991
|
else {
|
|
1917
1992
|
if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
|
|
1918
1993
|
out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
@@ -2079,7 +2154,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2079
2154
|
time: p.time,
|
|
2080
2155
|
playing: p.playing,
|
|
2081
2156
|
loop: p.loop,
|
|
2082
|
-
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call',
|
|
2157
|
+
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _56 => _56(timeline, p.time), 'optionalAccess', _57 => _57.id]), () => ( null)),
|
|
2083
2158
|
selection: this.selection,
|
|
2084
2159
|
readOnly: this.readOnly,
|
|
2085
2160
|
error: this.error,
|
|
@@ -2118,7 +2193,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2118
2193
|
this.applying = false;
|
|
2119
2194
|
}
|
|
2120
2195
|
this.notify();
|
|
2121
|
-
_optionalChain([this, 'access',
|
|
2196
|
+
_optionalChain([this, 'access', _58 => _58.onChange, 'optionalCall', _59 => _59(scene)]);
|
|
2122
2197
|
}
|
|
2123
2198
|
commit(label, next) {
|
|
2124
2199
|
const before = this.scene;
|
|
@@ -2331,6 +2406,7 @@ var MOTION_COMMAND_SCHEMA = {
|
|
|
2331
2406
|
stroke: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
|
|
2332
2407
|
strokeWidth: { type: "number", minimum: 0, description: "Document units." }
|
|
2333
2408
|
}),
|
|
2409
|
+
command("match", `Smart Animate: tweens a group into another group (usually hidden), part by part. Parts pair by name, then by id with the group ids taken out (card-a-title \u2194 card-b-title), then by kind in order; each takes its partner's shape, paint and place. Unpaired parts fade out; parts of "into" with no partner do not appear (check_animation lists them).`, { into: { type: "string", minLength: 1, description: "Id of the group to become." } }, ["into"]),
|
|
2334
2410
|
command("morph", "Morphs each target into the shape (and paint) of another node, which usually stays hidden.", { into: { type: "string", minLength: 1, description: "Id of the node whose shape to take." } }, ["into"]),
|
|
2335
2411
|
command("follow", "Moves targets along a path node, centre on the path.", {
|
|
2336
2412
|
path: { type: "string", minLength: 1, description: "Id of a path node (it can be hidden)." },
|
|
@@ -2704,7 +2780,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2704
2780
|
inputSchema: object({ shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the run (default: the end)." } }, ["shot", "commands"]),
|
|
2705
2781
|
annotations: { title: "Add commands to a shot", ...WRITE },
|
|
2706
2782
|
handler: ({ shot, commands, at }) => {
|
|
2707
|
-
const before = _nullishCoalesce(_optionalChain([scene, 'call',
|
|
2783
|
+
const before = _nullishCoalesce(_optionalChain([scene, 'call', _60 => _60(), 'access', _61 => _61.shots, 'access', _62 => _62.find, 'call', _63 => _63((s) => s.id === shot), 'optionalAccess', _64 => _64.run, 'access', _65 => _65.length]), () => ( 0));
|
|
2708
2784
|
const first = _nullishCoalesce(at, () => ( before));
|
|
2709
2785
|
return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
|
|
2710
2786
|
const shots = describeMotionShots(s);
|
|
@@ -2852,7 +2928,7 @@ Commands
|
|
|
2852
2928
|
- enter also reveals through a clip over the target's box: wipe (behind an edge travelling "direction", default right), iris (from the centre; "shape": circle or box) and split (from a line through the middle; "orientation": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.
|
|
2853
2929
|
- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.
|
|
2854
2930
|
- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
|
|
2855
|
-
- morph turns a shape into another node's shape (keep the target hidden); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
|
|
2931
|
+
- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
|
|
2856
2932
|
- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or "axis": "vertical"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).
|
|
2857
2933
|
- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale "blend": "add" to stack them too (what they add stays); "blend": "replace" makes a change take over instead.
|
|
2858
2934
|
- Any command but enter and exit can "repeat" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); "duration" is one cycle.
|
|
@@ -2951,5 +3027,6 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
|
|
|
2951
3027
|
|
|
2952
3028
|
|
|
2953
3029
|
|
|
2954
|
-
|
|
3030
|
+
|
|
3031
|
+
exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.fitMotionRect = fitMotionRect; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCycleDuration = motionCycleDuration; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPathLength = motionPathLength; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
|
|
2955
3032
|
//# sourceMappingURL=index.cjs.map
|