animot-presenter 0.5.21 → 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 -98
- package/dist/cdn/animot-presenter.esm.js +4241 -4256
- 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,76 +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
|
-
/**
|
|
395
|
-
* Overlay tween-driven values for properties that the renderer reads
|
|
396
|
-
* directly from element data (instead of via tween.current). Only
|
|
397
|
-
* applies when this element has keyframes OR has an active override —
|
|
398
|
-
* for everything else this is a passthrough so the existing slide-morph
|
|
399
|
-
* render pipeline is left exactly as it was. Without this gate, every
|
|
400
|
-
* render frame allocated a new copy and crowded out the tween reads.
|
|
401
|
-
*/
|
|
402
|
-
function liveProps<T extends CanvasElement>(element: T): T {
|
|
403
|
-
const hasKeyframes = !!element.keyframes && element.keyframes.length > 0;
|
|
404
|
-
const overrides = keyframeOverrides.get(element.id);
|
|
405
|
-
if (!hasKeyframes && !overrides) return element;
|
|
406
|
-
const a = animatedElements.get(element.id) as any;
|
|
407
|
-
const e = element as any;
|
|
408
|
-
const out: any = { ...element };
|
|
409
|
-
if (a && hasKeyframes) {
|
|
410
|
-
if (a.borderRadius && e.borderRadius !== undefined) out.borderRadius = a.borderRadius.current;
|
|
411
|
-
if (a.fontSize && e.fontSize !== undefined) out.fontSize = a.fontSize.current;
|
|
412
|
-
if (a.fillColor && e.fillColor !== undefined) out.fillColor = a.fillColor.current;
|
|
413
|
-
if (a.strokeColor && e.strokeColor !== undefined) out.strokeColor = a.strokeColor.current;
|
|
414
|
-
if (a.strokeWidth && e.strokeWidth !== undefined) out.strokeWidth = a.strokeWidth.current;
|
|
415
|
-
if (a.blur && e.blur !== undefined) out.blur = a.blur.current;
|
|
416
|
-
if (a.brightness && e.brightness !== undefined) out.brightness = a.brightness.current;
|
|
417
|
-
if (a.contrast && e.contrast !== undefined) out.contrast = a.contrast.current;
|
|
418
|
-
if (a.saturate && e.saturate !== undefined) out.saturate = a.saturate.current;
|
|
419
|
-
if (a.grayscale && e.grayscale !== undefined) out.grayscale = a.grayscale.current;
|
|
420
|
-
}
|
|
421
|
-
if (overrides) Object.assign(out, overrides);
|
|
422
|
-
return out as T;
|
|
346
|
+
function getElementInSlide(slide: Slide | null, elementId: string): CanvasElement | undefined {
|
|
347
|
+
return slide?.canvas.elements.find(el => el.id === elementId);
|
|
423
348
|
}
|
|
424
349
|
|
|
425
350
|
// Typewriter
|
|
@@ -853,9 +778,7 @@
|
|
|
853
778
|
// Per-element morphing (transition type = 'none')
|
|
854
779
|
const animations: Promise<void>[] = [];
|
|
855
780
|
for (const elementId of allElementIds) {
|
|
856
|
-
|
|
857
|
-
// from where the keyframe loop left the element on screen.
|
|
858
|
-
const currentEl = getElementInSlide(currentSlide, elementId, 'exit');
|
|
781
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
859
782
|
const animated = animatedElements.get(elementId);
|
|
860
783
|
if (!animated) continue;
|
|
861
784
|
if (currentEl) {
|
|
@@ -916,8 +839,8 @@
|
|
|
916
839
|
const animationTasks: AnimationTask[] = [];
|
|
917
840
|
|
|
918
841
|
for (const elementId of allElementIds) {
|
|
919
|
-
const currentEl = getElementInSlide(currentSlide, elementId
|
|
920
|
-
const targetEl = getElementInSlide(targetSlide, elementId
|
|
842
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
843
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
921
844
|
const animated = animatedElements.get(elementId);
|
|
922
845
|
if (!animated) continue;
|
|
923
846
|
const animConfig = targetEl?.animationConfig || currentEl?.animationConfig;
|
|
@@ -1461,7 +1384,7 @@
|
|
|
1461
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) : ''}` }}
|
|
1462
1385
|
>
|
|
1463
1386
|
{#if element.type === 'code'}
|
|
1464
|
-
{@const codeEl =
|
|
1387
|
+
{@const codeEl = element as CodeElement}
|
|
1465
1388
|
{@const morphState = codeMorphState.get(codeEl.id)}
|
|
1466
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'}>
|
|
1467
1390
|
{#if codeEl.showHeader}
|
|
@@ -1518,7 +1441,7 @@
|
|
|
1518
1441
|
</div>
|
|
1519
1442
|
</div>
|
|
1520
1443
|
{:else if element.type === 'text'}
|
|
1521
|
-
{@const textEl =
|
|
1444
|
+
{@const textEl = element as TextElement}
|
|
1522
1445
|
{@const animFontSize = animated.fontSize?.current ?? textEl.fontSize}
|
|
1523
1446
|
{@const typewriterState = textTypewriterState.get(element.id)}
|
|
1524
1447
|
{@const displayText = typewriterState?.isAnimating ? typewriterState.fullText.slice(0, typewriterState.displayedChars) : textEl.content}
|
|
@@ -1549,7 +1472,7 @@
|
|
|
1549
1472
|
{#if !isActionTextMode}{displayText}{#if typewriterState?.isAnimating}<span class="animot-typewriter-cursor">|</span>{/if}{/if}
|
|
1550
1473
|
</div>
|
|
1551
1474
|
{:else if element.type === 'arrow'}
|
|
1552
|
-
{@const arrowEl =
|
|
1475
|
+
{@const arrowEl = element as ArrowElement}
|
|
1553
1476
|
{@const cp = arrowEl.controlPoints || []}
|
|
1554
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)}
|
|
1555
1478
|
{@const lastCp = cp.length > 0 ? cp[cp.length - 1] : arrowEl.startPoint}
|
|
@@ -1572,11 +1495,11 @@
|
|
|
1572
1495
|
{/if}
|
|
1573
1496
|
</svg>
|
|
1574
1497
|
{:else if element.type === 'image'}
|
|
1575
|
-
{@const imgEl =
|
|
1498
|
+
{@const imgEl = element as ImageElement}
|
|
1576
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'}
|
|
1577
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'} />
|
|
1578
1501
|
{:else if element.type === 'video'}
|
|
1579
|
-
{@const videoEl =
|
|
1502
|
+
{@const videoEl = element as VideoElement}
|
|
1580
1503
|
{@const videoEmbed = parseEmbedUrl(videoEl.src)}
|
|
1581
1504
|
{#if videoEmbed}
|
|
1582
1505
|
<div class="animot-video-element animot-embed-wrap" style:border-radius="{videoEl.borderRadius}px" style:opacity={videoEl.opacity}>
|
|
@@ -1586,7 +1509,7 @@
|
|
|
1586
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>
|
|
1587
1510
|
{/if}
|
|
1588
1511
|
{:else if element.type === 'shape'}
|
|
1589
|
-
{@const shapeEl =
|
|
1512
|
+
{@const shapeEl = element as ShapeElement}
|
|
1590
1513
|
{@const animFill = animated.fillColor?.current ?? shapeEl.fillColor}
|
|
1591
1514
|
{@const animStroke = animated.strokeColor?.current ?? shapeEl.strokeColor}
|
|
1592
1515
|
{@const animStrokeWidth = animated.strokeWidth?.current ?? shapeEl.strokeWidth}
|