@markdy/renderer-dom 0.8.13 → 0.8.15

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
@@ -11,7 +11,8 @@ Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Tra
11
11
  - **Focused diagram visuals** — flowchart shapes, tree buses, state self-loops, sequence lifelines/messages, constellation orbits, group zones, and annotations
12
12
  - **Semantic node cards** — compact SVG glyphs for browsers, services, gateways, queues, workers, databases, storage, CDN, security, platform, and more
13
13
  - **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
14
- - **Playback-rate controls** — set timeline speed to slow down or speed up diagrams without rebuilding animations
14
+ - **Playback-rate controls** — set normalized timeline speed to slow down or speed up diagrams without rebuilding animations
15
+ - **Interactive viewport** — opt into wheel zoom and drag pan while click-to-pause stays available
15
16
  - **Semantic themes** — `paper`, `editorial`, `nebula`, `midnight`, `blueprint`, and `graphite`, with per-role node and edge colors
16
17
  - **Single dependency** — only `@markdy/core`
17
18
 
@@ -73,7 +74,8 @@ diagram.destroy(); // clean up DOM + cancel animations
73
74
  | `copyright` | `boolean` | `true` | Show a small "Powered by Markdy" badge below the animation |
74
75
  | `progressBar` | `boolean` | `true` | Deprecated compatibility flag for the rainbow scene-boundary progress bar |
75
76
  | `sceneBoundaryProgress` | `boolean` | `progressBar ?? true` | Preferred flag for the rainbow scene-boundary progress bar |
76
- | `playbackRate` | `number` | `1` | Timeline speed multiplier |
77
+ | `playbackRate` | `number` | `1` | Normalized timeline speed multiplier; `1` is Markdy's normal pace |
78
+ | `interactiveViewport` | `boolean` | `false` | Enable wheel zoom and drag pan on the rendered viewport |
77
79
  | `onWarning` | `(warning: Diagnostic) => void` | `console.warn` | Called for each soft parse warning |
78
80
  | `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
79
81
  | `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
package/dist/index.d.ts CHANGED
@@ -20,8 +20,10 @@ interface DiagramOptions {
20
20
  progressBar?: boolean;
21
21
  /** Show rainbow progress around scene boundary. Defaults to true. */
22
22
  sceneBoundaryProgress?: boolean;
23
- /** Playback speed multiplier. Defaults to 1. */
23
+ /** Playback speed multiplier. Defaults to 1, where 1 is Markdy's normal pace. */
24
24
  playbackRate?: number;
25
+ /** Enable wheel zoom and drag pan on the rendered viewport. Defaults to false. */
26
+ interactiveViewport?: boolean;
25
27
  onWarning?: (warning: Diagnostic) => void;
26
28
  onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
27
29
  onPlayStateChange?: (playing: boolean) => void;
package/dist/index.js CHANGED
@@ -1810,6 +1810,12 @@ function applyThemeToScene(scene, theme) {
1810
1810
  }
1811
1811
 
1812
1812
  // src/diagram.ts
1813
+ var NORMAL_PLAYBACK_RATE = 4 / 5;
1814
+ var DEFAULT_PLAYBACK_RATE = 1;
1815
+ var MIN_VIEWPORT_ZOOM = 0.5;
1816
+ var MAX_VIEWPORT_ZOOM = 3;
1817
+ var VIEWPORT_ZOOM_STEP = 15e-4;
1818
+ var DRAG_CLICK_THRESHOLD_PX = 4;
1813
1819
  function createBeatCaptionLayer(doc, beats) {
1814
1820
  const layer = doc.createElement("div");
1815
1821
  layer.className = "markdy-beat-caption-layer";
@@ -1856,7 +1862,8 @@ function createDiagram(opts) {
1856
1862
  assets,
1857
1863
  progressBar,
1858
1864
  sceneBoundaryProgress,
1859
- playbackRate: initialPlaybackRate = 1,
1865
+ playbackRate: initialPlaybackRate = DEFAULT_PLAYBACK_RATE,
1866
+ interactiveViewport = false,
1860
1867
  onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
1861
1868
  onTimeUpdate,
1862
1869
  onPlayStateChange
@@ -1923,6 +1930,15 @@ function createDiagram(opts) {
1923
1930
  }
1924
1931
  ensureSceneStyles(document);
1925
1932
  ensureNodeStyles(document);
1933
+ const viewportTransform = document.createElement("div");
1934
+ viewportTransform.className = "markdy-viewport-transform";
1935
+ Object.assign(viewportTransform.style, {
1936
+ position: "absolute",
1937
+ inset: "0",
1938
+ transformOrigin: "0 0",
1939
+ willChange: interactiveViewport ? "transform" : "auto"
1940
+ });
1941
+ viewport.appendChild(viewportTransform);
1926
1942
  const scene = document.createElement("div");
1927
1943
  scene.className = "markdy-scene-root";
1928
1944
  Object.assign(scene.style, {
@@ -1936,7 +1952,7 @@ function createDiagram(opts) {
1936
1952
  transformOrigin: "0 0"
1937
1953
  });
1938
1954
  applyThemeToScene(scene, plan.theme);
1939
- viewport.appendChild(scene);
1955
+ viewportTransform.appendChild(scene);
1940
1956
  const sceneContent = document.createElement("div");
1941
1957
  sceneContent.className = "markdy-scene-content";
1942
1958
  Object.assign(sceneContent.style, { position: "absolute", inset: "0" });
@@ -2042,16 +2058,75 @@ function createDiagram(opts) {
2042
2058
  anim.currentTime = 0;
2043
2059
  }
2044
2060
  let sceneMs = 0;
2045
- let playbackRate = Number.isFinite(initialPlaybackRate) && initialPlaybackRate > 0 ? initialPlaybackRate : 1;
2061
+ let playbackRate = Number.isFinite(initialPlaybackRate) && initialPlaybackRate > 0 ? initialPlaybackRate : DEFAULT_PLAYBACK_RATE;
2046
2062
  let lastRafTs = null;
2047
2063
  let isPlaying = false;
2048
2064
  let rafId = null;
2065
+ let viewportScale = 1;
2066
+ let viewportPanX = 0;
2067
+ let viewportPanY = 0;
2068
+ let activePointerId = null;
2069
+ let dragStartX = 0;
2070
+ let dragStartY = 0;
2071
+ let dragLastX = 0;
2072
+ let dragLastY = 0;
2073
+ let dragMoved = false;
2074
+ let suppressNextClick = false;
2075
+ function applyViewportTransform() {
2076
+ viewportTransform.style.transform = `translate(${viewportPanX}px, ${viewportPanY}px) scale(${viewportScale})`;
2077
+ }
2078
+ function handleViewportWheel(event) {
2079
+ event.preventDefault();
2080
+ const rect = viewport.getBoundingClientRect();
2081
+ const pointerX = event.clientX - rect.left;
2082
+ const pointerY = event.clientY - rect.top;
2083
+ const nextScale = Math.min(MAX_VIEWPORT_ZOOM, Math.max(MIN_VIEWPORT_ZOOM, viewportScale * Math.exp(-event.deltaY * VIEWPORT_ZOOM_STEP)));
2084
+ if (nextScale === viewportScale) return;
2085
+ const sceneX = (pointerX - viewportPanX) / viewportScale;
2086
+ const sceneY = (pointerY - viewportPanY) / viewportScale;
2087
+ viewportScale = nextScale;
2088
+ viewportPanX = pointerX - sceneX * viewportScale;
2089
+ viewportPanY = pointerY - sceneY * viewportScale;
2090
+ applyViewportTransform();
2091
+ }
2092
+ function handleViewportPointerDown(event) {
2093
+ if (event.button !== 0 || activePointerId !== null) return;
2094
+ activePointerId = event.pointerId;
2095
+ dragStartX = event.clientX;
2096
+ dragStartY = event.clientY;
2097
+ dragLastX = event.clientX;
2098
+ dragLastY = event.clientY;
2099
+ dragMoved = false;
2100
+ viewport.setPointerCapture(event.pointerId);
2101
+ viewport.style.cursor = "grabbing";
2102
+ }
2103
+ function handleViewportPointerMove(event) {
2104
+ if (event.pointerId !== activePointerId) return;
2105
+ const deltaX = event.clientX - dragLastX;
2106
+ const deltaY = event.clientY - dragLastY;
2107
+ const totalX = event.clientX - dragStartX;
2108
+ const totalY = event.clientY - dragStartY;
2109
+ if (!dragMoved && Math.hypot(totalX, totalY) >= DRAG_CLICK_THRESHOLD_PX) dragMoved = true;
2110
+ dragLastX = event.clientX;
2111
+ dragLastY = event.clientY;
2112
+ viewportPanX += deltaX;
2113
+ viewportPanY += deltaY;
2114
+ applyViewportTransform();
2115
+ }
2116
+ function handleViewportPointerEnd(event) {
2117
+ if (event.pointerId !== activePointerId) return;
2118
+ if (dragMoved) suppressNextClick = true;
2119
+ if (viewport.hasPointerCapture(event.pointerId)) viewport.releasePointerCapture(event.pointerId);
2120
+ activePointerId = null;
2121
+ dragMoved = false;
2122
+ viewport.style.cursor = interactiveViewport ? "grab" : "pointer";
2123
+ }
2049
2124
  function applyCurrentTime() {
2050
2125
  for (const anim of allAnims) anim.currentTime = sceneMs;
2051
2126
  onTimeUpdate?.(sceneMs / 1e3, durationSeconds);
2052
2127
  }
2053
2128
  function rafTick(timestamp) {
2054
- if (lastRafTs !== null) sceneMs += (timestamp - lastRafTs) * playbackRate;
2129
+ if (lastRafTs !== null) sceneMs += (timestamp - lastRafTs) * playbackRate * NORMAL_PLAYBACK_RATE;
2055
2130
  lastRafTs = timestamp;
2056
2131
  if (totalDurationMs > 0 && sceneMs >= totalDurationMs) {
2057
2132
  if (loop) sceneMs = sceneMs % totalDurationMs;
@@ -2122,8 +2197,20 @@ function createDiagram(opts) {
2122
2197
  if (viewport.parentNode === container) container.removeChild(viewport);
2123
2198
  }
2124
2199
  };
2125
- viewport.style.cursor = "pointer";
2200
+ viewport.style.cursor = interactiveViewport ? "grab" : "pointer";
2201
+ if (interactiveViewport) {
2202
+ viewport.style.touchAction = "none";
2203
+ viewport.addEventListener("wheel", handleViewportWheel, { passive: false });
2204
+ viewport.addEventListener("pointerdown", handleViewportPointerDown);
2205
+ viewport.addEventListener("pointermove", handleViewportPointerMove);
2206
+ viewport.addEventListener("pointerup", handleViewportPointerEnd);
2207
+ viewport.addEventListener("pointercancel", handleViewportPointerEnd);
2208
+ }
2126
2209
  viewport.addEventListener("click", () => {
2210
+ if (suppressNextClick) {
2211
+ suppressNextClick = false;
2212
+ return;
2213
+ }
2127
2214
  if (isPlaying) diagram.pause();
2128
2215
  else {
2129
2216
  if (!loop && sceneMs >= totalDurationMs) sceneMs = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.8.13",
3
+ "version": "0.8.15",
4
4
  "description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,14 +44,14 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@markdy/core": "0.8.13"
47
+ "@markdy/core": "0.8.15"
48
48
  },
49
49
  "devDependencies": {
50
50
  "jsdom": "^29.1.1",
51
51
  "tsup": "^8.5.1",
52
52
  "typescript": "^5.9.3",
53
53
  "vitest": "^4.1.7",
54
- "@markdy/stdlib-systems": "0.8.13"
54
+ "@markdy/stdlib-systems": "0.8.15"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsup",