@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 +17 -1
- package/dist/index.js +72 -1
- package/package.json +3 -3
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.
|
|
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.
|
|
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.
|
|
54
|
+
"@markdy/stdlib-systems": "0.8.28"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|