animot-presenter 0.2.7 → 0.2.9
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 +124 -9
- package/dist/cdn/animot-presenter.esm.js +3471 -3415
- package/dist/cdn/animot-presenter.min.js +8 -8
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -100,10 +100,23 @@
|
|
|
100
100
|
for (const seg of segs) {
|
|
101
101
|
if (accum + seg.length >= targetLength || seg === segs[segs.length - 1]) {
|
|
102
102
|
const t = Math.max(0, Math.min(1, seg.length > 0 ? (targetLength - accum) / seg.length : 0));
|
|
103
|
+
let dx = cubicBezDeriv(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t);
|
|
104
|
+
let dy = cubicBezDeriv(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t);
|
|
105
|
+
// Degenerate tangent at endpoints (no Bezier handles) — sample nearby
|
|
106
|
+
if (Math.abs(dx) < 0.001 && Math.abs(dy) < 0.001) {
|
|
107
|
+
const epsilon = t < 0.5 ? 0.01 : -0.01;
|
|
108
|
+
dx = cubicBezDeriv(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t + epsilon);
|
|
109
|
+
dy = cubicBezDeriv(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t + epsilon);
|
|
110
|
+
}
|
|
111
|
+
// Still zero (fully degenerate segment) — use chord direction
|
|
112
|
+
if (Math.abs(dx) < 0.001 && Math.abs(dy) < 0.001) {
|
|
113
|
+
dx = seg.p3x - seg.p0x;
|
|
114
|
+
dy = seg.p3y - seg.p0y;
|
|
115
|
+
}
|
|
103
116
|
return {
|
|
104
117
|
x: cubicBez(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t),
|
|
105
118
|
y: cubicBez(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t),
|
|
106
|
-
angle: Math.atan2(
|
|
119
|
+
angle: Math.atan2(dy, dx) * (180 / Math.PI)
|
|
107
120
|
};
|
|
108
121
|
}
|
|
109
122
|
accum += seg.length;
|
|
@@ -114,8 +127,12 @@
|
|
|
114
127
|
function computeMotionPathPosition(
|
|
115
128
|
mpPoint: { x: number; y: number; angle: number },
|
|
116
129
|
startPoint: { x: number; y: number; angle: number },
|
|
117
|
-
animX: number, animY: number, animW: number, animH: number
|
|
130
|
+
animX: number, animY: number, animW: number, animH: number,
|
|
131
|
+
closed: boolean
|
|
118
132
|
): { x: number; y: number } {
|
|
133
|
+
if (!closed) {
|
|
134
|
+
return { x: mpPoint.x - animW / 2, y: mpPoint.y - animH / 2 };
|
|
135
|
+
}
|
|
119
136
|
const offsetX = (animX + animW / 2) - startPoint.x;
|
|
120
137
|
const offsetY = (animY + animH / 2) - startPoint.y;
|
|
121
138
|
const angleDelta = (mpPoint.angle - startPoint.angle) * Math.PI / 180;
|
|
@@ -416,6 +433,86 @@
|
|
|
416
433
|
}
|
|
417
434
|
}
|
|
418
435
|
|
|
436
|
+
// Reset presentation to first slide (snap all elements back to initial state)
|
|
437
|
+
async function resetToFirstSlide() {
|
|
438
|
+
if (isTransitioning) return;
|
|
439
|
+
isTransitioning = true;
|
|
440
|
+
clearAllTypewriterAnimations();
|
|
441
|
+
cancelMotionPathLoops();
|
|
442
|
+
const firstSlide = slides[0];
|
|
443
|
+
if (!firstSlide) { isTransitioning = false; return; }
|
|
444
|
+
|
|
445
|
+
for (const elementId of allElementIds) {
|
|
446
|
+
const targetEl = getElementInSlide(firstSlide, elementId);
|
|
447
|
+
const animated = animatedElements.get(elementId);
|
|
448
|
+
if (!animated) continue;
|
|
449
|
+
if (targetEl) {
|
|
450
|
+
animated.x.to(targetEl.position.x, { duration: 0 }); animated.y.to(targetEl.position.y, { duration: 0 });
|
|
451
|
+
animated.width.to(targetEl.size.width, { duration: 0 }); animated.height.to(targetEl.size.height, { duration: 0 });
|
|
452
|
+
animated.rotation.to(targetEl.rotation, { duration: 0 });
|
|
453
|
+
animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }); animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 });
|
|
454
|
+
animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 });
|
|
455
|
+
animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 });
|
|
456
|
+
animated.opacity.to((targetEl as any).opacity ?? 1, { duration: 0 });
|
|
457
|
+
animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 });
|
|
458
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: 0 });
|
|
459
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 });
|
|
460
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 });
|
|
461
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 });
|
|
462
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 });
|
|
463
|
+
if (targetEl.type === 'text' && animated.fontSize) animated.fontSize.to((targetEl as TextElement).fontSize, { duration: 0 });
|
|
464
|
+
if (targetEl.type === 'shape') {
|
|
465
|
+
const s = targetEl as ShapeElement;
|
|
466
|
+
if (animated.fillColor) animated.fillColor.to(s.fillColor, { duration: 0 });
|
|
467
|
+
if (animated.strokeColor) animated.strokeColor.to(s.strokeColor, { duration: 0 });
|
|
468
|
+
if (animated.strokeWidth) animated.strokeWidth.to(s.strokeWidth, { duration: 0 });
|
|
469
|
+
}
|
|
470
|
+
if (animated.motionPathProgress) animated.motionPathProgress.to(0, { duration: 0 });
|
|
471
|
+
} else {
|
|
472
|
+
animated.opacity.to(0, { duration: 0 });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const elementId of allElementIds) {
|
|
477
|
+
const targetEl = getElementInSlide(firstSlide, elementId);
|
|
478
|
+
if (targetEl) elementContent.set(elementId, JSON.parse(JSON.stringify(targetEl)));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const newPreviousCodeContent = new Map<string, string>();
|
|
482
|
+
for (const element of firstSlide.canvas.elements) {
|
|
483
|
+
if (element.type === 'code') newPreviousCodeContent.set(element.id, (element as CodeElement).code);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
for (const element of firstSlide.canvas.elements) {
|
|
487
|
+
if (element.type === 'code') {
|
|
488
|
+
const codeEl = element as CodeElement;
|
|
489
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
490
|
+
if (!codeHighlights.has(key)) {
|
|
491
|
+
const html = await highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers });
|
|
492
|
+
codeHighlights.set(key, html);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
codeHighlights = new Map(codeHighlights);
|
|
497
|
+
|
|
498
|
+
codeMorphState = new Map();
|
|
499
|
+
previousCodeContent = newPreviousCodeContent;
|
|
500
|
+
shapeMorphStates = new Map();
|
|
501
|
+
elementContent = new Map(elementContent);
|
|
502
|
+
currentSlideIndex = 0;
|
|
503
|
+
isTransitioning = false;
|
|
504
|
+
|
|
505
|
+
for (const element of firstSlide.canvas.elements) {
|
|
506
|
+
if (element.type === 'text') {
|
|
507
|
+
const textEl = element as TextElement;
|
|
508
|
+
if (textEl.animation?.mode === 'typewriter') startTypewriterAnimation(element.id, textEl.content, textEl.animation.typewriterSpeed || 50);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
animateMotionPaths(firstSlide);
|
|
513
|
+
onslidechange?.(0, slides.length);
|
|
514
|
+
}
|
|
515
|
+
|
|
419
516
|
// Animate to slide
|
|
420
517
|
async function animateToSlide(targetIndex: number) {
|
|
421
518
|
if (isTransitioning || targetIndex < 0 || targetIndex >= slides.length) return;
|
|
@@ -811,17 +908,34 @@
|
|
|
811
908
|
const slideDuration = durationOverride ?? currentSlide?.duration ?? 3000;
|
|
812
909
|
autoplayTimer = setTimeout(() => {
|
|
813
910
|
if (currentSlideIndex < slides.length - 1) animateToSlide(currentSlideIndex + 1);
|
|
814
|
-
else if (loop)
|
|
911
|
+
else if (loop) {
|
|
912
|
+
const loopMode = project?.settings?.loopMode ?? 'reset';
|
|
913
|
+
if (loopMode === 'transition') animateToSlide(0);
|
|
914
|
+
else resetToFirstSlide();
|
|
915
|
+
}
|
|
815
916
|
else isAutoplay = false;
|
|
816
917
|
}, slideDuration);
|
|
817
918
|
}
|
|
818
919
|
$effect(() => { if (isAutoplay && !isTransitioning) scheduleNextSlide(); });
|
|
819
920
|
$effect(() => () => clearAutoplayTimer());
|
|
820
921
|
|
|
922
|
+
function handleNextSlide() {
|
|
923
|
+
if (currentSlideIndex < slides.length - 1) {
|
|
924
|
+
animateToSlide(currentSlideIndex + 1);
|
|
925
|
+
} else if (loop) {
|
|
926
|
+
const loopMode = project?.settings?.loopMode ?? 'reset';
|
|
927
|
+
if (loopMode === 'transition') {
|
|
928
|
+
animateToSlide(0);
|
|
929
|
+
} else {
|
|
930
|
+
resetToFirstSlide();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
821
935
|
// Keyboard
|
|
822
936
|
function handleKeyDown(e: KeyboardEvent) {
|
|
823
937
|
if (!keyboard) return;
|
|
824
|
-
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { e.preventDefault();
|
|
938
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { e.preventDefault(); handleNextSlide(); }
|
|
825
939
|
else if (e.key === 'ArrowLeft' || e.key === 'Backspace') { e.preventDefault(); animateToSlide(currentSlideIndex - 1); }
|
|
826
940
|
else if (e.key === 'Home') animateToSlide(0);
|
|
827
941
|
else if (e.key === 'End') animateToSlide(slides.length - 1);
|
|
@@ -867,7 +981,7 @@
|
|
|
867
981
|
|
|
868
982
|
// Public API (exposed via bind:this)
|
|
869
983
|
export async function goto(slideIndex: number) { await animateToSlide(slideIndex); }
|
|
870
|
-
export async function next() {
|
|
984
|
+
export async function next() { handleNextSlide(); }
|
|
871
985
|
export async function prev() { await animateToSlide(currentSlideIndex - 1); }
|
|
872
986
|
export function play() { isAutoplay = true; }
|
|
873
987
|
export function pause() { isAutoplay = false; clearAutoplayTimer(); }
|
|
@@ -1017,10 +1131,11 @@
|
|
|
1017
1131
|
{@const mpProgress = animated?.motionPathProgress?.current ?? 0}
|
|
1018
1132
|
{@const mpPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, (mpConfig.startPercent + (mpConfig.endPercent - mpConfig.startPercent) * mpProgress) / 100) : null}
|
|
1019
1133
|
{@const mpStartPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, mpConfig.startPercent / 100) : null}
|
|
1020
|
-
{@const mpPos = mpPoint && mpStartPoint && animated
|
|
1134
|
+
{@const mpPos = mpPoint && mpStartPoint && animated && mpElement
|
|
1021
1135
|
? computeMotionPathPosition(mpPoint, mpStartPoint,
|
|
1022
1136
|
animated.x.current, animated.y.current,
|
|
1023
|
-
animated.width.current, animated.height.current
|
|
1137
|
+
animated.width.current, animated.height.current,
|
|
1138
|
+
mpElement.closed)
|
|
1024
1139
|
: null}
|
|
1025
1140
|
{@const elemX = mpPos ? mpPos.x : (animated?.x.current ?? 0)}
|
|
1026
1141
|
{@const elemY = mpPos ? mpPos.y : (animated?.y.current ?? 0)}
|
|
@@ -1209,7 +1324,7 @@
|
|
|
1209
1324
|
<button class="animot-arrow animot-arrow-left" onclick={() => animateToSlide(currentSlideIndex - 1)} disabled={currentSlideIndex === 0 || isTransitioning} aria-label="Previous slide">
|
|
1210
1325
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
|
|
1211
1326
|
</button>
|
|
1212
|
-
<button class="animot-arrow animot-arrow-right" onclick={() =>
|
|
1327
|
+
<button class="animot-arrow animot-arrow-right" onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next slide">
|
|
1213
1328
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
1214
1329
|
</button>
|
|
1215
1330
|
{/if}
|
|
@@ -1220,7 +1335,7 @@
|
|
|
1220
1335
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
1221
1336
|
</button>
|
|
1222
1337
|
<span class="animot-slide-indicator">{currentSlideIndex + 1} / {slides.length}</span>
|
|
1223
|
-
<button onclick={() =>
|
|
1338
|
+
<button onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next">
|
|
1224
1339
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
1225
1340
|
</button>
|
|
1226
1341
|
<button onclick={() => { isAutoplay = !isAutoplay; if (!isAutoplay) clearAutoplayTimer(); }} class:active={isAutoplay} aria-label={isAutoplay ? 'Pause' : 'Play'}>
|