animot-presenter 0.2.5 → 0.2.6

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.
@@ -111,6 +111,22 @@
111
111
  return { x: points[0].x, y: points[0].y, angle: 0 };
112
112
  }
113
113
 
114
+ function computeMotionPathPosition(
115
+ mpPoint: { x: number; y: number; angle: number },
116
+ startPoint: { x: number; y: number; angle: number },
117
+ animX: number, animY: number, animW: number, animH: number
118
+ ): { x: number; y: number } {
119
+ const offsetX = (animX + animW / 2) - startPoint.x;
120
+ const offsetY = (animY + animH / 2) - startPoint.y;
121
+ const angleDelta = (mpPoint.angle - startPoint.angle) * Math.PI / 180;
122
+ const cos = Math.cos(angleDelta);
123
+ const sin = Math.sin(angleDelta);
124
+ return {
125
+ x: mpPoint.x + offsetX * cos - offsetY * sin - animW / 2,
126
+ y: mpPoint.y + offsetX * sin + offsetY * cos - animH / 2
127
+ };
128
+ }
129
+
114
130
  let {
115
131
  src, data, autoplay = false, loop = false, controls = true, arrows = false,
116
132
  progress: showProgress = true, keyboard = true, duration: durationOverride,
@@ -316,7 +332,7 @@
316
332
  for (const element of slide.canvas.elements) {
317
333
  if (!animatedElements.has(element.id)) {
318
334
  const inCurrent = getElementInSlide(currentSlide, element.id);
319
- const startOpacity = inCurrent ? 1 : 0;
335
+ const startOpacity = inCurrent ? ((inCurrent as any).opacity ?? 1) : 0;
320
336
  const br = (element as any).borderRadius ?? 0;
321
337
  const isShape = element.type === 'shape';
322
338
  const shapeEl = isShape ? element as ShapeElement : null;
@@ -382,11 +398,14 @@
382
398
  const shouldLoop = element.motionPathConfig.loop;
383
399
 
384
400
  if (shouldLoop) {
401
+ const laps = element.motionPathConfig.laps ?? 0;
385
402
  (async () => {
386
- while (!signal.aborted) {
403
+ let lap = 0;
404
+ while (!signal.aborted && (laps === 0 || lap < laps)) {
387
405
  await animated.motionPathProgress!.to(0, { duration: 0 });
388
406
  await animated.motionPathProgress!.to(1, { duration, easing });
389
- if (!signal.aborted) await new Promise(r => setTimeout(r, 50));
407
+ lap++;
408
+ if (!signal.aborted && (laps === 0 || lap < laps)) await new Promise(r => setTimeout(r, 50));
390
409
  }
391
410
  })();
392
411
  } else {
@@ -435,7 +454,7 @@
435
454
  animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }); animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 });
436
455
  animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 });
437
456
  animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 });
438
- animated.opacity.to(1, { duration: 0 });
457
+ animated.opacity.to((targetEl as any).opacity ?? 1, { duration: 0 });
439
458
  animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 });
440
459
  animated.blur.to(targetEl.blur ?? 0, { duration: 0 });
441
460
  animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 });
@@ -510,7 +529,7 @@
510
529
  await animated.contrast.to(currentEl.contrast ?? 100, { duration: 0 });
511
530
  await animated.saturate.to(currentEl.saturate ?? 100, { duration: 0 });
512
531
  await animated.grayscale.to(currentEl.grayscale ?? 0, { duration: 0 });
513
- await animated.opacity.to(1, { duration: 0 });
532
+ await animated.opacity.to((currentEl as any).opacity ?? 1, { duration: 0 });
514
533
  if (currentEl.type === 'text' && animated.fontSize) await animated.fontSize.to((currentEl as TextElement).fontSize, { duration: 0 });
515
534
  if (currentEl.type === 'shape') {
516
535
  const s = currentEl as ShapeElement;
@@ -578,6 +597,10 @@
578
597
  animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing });
579
598
  }
580
599
  if (!sequencedProps.has('perspective')) anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: elementDuration, easing }));
600
+ if (!sequencedProps.has('opacity')) {
601
+ const targetOpacity = (targetEl as any).opacity ?? 1;
602
+ if (animated.opacity.current !== targetOpacity) anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
603
+ }
581
604
  const sortedSeqs = [...propertySequences].sort((a, b) => a.order - b.order);
582
605
  let cumulativeDelay = 0;
583
606
  for (const seq of sortedSeqs) {
@@ -604,6 +627,7 @@
604
627
  animated.strokeWidth?.to(s.strokeWidth, { duration: seqDuration, easing });
605
628
  }
606
629
  else if (seq.property === 'perspective') animated.perspective.to(targetEl.perspective ?? 1000, { duration: seqDuration, easing });
630
+ else if (seq.property === 'opacity') animated.opacity.to((targetEl as any).opacity ?? 1, { duration: seqDuration, easing });
607
631
  }, seqDelay);
608
632
  cumulativeDelay = seqDelay + seqDuration;
609
633
  }
@@ -625,6 +649,12 @@
625
649
  anims.push(animated.contrast.to(targetEl.contrast ?? 100, { duration: elementDuration, easing }));
626
650
  anims.push(animated.saturate.to(targetEl.saturate ?? 100, { duration: elementDuration, easing }));
627
651
  anims.push(animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing }));
652
+ // Opacity interpolation for morphing elements
653
+ const currOpacity = animated.opacity.current;
654
+ const targetOpacity = (targetEl as any).opacity ?? 1;
655
+ if (currOpacity !== targetOpacity) {
656
+ anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
657
+ }
628
658
  }
629
659
  // Motion path progress — await reset, then animate forward
630
660
  if (animated.motionPathProgress && targetEl.motionPathConfig) {
@@ -686,10 +716,11 @@
686
716
  if (animated.strokeWidth) anims.push(animated.strokeWidth.to(s.strokeWidth, { duration: 0 }));
687
717
  }
688
718
  const entrance = targetEl.animationConfig?.entrance ?? 'fade';
719
+ const targetOpacity = (targetEl as any).opacity ?? 1;
689
720
  if (entrance === 'fade') {
690
- anims.push(animated.opacity.to(1, { duration: elementDuration / 2, easing }));
721
+ anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration / 2, easing }));
691
722
  } else {
692
- anims.push(animated.opacity.to(1, { duration: 0 }));
723
+ anims.push(animated.opacity.to(targetOpacity, { duration: 0 }));
693
724
  }
694
725
  }
695
726
  return anims;
@@ -985,8 +1016,14 @@
985
1016
  {@const mpElement = mpConfig ? currentSlide?.canvas.elements.find(el => el.id === mpConfig.motionPathId) as MotionPathElement | undefined : undefined}
986
1017
  {@const mpProgress = animated?.motionPathProgress?.current ?? 0}
987
1018
  {@const mpPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, (mpConfig.startPercent + (mpConfig.endPercent - mpConfig.startPercent) * mpProgress) / 100) : null}
988
- {@const elemX = mpPoint ? mpPoint.x - (animated?.width.current ?? 0) / 2 : animated?.x.current ?? 0}
989
- {@const elemY = mpPoint ? mpPoint.y - (animated?.height.current ?? 0) / 2 : animated?.y.current ?? 0}
1019
+ {@const mpStartPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, mpConfig.startPercent / 100) : null}
1020
+ {@const mpPos = mpPoint && mpStartPoint && animated
1021
+ ? computeMotionPathPosition(mpPoint, mpStartPoint,
1022
+ animated.x.current, animated.y.current,
1023
+ animated.width.current, animated.height.current)
1024
+ : null}
1025
+ {@const elemX = mpPos ? mpPos.x : (animated?.x.current ?? 0)}
1026
+ {@const elemY = mpPos ? mpPos.y : (animated?.y.current ?? 0)}
990
1027
  {@const mpRotation = mpPoint && mpConfig?.autoRotate
991
1028
  ? mpPoint.angle + (mpConfig.orientationOffset ?? 0)
992
1029
  : null}
@@ -1089,7 +1126,6 @@
1089
1126
  style:border-radius="{textEl.borderRadius}px"
1090
1127
  style:text-align={textEl.textAlign}
1091
1128
  style:justify-content={textEl.textAlign === 'center' ? 'center' : textEl.textAlign === 'right' ? 'flex-end' : 'flex-start'}
1092
- style:opacity={textEl.opacity ?? 1}
1093
1129
  style:-webkit-text-stroke={textEl.textStroke?.enabled ? `${textEl.textStroke.width}px ${textEl.textStroke.color}` : '0'}
1094
1130
  style:text-shadow={textEl.textShadow?.enabled ? `${textEl.textShadow.offsetX}px ${textEl.textShadow.offsetY}px ${textEl.textShadow.blur}px ${textEl.textShadow.color}` : 'none'}
1095
1131
  >
@@ -1118,7 +1154,7 @@
1118
1154
  {:else if element.type === 'image'}
1119
1155
  {@const imgEl = element as ImageElement}
1120
1156
  {@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'}
1121
- <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:opacity={imgEl.opacity} style:clip-path={clipPath} style:background-color={imgEl.backgroundColor ?? 'transparent'} />
1157
+ <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'} />
1122
1158
  {:else if element.type === 'shape'}
1123
1159
  {@const shapeEl = element as ShapeElement}
1124
1160
  {@const animFill = animated.fillColor?.current ?? shapeEl.fillColor}
@@ -1128,15 +1164,15 @@
1128
1164
  {@const morphProgress = animated.shapeMorph?.current ?? 1}
1129
1165
  {@const effectiveShapeType = mState ? (morphProgress >= 1 ? mState.toType : (morphProgress <= 0 ? mState.fromType : null)) : shapeEl.shapeType}
1130
1166
  {@const isMorphing = mState && morphProgress > 0 && morphProgress < 1}
1131
- <svg class="animot-shape-element" viewBox="0 0 {animated.width.current} {animated.height.current}" style:opacity={shapeEl.opacity} style:filter={shapeEl.boxShadow?.enabled ? `drop-shadow(${shapeEl.boxShadow.offsetX}px ${shapeEl.boxShadow.offsetY}px ${shapeEl.boxShadow.blur}px ${shapeEl.boxShadow.color})` : 'none'}>
1167
+ <svg class="animot-shape-element" viewBox="0 0 {animated.width.current} {animated.height.current}" style:filter={shapeEl.boxShadow?.enabled ? `drop-shadow(${shapeEl.boxShadow.offsetX}px ${shapeEl.boxShadow.offsetY}px ${shapeEl.boxShadow.blur}px ${shapeEl.boxShadow.color})` : 'none'}>
1132
1168
  {#if isMorphing}
1133
1169
  {@const w = animated.width.current}
1134
1170
  {@const h = animated.height.current}
1135
1171
  {@const sw = animStrokeWidth}
1136
- <g style:opacity={1 - morphProgress}>{@html renderShape(mState!.fromType, w, h, animated.borderRadius.current, animFill, animStroke, sw)}</g>
1137
- <g style:opacity={morphProgress}>{@html renderShape(mState!.toType, w, h, animated.borderRadius.current, animFill, animStroke, sw)}</g>
1172
+ <g style:opacity={1 - morphProgress}>{@html renderShape(mState!.fromType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
1173
+ <g style:opacity={morphProgress}>{@html renderShape(mState!.toType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
1138
1174
  {:else}
1139
- {@html renderShape(effectiveShapeType ?? shapeEl.shapeType, animated.width.current, animated.height.current, animated.borderRadius.current, animFill, animStroke, animStrokeWidth)}
1175
+ {@html renderShape(effectiveShapeType ?? shapeEl.shapeType, animated.width.current, animated.height.current, animated.borderRadius.current, animFill, animStroke, animStrokeWidth, shapeEl.strokeStyle, shapeEl.strokeDashGap)}
1140
1176
  {/if}
1141
1177
  </svg>
1142
1178
  {:else if element.type === 'counter'}
@@ -1148,7 +1184,7 @@
1148
1184
  {:else if element.type === 'svg'}
1149
1185
  {@const svgEl = element as SvgElement}
1150
1186
  {@const svgParsed = (() => { const m = svgEl.svgContent.trim().match(/^<svg([^>]*)>([\s\S]*)<\/svg>$/i); if (m) { const vb = m[1].match(/viewBox=["']([^"']+)["']/i); return { inner: m[2], viewBox: vb ? vb[1] : null }; } return { inner: svgEl.svgContent, viewBox: null }; })()}
1151
- <div class="animot-svg-element" style:opacity={svgEl.opacity}>
1187
+ <div class="animot-svg-element">
1152
1188
  <svg width="100%" height="100%" viewBox={svgEl.viewBox ?? svgParsed.viewBox ?? `0 0 ${svgEl.size.width} ${svgEl.size.height}`} preserveAspectRatio={svgEl.preserveAspectRatio} xmlns="http://www.w3.org/2000/svg">
1153
1189
  <g style={svgEl.color ? `fill:${svgEl.color};stroke:${svgEl.color}` : ''}>
1154
1190
  {@html svgParsed.inner}
@@ -1158,7 +1194,7 @@
1158
1194
  {:else if element.type === 'motionPath'}
1159
1195
  {@const mpEl = element as MotionPathElement}
1160
1196
  {#if mpEl.showInPresentation}
1161
- <svg width="100%" height="100%" style="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;">
1197
+ <svg width="100%" height="100%" viewBox="0 0 {animated.width.current} {animated.height.current}" style="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;">
1162
1198
  <path d={buildPresenterPathD(mpEl.points, mpEl.closed)} stroke={mpEl.pathColor} stroke-width={mpEl.pathWidth} fill="none" stroke-dasharray="8 4" />
1163
1199
  </svg>
1164
1200
  {/if}
@@ -1204,21 +1240,29 @@
1204
1240
  </div>
1205
1241
 
1206
1242
  <script module lang="ts">
1207
- function renderShape(type: string, w: number, h: number, br: number, fill: string, stroke: string, sw: number): string {
1243
+ function renderShape(type: string, w: number, h: number, br: number, fill: string, stroke: string, sw: number, strokeStyle?: string, strokeDashGap?: number): string {
1244
+ let dashAttr = '';
1245
+ if (strokeStyle && strokeStyle !== 'solid') {
1246
+ const s = sw || 1;
1247
+ const gap = strokeDashGap ?? (strokeStyle === 'dashed' ? s * 3 : s * 2);
1248
+ const da = strokeStyle === 'dashed' ? `${s * 3},${gap}` : `${s * 0.1},${gap}`;
1249
+ const lc = strokeStyle === 'dotted' ? 'round' : 'butt';
1250
+ dashAttr = ` stroke-dasharray="${da}" stroke-linecap="${lc}"`;
1251
+ }
1208
1252
  switch (type) {
1209
- case 'rectangle': return `<rect x="${sw/2}" y="${sw/2}" width="${w-sw}" height="${h-sw}" rx="${br}" ry="${br}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"/>`;
1210
- case 'circle': return `<circle cx="${w/2}" cy="${h/2}" r="${Math.min(w,h)/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"/>`;
1211
- case 'ellipse': return `<ellipse cx="${w/2}" cy="${h/2}" rx="${w/2-sw/2}" ry="${h/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"/>`;
1212
- case 'triangle': return `<polygon points="${w/2},${sw/2} ${sw/2},${h-sw/2} ${w-sw/2},${h-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
1253
+ case 'rectangle': return `<rect x="${sw/2}" y="${sw/2}" width="${w-sw}" height="${h-sw}" rx="${br}" ry="${br}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
1254
+ case 'circle': return `<circle cx="${w/2}" cy="${h/2}" r="${Math.min(w,h)/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
1255
+ case 'ellipse': return `<ellipse cx="${w/2}" cy="${h/2}" rx="${w/2-sw/2}" ry="${h/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
1256
+ case 'triangle': return `<polygon points="${w/2},${sw/2} ${sw/2},${h-sw/2} ${w-sw/2},${h-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
1213
1257
  case 'star': {
1214
1258
  const cx = w/2, cy = h/2, outerR = Math.min(w,h)/2-sw/2, innerR = outerR*0.4;
1215
1259
  const pts = Array.from({length:10},(_,i)=>{const a=(i*Math.PI/5)-Math.PI/2;const r=i%2===0?outerR:innerR;return`${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`;}).join(' ');
1216
- return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
1260
+ return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
1217
1261
  }
1218
1262
  case 'hexagon': {
1219
1263
  const cx = w/2, cy = h/2, r = Math.min(w,h)/2-sw/2;
1220
1264
  const pts = Array.from({length:6},(_,i)=>{const a=(i*Math.PI/3)-Math.PI/2;return`${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`;}).join(' ');
1221
- return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
1265
+ return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
1222
1266
  }
1223
1267
  default: return '';
1224
1268
  }