animot-presenter 0.2.8 → 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.
@@ -433,6 +433,86 @@
433
433
  }
434
434
  }
435
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
+
436
516
  // Animate to slide
437
517
  async function animateToSlide(targetIndex: number) {
438
518
  if (isTransitioning || targetIndex < 0 || targetIndex >= slides.length) return;
@@ -828,17 +908,34 @@
828
908
  const slideDuration = durationOverride ?? currentSlide?.duration ?? 3000;
829
909
  autoplayTimer = setTimeout(() => {
830
910
  if (currentSlideIndex < slides.length - 1) animateToSlide(currentSlideIndex + 1);
831
- else if (loop) animateToSlide(0);
911
+ else if (loop) {
912
+ const loopMode = project?.settings?.loopMode ?? 'reset';
913
+ if (loopMode === 'transition') animateToSlide(0);
914
+ else resetToFirstSlide();
915
+ }
832
916
  else isAutoplay = false;
833
917
  }, slideDuration);
834
918
  }
835
919
  $effect(() => { if (isAutoplay && !isTransitioning) scheduleNextSlide(); });
836
920
  $effect(() => () => clearAutoplayTimer());
837
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
+
838
935
  // Keyboard
839
936
  function handleKeyDown(e: KeyboardEvent) {
840
937
  if (!keyboard) return;
841
- if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { e.preventDefault(); animateToSlide(currentSlideIndex + 1); }
938
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { e.preventDefault(); handleNextSlide(); }
842
939
  else if (e.key === 'ArrowLeft' || e.key === 'Backspace') { e.preventDefault(); animateToSlide(currentSlideIndex - 1); }
843
940
  else if (e.key === 'Home') animateToSlide(0);
844
941
  else if (e.key === 'End') animateToSlide(slides.length - 1);
@@ -884,7 +981,7 @@
884
981
 
885
982
  // Public API (exposed via bind:this)
886
983
  export async function goto(slideIndex: number) { await animateToSlide(slideIndex); }
887
- export async function next() { await animateToSlide(currentSlideIndex + 1); }
984
+ export async function next() { handleNextSlide(); }
888
985
  export async function prev() { await animateToSlide(currentSlideIndex - 1); }
889
986
  export function play() { isAutoplay = true; }
890
987
  export function pause() { isAutoplay = false; clearAutoplayTimer(); }
@@ -1227,7 +1324,7 @@
1227
1324
  <button class="animot-arrow animot-arrow-left" onclick={() => animateToSlide(currentSlideIndex - 1)} disabled={currentSlideIndex === 0 || isTransitioning} aria-label="Previous slide">
1228
1325
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
1229
1326
  </button>
1230
- <button class="animot-arrow animot-arrow-right" onclick={() => animateToSlide(currentSlideIndex + 1)} disabled={currentSlideIndex === slides.length - 1 || isTransitioning} aria-label="Next slide">
1327
+ <button class="animot-arrow animot-arrow-right" onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next slide">
1231
1328
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
1232
1329
  </button>
1233
1330
  {/if}
@@ -1238,7 +1335,7 @@
1238
1335
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
1239
1336
  </button>
1240
1337
  <span class="animot-slide-indicator">{currentSlideIndex + 1} / {slides.length}</span>
1241
- <button onclick={() => animateToSlide(currentSlideIndex + 1)} disabled={currentSlideIndex === slides.length - 1 || isTransitioning} aria-label="Next">
1338
+ <button onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next">
1242
1339
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
1243
1340
  </button>
1244
1341
  <button onclick={() => { isAutoplay = !isAutoplay; if (!isAutoplay) clearAutoplayTimer(); }} class:active={isAutoplay} aria-label={isAutoplay ? 'Pause' : 'Play'}>