@markdy/renderer-dom 0.8.27 → 0.8.28

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/index.d.ts CHANGED
@@ -33,6 +33,7 @@ interface DiagramOptions {
33
33
  onWarning?: (warning: Diagnostic) => void;
34
34
  onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
35
35
  onPlayStateChange?: (playing: boolean) => void;
36
+ onEnded?: () => void;
36
37
  }
37
38
  interface Diagram {
38
39
  play(): void;
@@ -81,6 +82,21 @@ interface SvgExportOptions {
81
82
  }
82
83
  declare function exportDiagramAsVectorSvg(containerEl: HTMLElement, options?: SvgExportOptions): string;
83
84
 
85
+ interface TimelineController {
86
+ seek(seconds: number): void;
87
+ currentTime(): number;
88
+ duration(): number;
89
+ isPlaying(): boolean;
90
+ play(): void;
91
+ pause(): void;
92
+ }
93
+ interface GifDiagramExportOptions extends SvgExportOptions {
94
+ fps?: number;
95
+ pixelRatio?: number;
96
+ loop?: boolean;
97
+ }
98
+ declare function exportDiagramAsGif(container: HTMLElement, timeline: TimelineController, options?: GifDiagramExportOptions): Promise<Blob>;
99
+
84
100
  /**
85
101
  * packages/renderer-dom/src/export/png-exporter.ts
86
102
  * High-DPI raster PNG export with 2x retina scaling.
@@ -119,4 +135,4 @@ declare class DiagramPresentationController {
119
135
  destroy(): void;
120
136
  }
121
137
 
122
- export { type AnimationRecordFrame, type ControllerOptions, type Diagram, type DiagramOptions, DiagramPresentationController, type GifExportOptions, ICON_REGISTRY, type IconSpec, type PngExportOptions, type SvgExportOptions, createDiagram, encodeGifSequence, exportDiagramAsPng, exportDiagramAsVectorSvg };
138
+ export { type AnimationRecordFrame, type ControllerOptions, type Diagram, type DiagramOptions, DiagramPresentationController, type GifDiagramExportOptions, type GifExportOptions, ICON_REGISTRY, type IconSpec, type PngExportOptions, type SvgExportOptions, type TimelineController, createDiagram, encodeGifSequence, exportDiagramAsGif, exportDiagramAsPng, exportDiagramAsVectorSvg };
package/dist/index.js CHANGED
@@ -2091,7 +2091,8 @@ function createDiagram(opts) {
2091
2091
  copyright: explicitCopyright,
2092
2092
  onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
2093
2093
  onTimeUpdate,
2094
- onPlayStateChange
2094
+ onPlayStateChange,
2095
+ onEnded
2095
2096
  } = opts;
2096
2097
  const { ast, plan } = parseAndCompile(code);
2097
2098
  for (const w of ast.diagnostics) {
@@ -2503,6 +2504,7 @@ function createDiagram(opts) {
2503
2504
  emitPlayStateChange(false);
2504
2505
  lastRafTs = null;
2505
2506
  rafId = null;
2507
+ onEnded?.();
2506
2508
  return;
2507
2509
  }
2508
2510
  }
@@ -2861,6 +2863,19 @@ function encodeGifSequence(frames, options = {}) {
2861
2863
  }
2862
2864
 
2863
2865
  // src/export/svg-exporter.ts
2866
+ function copyRenderedStyles(source, clone) {
2867
+ if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
2868
+ const sourceElements = [source, ...Array.from(source.querySelectorAll("*"))];
2869
+ const cloneElements = [clone, ...Array.from(clone.querySelectorAll("*"))];
2870
+ for (let index = 0; index < Math.min(sourceElements.length, cloneElements.length); index++) {
2871
+ const computed = window.getComputedStyle(sourceElements[index]);
2872
+ const target = cloneElements[index].style;
2873
+ for (let propertyIndex = 0; propertyIndex < computed.length; propertyIndex++) {
2874
+ const property = computed.item(propertyIndex);
2875
+ target.setProperty(property, computed.getPropertyValue(property), computed.getPropertyPriority(property));
2876
+ }
2877
+ }
2878
+ }
2864
2879
  function exportDiagramAsVectorSvg(containerEl, options = {}) {
2865
2880
  const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
2866
2881
  if (!sceneEl) throw new Error("No Markdy scene element found in container");
@@ -2874,6 +2889,7 @@ function exportDiagramAsVectorSvg(containerEl, options = {}) {
2874
2889
  ${serializer2.serializeToString(clonedSvg)}`;
2875
2890
  }
2876
2891
  const clonedScene = sceneEl.cloneNode(true);
2892
+ copyRenderedStyles(sceneEl, clonedScene);
2877
2893
  const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
2878
2894
  const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
2879
2895
  let width = parseFloat(widthStr);
@@ -2923,6 +2939,60 @@ ${serializer2.serializeToString(clonedSvg)}`;
2923
2939
  ` + serializer.serializeToString(svg);
2924
2940
  }
2925
2941
 
2942
+ // src/export/gif-exporter.ts
2943
+ var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
2944
+ async function rasterizeFrame(container, pixelRatio, options) {
2945
+ const svg = exportDiagramAsVectorSvg(container, options);
2946
+ const url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml;charset=utf-8" }));
2947
+ try {
2948
+ const image = new Image();
2949
+ await new Promise((resolve, reject) => {
2950
+ image.onload = () => resolve();
2951
+ image.onerror = () => reject(new Error("Failed to rasterize GIF frame"));
2952
+ image.src = url;
2953
+ });
2954
+ const canvas = document.createElement("canvas");
2955
+ canvas.width = image.naturalWidth * pixelRatio;
2956
+ canvas.height = image.naturalHeight * pixelRatio;
2957
+ const context = canvas.getContext("2d");
2958
+ if (!context) throw new Error("Could not create GIF canvas context");
2959
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
2960
+ return context.getImageData(0, 0, canvas.width, canvas.height);
2961
+ } finally {
2962
+ URL.revokeObjectURL(url);
2963
+ }
2964
+ }
2965
+ async function exportDiagramAsGif(container, timeline, options = {}) {
2966
+ const fps = Math.min(24, Math.max(1, Math.round(options.fps ?? 12)));
2967
+ const duration = timeline.duration();
2968
+ const frameCount = Math.max(1, Math.ceil(duration * fps));
2969
+ const delayMs = Math.max(20, Math.round(1e3 / fps));
2970
+ const priorTime = timeline.currentTime();
2971
+ const wasPlaying = timeline.isPlaying();
2972
+ timeline.pause();
2973
+ try {
2974
+ const frames = [];
2975
+ for (let frame = 0; frame < frameCount; frame++) {
2976
+ timeline.seek(Math.min(duration, frame / fps));
2977
+ await nextFrame();
2978
+ frames.push({
2979
+ imageData: await rasterizeFrame(container, options.pixelRatio ?? 1, options),
2980
+ delayMs
2981
+ });
2982
+ }
2983
+ timeline.seek(duration);
2984
+ await nextFrame();
2985
+ frames.push({ imageData: await rasterizeFrame(container, options.pixelRatio ?? 1, options), delayMs: Math.max(delayMs, 800) });
2986
+ const encoded = encodeGifSequence(frames, { dither: true, loop: options.loop ?? true });
2987
+ const bytes = new Uint8Array(encoded.byteLength);
2988
+ bytes.set(encoded);
2989
+ return new Blob([bytes.buffer], { type: "image/gif" });
2990
+ } finally {
2991
+ timeline.seek(priorTime);
2992
+ if (wasPlaying) timeline.play();
2993
+ }
2994
+ }
2995
+
2926
2996
  // src/export/png-exporter.ts
2927
2997
  async function exportDiagramAsPng(containerEl, options = {}) {
2928
2998
  const svgXml = exportDiagramAsVectorSvg(containerEl, options);
@@ -3044,6 +3114,7 @@ export {
3044
3114
  ICON_REGISTRY,
3045
3115
  createDiagram,
3046
3116
  encodeGifSequence,
3117
+ exportDiagramAsGif,
3047
3118
  exportDiagramAsPng,
3048
3119
  exportDiagramAsVectorSvg
3049
3120
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.8.27",
3
+ "version": "0.8.28",
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.27"
47
+ "@markdy/core": "0.8.28"
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.27"
54
+ "@markdy/stdlib-systems": "0.8.28"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsup",