animot-presenter 0.5.20 → 0.5.22
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/dist/AnimotPresenter.svelte +21 -89
- package/dist/cdn/animot-presenter.esm.js +4182 -4197
- package/dist/cdn/animot-presenter.min.js +8 -8
- package/package.json +1 -1
|
@@ -56,18 +56,15 @@
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Keyframe schedule loop — independent from motion-path / morph engines.
|
|
59
|
+
// Plain (non-reactive) state: no $state for the abort controller because
|
|
60
|
+
// the keyframe loop only retargets existing tweens and doesn't need to
|
|
61
|
+
// drive any reactive UI of its own. Adding reactive state here proved to
|
|
62
|
+
// crowd out the existing tween reactivity in 0.5.20.
|
|
59
63
|
let keyframeLoopAbort: AbortController | null = null;
|
|
60
|
-
let keyframeOverrides = $state<Map<string, Record<string, any>>>(new Map());
|
|
61
64
|
function cancelKeyframeLoops() {
|
|
62
65
|
if (keyframeLoopAbort) { keyframeLoopAbort.abort(); keyframeLoopAbort = null; }
|
|
63
66
|
}
|
|
64
|
-
function setKeyframeOverride(elementId: string, prop: string, value: any) {
|
|
65
|
-
const cur = keyframeOverrides.get(elementId) ?? {};
|
|
66
|
-
keyframeOverrides.set(elementId, { ...cur, [prop]: value });
|
|
67
|
-
keyframeOverrides = new Map(keyframeOverrides);
|
|
68
|
-
}
|
|
69
67
|
function easingForTween(name: string | undefined): string {
|
|
70
|
-
// Animotion tween easing accepts strings; pass-through with default.
|
|
71
68
|
switch (name) {
|
|
72
69
|
case 'linear': case 'ease-in': case 'ease-out': case 'ease-in-out': return name;
|
|
73
70
|
case 'spring': return 'ease-out';
|
|
@@ -76,17 +73,19 @@
|
|
|
76
73
|
}
|
|
77
74
|
function animateKeyframes(slide: Slide) {
|
|
78
75
|
cancelKeyframeLoops();
|
|
76
|
+
// Bail if nothing on the slide actually has keyframes — avoids
|
|
77
|
+
// allocating an AbortController + iterating elements for the common
|
|
78
|
+
// case (no keyframes anywhere).
|
|
79
|
+
const hasAnyKeyframes = slide.canvas.elements.some((el) => el.keyframes && el.keyframes.length > 0);
|
|
80
|
+
if (!hasAnyKeyframes) return;
|
|
79
81
|
keyframeLoopAbort = new AbortController();
|
|
80
82
|
const signal = keyframeLoopAbort.signal;
|
|
81
|
-
if (keyframeOverrides.size > 0) keyframeOverrides = new Map();
|
|
82
83
|
for (const element of slide.canvas.elements) {
|
|
83
84
|
if (!element.keyframes || element.keyframes.length === 0) continue;
|
|
84
85
|
const animated = animatedElements.get(element.id) as any;
|
|
85
86
|
if (!animated) continue;
|
|
86
87
|
const sorted = [...element.keyframes].sort((a, b) => a.time - b.time);
|
|
87
88
|
const first = sorted[0];
|
|
88
|
-
|
|
89
|
-
// Snap to KF1 instantly so the slide-displayed pose IS the start.
|
|
90
89
|
if (first.position) { animated.x?.to(first.position.x, { duration: 0 }); animated.y?.to(first.position.y, { duration: 0 }); }
|
|
91
90
|
if (first.size) { animated.width?.to(first.size.width, { duration: 0 }); animated.height?.to(first.size.height, { duration: 0 }); }
|
|
92
91
|
if (first.rotation !== undefined) animated.rotation?.to(first.rotation, { duration: 0 });
|
|
@@ -105,11 +104,7 @@
|
|
|
105
104
|
if (first.contrast !== undefined) animated.contrast?.to(first.contrast, { duration: 0 });
|
|
106
105
|
if (first.saturate !== undefined) animated.saturate?.to(first.saturate, { duration: 0 });
|
|
107
106
|
if (first.grayscale !== undefined) animated.grayscale?.to(first.grayscale, { duration: 0 });
|
|
108
|
-
if (first.backgroundColor !== undefined) setKeyframeOverride(element.id, 'backgroundColor', first.backgroundColor);
|
|
109
|
-
if (first.color !== undefined) setKeyframeOverride(element.id, 'color', first.color);
|
|
110
|
-
|
|
111
107
|
for (const kf of sorted.slice(1)) {
|
|
112
|
-
const delay = Math.max(0, kf.time);
|
|
113
108
|
setTimeout(() => {
|
|
114
109
|
if (signal.aborted) return;
|
|
115
110
|
const easing = easingForTween(kf.easing);
|
|
@@ -134,9 +129,7 @@
|
|
|
134
129
|
if (kf.contrast !== undefined) animated.contrast?.to(kf.contrast, { duration: span, easing });
|
|
135
130
|
if (kf.saturate !== undefined) animated.saturate?.to(kf.saturate, { duration: span, easing });
|
|
136
131
|
if (kf.grayscale !== undefined) animated.grayscale?.to(kf.grayscale, { duration: span, easing });
|
|
137
|
-
|
|
138
|
-
if (kf.color !== undefined) setKeyframeOverride(element.id, 'color', kf.color);
|
|
139
|
-
}, delay);
|
|
132
|
+
}, Math.max(0, kf.time));
|
|
140
133
|
}
|
|
141
134
|
}
|
|
142
135
|
}
|
|
@@ -350,67 +343,8 @@
|
|
|
350
343
|
return elements.map(e => e.id);
|
|
351
344
|
});
|
|
352
345
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* into account. Keyframes turn the element's "effective state" into
|
|
356
|
-
* something time-dependent — `pose='enter'` returns the FIRST keyframe's
|
|
357
|
-
* state (used as morph TARGET / initial-tween-seed), `pose='exit'` the
|
|
358
|
-
* LAST keyframe's (used as morph SOURCE on slide leave).
|
|
359
|
-
*/
|
|
360
|
-
function getElementInSlide(slide: Slide | null, elementId: string, pose: 'enter' | 'exit' = 'enter'): CanvasElement | undefined {
|
|
361
|
-
const el = slide?.canvas.elements.find(el => el.id === elementId);
|
|
362
|
-
if (!el) return el;
|
|
363
|
-
if (!el.keyframes || el.keyframes.length === 0) return el;
|
|
364
|
-
const sorted = [...el.keyframes].sort((a, b) => a.time - b.time);
|
|
365
|
-
const k = pose === 'exit' ? sorted[sorted.length - 1] : sorted[0];
|
|
366
|
-
const out: any = { ...el };
|
|
367
|
-
if (k.position) out.position = k.position;
|
|
368
|
-
if (k.size) out.size = k.size;
|
|
369
|
-
if (k.rotation !== undefined) out.rotation = k.rotation;
|
|
370
|
-
if (k.opacity !== undefined) out.opacity = k.opacity;
|
|
371
|
-
if (k.skewX !== undefined) out.skewX = k.skewX;
|
|
372
|
-
if (k.skewY !== undefined) out.skewY = k.skewY;
|
|
373
|
-
if (k.tiltX !== undefined) out.tiltX = k.tiltX;
|
|
374
|
-
if (k.tiltY !== undefined) out.tiltY = k.tiltY;
|
|
375
|
-
if (k.borderRadius !== undefined) out.borderRadius = k.borderRadius;
|
|
376
|
-
if (k.fontSize !== undefined) out.fontSize = k.fontSize;
|
|
377
|
-
if (k.fillColor !== undefined) out.fillColor = k.fillColor;
|
|
378
|
-
if (k.strokeColor !== undefined) out.strokeColor = k.strokeColor;
|
|
379
|
-
if (k.strokeWidth !== undefined) out.strokeWidth = k.strokeWidth;
|
|
380
|
-
if (k.blur !== undefined) out.blur = k.blur;
|
|
381
|
-
if (k.brightness !== undefined) out.brightness = k.brightness;
|
|
382
|
-
if (k.contrast !== undefined) out.contrast = k.contrast;
|
|
383
|
-
if (k.saturate !== undefined) out.saturate = k.saturate;
|
|
384
|
-
if (k.grayscale !== undefined) out.grayscale = k.grayscale;
|
|
385
|
-
return out as CanvasElement;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Overlay the current LIVE tween values for properties that the renderer
|
|
390
|
-
* reads directly from element data. Lets per-element keyframes drive
|
|
391
|
-
* borderRadius / fontSize / shape colors / stroke width / CSS filters
|
|
392
|
-
* at render time, plus non-tweened overrides (backgroundColor, color).
|
|
393
|
-
*/
|
|
394
|
-
function liveProps<T extends CanvasElement>(element: T): T {
|
|
395
|
-
const a = animatedElements.get(element.id);
|
|
396
|
-
const overrides = keyframeOverrides.get(element.id);
|
|
397
|
-
if (!a && !overrides) return element;
|
|
398
|
-
const e = element as any;
|
|
399
|
-
const out: any = { ...element };
|
|
400
|
-
if (a) {
|
|
401
|
-
if ((a as any).borderRadius && e.borderRadius !== undefined) out.borderRadius = (a as any).borderRadius.current;
|
|
402
|
-
if ((a as any).fontSize && e.fontSize !== undefined) out.fontSize = (a as any).fontSize.current;
|
|
403
|
-
if ((a as any).fillColor && e.fillColor !== undefined) out.fillColor = (a as any).fillColor.current;
|
|
404
|
-
if ((a as any).strokeColor && e.strokeColor !== undefined) out.strokeColor = (a as any).strokeColor.current;
|
|
405
|
-
if ((a as any).strokeWidth && e.strokeWidth !== undefined) out.strokeWidth = (a as any).strokeWidth.current;
|
|
406
|
-
if ((a as any).blur && e.blur !== undefined) out.blur = (a as any).blur.current;
|
|
407
|
-
if ((a as any).brightness && e.brightness !== undefined) out.brightness = (a as any).brightness.current;
|
|
408
|
-
if ((a as any).contrast && e.contrast !== undefined) out.contrast = (a as any).contrast.current;
|
|
409
|
-
if ((a as any).saturate && e.saturate !== undefined) out.saturate = (a as any).saturate.current;
|
|
410
|
-
if ((a as any).grayscale && e.grayscale !== undefined) out.grayscale = (a as any).grayscale.current;
|
|
411
|
-
}
|
|
412
|
-
if (overrides) Object.assign(out, overrides);
|
|
413
|
-
return out as T;
|
|
346
|
+
function getElementInSlide(slide: Slide | null, elementId: string): CanvasElement | undefined {
|
|
347
|
+
return slide?.canvas.elements.find(el => el.id === elementId);
|
|
414
348
|
}
|
|
415
349
|
|
|
416
350
|
// Typewriter
|
|
@@ -844,9 +778,7 @@
|
|
|
844
778
|
// Per-element morphing (transition type = 'none')
|
|
845
779
|
const animations: Promise<void>[] = [];
|
|
846
780
|
for (const elementId of allElementIds) {
|
|
847
|
-
|
|
848
|
-
// from where the keyframe loop left the element on screen.
|
|
849
|
-
const currentEl = getElementInSlide(currentSlide, elementId, 'exit');
|
|
781
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
850
782
|
const animated = animatedElements.get(elementId);
|
|
851
783
|
if (!animated) continue;
|
|
852
784
|
if (currentEl) {
|
|
@@ -907,8 +839,8 @@
|
|
|
907
839
|
const animationTasks: AnimationTask[] = [];
|
|
908
840
|
|
|
909
841
|
for (const elementId of allElementIds) {
|
|
910
|
-
const currentEl = getElementInSlide(currentSlide, elementId
|
|
911
|
-
const targetEl = getElementInSlide(targetSlide, elementId
|
|
842
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
843
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
912
844
|
const animated = animatedElements.get(elementId);
|
|
913
845
|
if (!animated) continue;
|
|
914
846
|
const animConfig = targetEl?.animationConfig || currentEl?.animationConfig;
|
|
@@ -1452,7 +1384,7 @@
|
|
|
1452
1384
|
use:decorations={{ config: element.decorations, slideDuration: currentSlide?.duration, shape: element.type === 'shape' ? { type: (element as any).shapeType, borderRadius: (element as any).borderRadius } : undefined, key: `${currentSlideIndex}-${JSON.stringify(element.decorations ?? null)}-${element.type === 'shape' ? (element as any).shapeType + ':' + ((element as any).borderRadius ?? 0) : ''}` }}
|
|
1453
1385
|
>
|
|
1454
1386
|
{#if element.type === 'code'}
|
|
1455
|
-
{@const codeEl =
|
|
1387
|
+
{@const codeEl = element as CodeElement}
|
|
1456
1388
|
{@const morphState = codeMorphState.get(codeEl.id)}
|
|
1457
1389
|
<div class="animot-code-block" class:transparent-bg={codeEl.transparentBackground} style:font-size="{codeEl.fontSize}px" style:font-weight={codeEl.fontWeight || 400} style:padding="{codeEl.padding}px" style:border-radius="{animated.borderRadius.current}px" style:background={codeEl.bgColor ?? '#0d1117'}>
|
|
1458
1390
|
{#if codeEl.showHeader}
|
|
@@ -1509,7 +1441,7 @@
|
|
|
1509
1441
|
</div>
|
|
1510
1442
|
</div>
|
|
1511
1443
|
{:else if element.type === 'text'}
|
|
1512
|
-
{@const textEl =
|
|
1444
|
+
{@const textEl = element as TextElement}
|
|
1513
1445
|
{@const animFontSize = animated.fontSize?.current ?? textEl.fontSize}
|
|
1514
1446
|
{@const typewriterState = textTypewriterState.get(element.id)}
|
|
1515
1447
|
{@const displayText = typewriterState?.isAnimating ? typewriterState.fullText.slice(0, typewriterState.displayedChars) : textEl.content}
|
|
@@ -1540,7 +1472,7 @@
|
|
|
1540
1472
|
{#if !isActionTextMode}{displayText}{#if typewriterState?.isAnimating}<span class="animot-typewriter-cursor">|</span>{/if}{/if}
|
|
1541
1473
|
</div>
|
|
1542
1474
|
{:else if element.type === 'arrow'}
|
|
1543
|
-
{@const arrowEl =
|
|
1475
|
+
{@const arrowEl = element as ArrowElement}
|
|
1544
1476
|
{@const cp = arrowEl.controlPoints || []}
|
|
1545
1477
|
{@const pathD = cp.length === 0 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} L ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : cp.length === 1 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} Q ${cp[0].x} ${cp[0].y} ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : cp.length === 2 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} C ${cp[0].x} ${cp[0].y} ${cp[1].x} ${cp[1].y} ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : buildCatmullRomPath(arrowEl.startPoint, cp, arrowEl.endPoint)}
|
|
1546
1478
|
{@const lastCp = cp.length > 0 ? cp[cp.length - 1] : arrowEl.startPoint}
|
|
@@ -1563,11 +1495,11 @@
|
|
|
1563
1495
|
{/if}
|
|
1564
1496
|
</svg>
|
|
1565
1497
|
{:else if element.type === 'image'}
|
|
1566
|
-
{@const imgEl =
|
|
1498
|
+
{@const imgEl = element as ImageElement}
|
|
1567
1499
|
{@const clipPath = imgEl.clipMask?.enabled ? (imgEl.clipMask.shapeType === 'circle' ? 'circle(50% at 50% 50%)' : imgEl.clipMask.shapeType === 'ellipse' ? 'ellipse(50% 50% at 50% 50%)' : imgEl.clipMask.shapeType === 'triangle' ? 'polygon(50% 0%, 0% 100%, 100% 100%)' : imgEl.clipMask.shapeType === 'star' ? 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)' : imgEl.clipMask.shapeType === 'hexagon' ? 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)' : (imgEl.clipMask.borderRadius ?? 0) > 0 ? `inset(0 round ${imgEl.clipMask.borderRadius}px)` : 'none') : 'none'}
|
|
1568
1500
|
<img class="animot-image-element" src={imgEl.src} alt="" style:object-fit={imgEl.objectFit} style:border-radius="{imgEl.clipMask?.enabled ? 0 : imgEl.borderRadius}px" style:clip-path={clipPath} style:background-color={imgEl.backgroundColor ?? 'transparent'} />
|
|
1569
1501
|
{:else if element.type === 'video'}
|
|
1570
|
-
{@const videoEl =
|
|
1502
|
+
{@const videoEl = element as VideoElement}
|
|
1571
1503
|
{@const videoEmbed = parseEmbedUrl(videoEl.src)}
|
|
1572
1504
|
{#if videoEmbed}
|
|
1573
1505
|
<div class="animot-video-element animot-embed-wrap" style:border-radius="{videoEl.borderRadius}px" style:opacity={videoEl.opacity}>
|
|
@@ -1577,7 +1509,7 @@
|
|
|
1577
1509
|
<video class="animot-video-element" src={videoEl.src} poster={videoEl.posterImage} autoplay={videoEl.autoplay} loop={videoEl.loop} muted={videoEl.muted} controls={!!videoEl.showControls} playsinline preload="auto" style:object-fit={videoEl.objectFit} style:border-radius="{videoEl.borderRadius}px" style:opacity={videoEl.opacity}></video>
|
|
1578
1510
|
{/if}
|
|
1579
1511
|
{:else if element.type === 'shape'}
|
|
1580
|
-
{@const shapeEl =
|
|
1512
|
+
{@const shapeEl = element as ShapeElement}
|
|
1581
1513
|
{@const animFill = animated.fillColor?.current ?? shapeEl.fillColor}
|
|
1582
1514
|
{@const animStroke = animated.strokeColor?.current ?? shapeEl.strokeColor}
|
|
1583
1515
|
{@const animStrokeWidth = animated.strokeWidth?.current ?? shapeEl.strokeWidth}
|