@markdy/renderer-dom 0.7.23 → 0.7.25

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/README.md CHANGED
@@ -70,6 +70,9 @@ player.destroy(); // clean up DOM + cancel animations
70
70
  | `loop` | `boolean` | `true` | Loop the animation when it reaches the end |
71
71
  | `copyright` | `boolean` | `true` | Show a small "Powered by Markdy" badge below the animation |
72
72
  | `progressBar` | `boolean` | `true` | Show a rainbow progress bar around the viewport border |
73
+ | `onWarning` | `(warning: ParseWarning) => void` | `console.warn` | Called for each soft parse warning |
74
+ | `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
75
+ | `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
73
76
 
74
77
  ### `Player`
75
78
 
@@ -78,6 +81,11 @@ player.destroy(); // clean up DOM + cancel animations
78
81
  | `play()` | Start or resume playback |
79
82
  | `pause()` | Pause at current position |
80
83
  | `seek(seconds)` | Jump to a specific time |
84
+ | `currentTime()` | Current playback position in seconds |
85
+ | `duration()` | Total scene duration in seconds |
86
+ | `isPlaying()` | Whether the scene is currently playing |
87
+ | `chapters()` | Named `scene "..." { ... }` chapter blocks, in author order (empty if none) |
88
+ | `seekToChapter(name)` | Seek to the start of a named chapter; no-op if the name doesn't match |
81
89
  | `destroy()` | Remove DOM elements and cancel all animations |
82
90
 
83
91
  ## Module Structure
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SceneAST, ParseWarning } from '@markdy/core';
1
+ import { Chapter, SceneAST, ParseWarning } from '@markdy/core';
2
2
 
3
3
  /**
4
4
  * @markdy/renderer-dom — Player
@@ -46,11 +46,22 @@ interface PlayerOptions {
46
46
  * Pass a no-op to silence; pass a custom collector to surface warnings in a UI.
47
47
  */
48
48
  onWarning?: (warning: ParseWarning) => void;
49
+ /** Invoked whenever playback or seek changes the current timeline time. */
50
+ onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
51
+ /** Invoked when playback starts or pauses. */
52
+ onPlayStateChange?: (playing: boolean) => void;
49
53
  }
50
54
  interface Player {
51
55
  play(): void;
52
56
  pause(): void;
53
57
  seek(seconds: number): void;
58
+ currentTime(): number;
59
+ duration(): number;
60
+ isPlaying(): boolean;
61
+ /** Named `scene "..." { ... }` chapter blocks, in author order. Empty when the scene has none. */
62
+ chapters(): Chapter[];
63
+ /** Seeks to the start of the named chapter. No-op if the name doesn't match a chapter. */
64
+ seekToChapter(name: string): void;
54
65
  destroy(): void;
55
66
  }
56
67
  declare function createPlayer(opts: PlayerOptions): Player;
package/dist/index.js CHANGED
@@ -21,10 +21,20 @@ var EASE_MAP = {
21
21
  linear: "linear",
22
22
  in: "ease-in",
23
23
  out: "ease-out",
24
- inout: "ease-in-out"
24
+ inout: "ease-in-out",
25
+ // Named cubic-bezier presets for authors who want a more polished feel
26
+ // than the four raw CSS keywords above without hand-writing a curve.
27
+ smooth: "cubic-bezier(0.4, 0, 0.2, 1)",
28
+ snappy: "cubic-bezier(0.16, 1, 0.3, 1)",
29
+ overshoot: "cubic-bezier(0.34, 1.56, 0.64, 1)",
30
+ sharp: "cubic-bezier(0.4, 0, 1, 1)"
25
31
  };
32
+ var CUBIC_BEZIER_RE = /^cubic-bezier\(\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*,\s*-?\d*\.?\d+\s*\)$/;
26
33
  function toEasing(val) {
27
- return EASE_MAP[String(val ?? "")] ?? "linear";
34
+ const str = String(val ?? "");
35
+ if (EASE_MAP[str]) return EASE_MAP[str];
36
+ if (CUBIC_BEZIER_RE.test(str)) return str;
37
+ return "linear";
28
38
  }
29
39
 
30
40
  // src/figure.ts
@@ -450,6 +460,12 @@ function createActorEl(name, def, assetDefs, assetOverrides) {
450
460
  }
451
461
 
452
462
  // src/animations.ts
463
+ var DEFAULT_EASE_BY_ACTION = {
464
+ enter: "out",
465
+ fade_in: "out",
466
+ exit: "in",
467
+ fade_out: "in"
468
+ };
453
469
  function isSceneDark(scene) {
454
470
  const bg = scene.style.background || "white";
455
471
  let hex = bg.trim().replace(/^#/, "");
@@ -483,7 +499,7 @@ function buildAnimations(ast, actorEls, scene, assetOverrides, faceSwaps) {
483
499
  1,
484
500
  (typeof ev.params.dur === "number" ? ev.params.dur : 0.5) * 1e3
485
501
  );
486
- const easing = toEasing(ev.params.ease);
502
+ const easing = toEasing(ev.params.ease ?? DEFAULT_EASE_BY_ACTION[ev.action]);
487
503
  const baseOpts = {
488
504
  delay: delayMs,
489
505
  duration: durMs,
@@ -1328,10 +1344,13 @@ function createPlayer(opts) {
1328
1344
  loop = true,
1329
1345
  copyright = true,
1330
1346
  progressBar = true,
1331
- onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message} (${w.kind})`)
1347
+ onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message} (${w.kind})`),
1348
+ onTimeUpdate,
1349
+ onPlayStateChange
1332
1350
  } = opts;
1333
1351
  const ast = parse(code, imports ? { imports } : void 0);
1334
1352
  const totalDurationMs = (ast.meta.duration ?? 0) * 1e3;
1353
+ const durationSeconds = totalDurationMs / 1e3;
1335
1354
  for (const w of ast.warnings) onWarning(w);
1336
1355
  const viewport = document.createElement("div");
1337
1356
  Object.assign(viewport.style, {
@@ -1456,6 +1475,7 @@ function createPlayer(opts) {
1456
1475
  anim.currentTime = sceneMs;
1457
1476
  }
1458
1477
  applyFaceSwaps();
1478
+ onTimeUpdate?.(sceneMs / 1e3, durationSeconds);
1459
1479
  }
1460
1480
  function applyFaceSwaps() {
1461
1481
  if (faceSwaps.length === 0) return;
@@ -1489,6 +1509,7 @@ function createPlayer(opts) {
1489
1509
  sceneMs = totalDurationMs;
1490
1510
  applyCurrentTime();
1491
1511
  isPlaying = false;
1512
+ onPlayStateChange?.(false);
1492
1513
  lastRafTs = null;
1493
1514
  rafId = null;
1494
1515
  return;
@@ -1502,12 +1523,14 @@ function createPlayer(opts) {
1502
1523
  play() {
1503
1524
  if (isPlaying) return;
1504
1525
  isPlaying = true;
1526
+ onPlayStateChange?.(true);
1505
1527
  lastRafTs = null;
1506
1528
  rafId = requestAnimationFrame(rafTick);
1507
1529
  },
1508
1530
  pause() {
1509
1531
  if (!isPlaying) return;
1510
1532
  isPlaying = false;
1533
+ onPlayStateChange?.(false);
1511
1534
  if (rafId !== null) {
1512
1535
  cancelAnimationFrame(rafId);
1513
1536
  rafId = null;
@@ -1515,10 +1538,28 @@ function createPlayer(opts) {
1515
1538
  lastRafTs = null;
1516
1539
  },
1517
1540
  seek(seconds) {
1518
- sceneMs = seconds * 1e3;
1541
+ const nextMs = seconds * 1e3;
1542
+ sceneMs = totalDurationMs > 0 ? Math.min(Math.max(nextMs, 0), totalDurationMs) : Math.max(nextMs, 0);
1519
1543
  applyCurrentTime();
1520
1544
  if (totalDurationMs > 0) updateProgressBar(sceneMs / totalDurationMs);
1521
1545
  },
1546
+ currentTime() {
1547
+ return sceneMs / 1e3;
1548
+ },
1549
+ duration() {
1550
+ return durationSeconds;
1551
+ },
1552
+ isPlaying() {
1553
+ return isPlaying;
1554
+ },
1555
+ chapters() {
1556
+ return ast.chapters;
1557
+ },
1558
+ seekToChapter(name) {
1559
+ const chapter = ast.chapters.find((c) => c.name === name);
1560
+ if (!chapter) return;
1561
+ player.seek(chapter.startTime);
1562
+ },
1522
1563
  destroy() {
1523
1564
  player.pause();
1524
1565
  for (const anim of allAnims) anim.cancel();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "Web Animations API renderer for MarkdyScript scenes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@markdy/core": "0.7.23"
46
+ "@markdy/core": "0.7.25"
47
47
  },
48
48
  "devDependencies": {
49
49
  "jsdom": "^29.1.1",