@markdy/renderer-dom 1.0.0 → 1.0.2

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
@@ -97,17 +97,6 @@ interface GifDiagramExportOptions extends SvgExportOptions {
97
97
  }
98
98
  declare function exportDiagramAsGif(container: HTMLElement, timeline: TimelineController, options?: GifDiagramExportOptions): Promise<Blob>;
99
99
 
100
- /**
101
- * packages/renderer-dom/src/export/png-exporter.ts
102
- * High-DPI raster PNG export with 2x retina scaling.
103
- * Zero external dependencies.
104
- *
105
- * Fix: Inline all external resources (images, fonts) as base64 data URIs
106
- * before drawing the SVG to canvas. A foreignObject-wrapped SVG taints the
107
- * canvas whenever it references any external URL, so every resource must be
108
- * inlined first.
109
- */
110
-
111
100
  interface PngExportOptions extends SvgExportOptions {
112
101
  pixelRatio?: number;
113
102
  }
package/dist/index.js CHANGED
@@ -2038,6 +2038,10 @@ var MIN_VIEWPORT_ZOOM = 0.5;
2038
2038
  var MAX_VIEWPORT_ZOOM = 3;
2039
2039
  var VIEWPORT_ZOOM_STEP = 15e-4;
2040
2040
  var DRAG_CLICK_THRESHOLD_PX = 4;
2041
+ var MARKDY_PLAYGROUND_URL = "https://markdy.com/playground/";
2042
+ function encodeCodeForPlaygroundHash(code) {
2043
+ return encodeURIComponent(btoa(encodeURIComponent(code)));
2044
+ }
2041
2045
  function createBeatCaptionLayer(doc, beats) {
2042
2046
  const layer = doc.createElement("div");
2043
2047
  layer.className = "markdy-beat-caption-layer";
@@ -2171,7 +2175,7 @@ function createDiagram(opts) {
2171
2175
  let badge = null;
2172
2176
  if (copyright) {
2173
2177
  badge = document.createElement("a");
2174
- badge.href = "https://markdy.com";
2178
+ badge.href = `${MARKDY_PLAYGROUND_URL}#code=${encodeCodeForPlaygroundHash(code)}`;
2175
2179
  badge.target = "_blank";
2176
2180
  badge.rel = "noopener noreferrer";
2177
2181
  badge.textContent = "Powered by Markdy";
@@ -2862,6 +2866,9 @@ function encodeGifSequence(frames, options = {}) {
2862
2866
  return new Uint8Array(buffer);
2863
2867
  }
2864
2868
 
2869
+ // src/export/png-exporter.ts
2870
+ import html2canvas from "html2canvas";
2871
+
2865
2872
  // src/export/svg-exporter.ts
2866
2873
  function copyRenderedStyles(source, clone) {
2867
2874
  if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
@@ -2876,20 +2883,22 @@ function copyRenderedStyles(source, clone) {
2876
2883
  }
2877
2884
  }
2878
2885
  }
2879
- function exportDiagramAsVectorSvg(containerEl, options = {}) {
2886
+ function normalizeExportViewport(scene) {
2887
+ scene.querySelectorAll(".markdy-viewport-transform").forEach((viewportTransform) => {
2888
+ viewportTransform.style.transform = "translate(0px, 0px) scale(1)";
2889
+ viewportTransform.style.transformOrigin = "0 0";
2890
+ viewportTransform.style.willChange = "auto";
2891
+ });
2892
+ }
2893
+ function getDiagramSceneElement(containerEl) {
2880
2894
  const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
2881
2895
  if (!sceneEl) throw new Error("No Markdy scene element found in container");
2882
- if (sceneEl.tagName?.toLowerCase() === "svg") {
2883
- const clonedSvg = sceneEl.cloneNode(true);
2884
- if (!clonedSvg.getAttribute("xmlns")) {
2885
- clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
2886
- }
2887
- const serializer2 = new XMLSerializer();
2888
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2889
- ${serializer2.serializeToString(clonedSvg)}`;
2890
- }
2896
+ return sceneEl;
2897
+ }
2898
+ function prepareHtmlSceneForExport(sceneEl, options = {}) {
2891
2899
  const clonedScene = sceneEl.cloneNode(true);
2892
2900
  copyRenderedStyles(sceneEl, clonedScene);
2901
+ normalizeExportViewport(clonedScene);
2893
2902
  const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
2894
2903
  const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
2895
2904
  let width = parseFloat(widthStr);
@@ -2904,10 +2913,25 @@ ${serializer2.serializeToString(clonedSvg)}`;
2904
2913
  clonedScene.style.position = "relative";
2905
2914
  clonedScene.style.left = "0px";
2906
2915
  clonedScene.style.top = "0px";
2916
+ clonedScene.style.margin = "0";
2907
2917
  clonedScene.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2908
2918
  if (options.transparentBackground) {
2909
2919
  clonedScene.style.background = "transparent";
2910
2920
  }
2921
+ return { sceneEl, clonedScene, width, height, scaledWidth, scaledHeight };
2922
+ }
2923
+ function exportDiagramAsVectorSvg(containerEl, options = {}) {
2924
+ const sceneEl = getDiagramSceneElement(containerEl);
2925
+ if (sceneEl.tagName?.toLowerCase() === "svg") {
2926
+ const clonedSvg = sceneEl.cloneNode(true);
2927
+ if (!clonedSvg.getAttribute("xmlns")) {
2928
+ clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
2929
+ }
2930
+ const serializer2 = new XMLSerializer();
2931
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2932
+ ${serializer2.serializeToString(clonedSvg)}`;
2933
+ }
2934
+ const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
2911
2935
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2912
2936
  svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
2913
2937
  svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
@@ -2940,19 +2964,35 @@ ${serializer2.serializeToString(clonedSvg)}`;
2940
2964
  }
2941
2965
 
2942
2966
  // src/export/inline-resources.ts
2967
+ var TRANSPARENT_PIXEL_DATA_URI = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
2968
+ var RESOURCE_FETCH_TIMEOUT_MS = 3e3;
2969
+ var dataUriCache = /* @__PURE__ */ new Map();
2970
+ function shouldInlineUrl(url) {
2971
+ const trimmed = url.trim();
2972
+ return !!trimmed && !trimmed.startsWith("data:") && !trimmed.startsWith("blob:") && !trimmed.startsWith("#");
2973
+ }
2943
2974
  async function toDataUri(url) {
2944
- try {
2945
- const resp = await fetch(url, { mode: "cors", credentials: "same-origin" });
2946
- if (!resp.ok) return null;
2947
- const arrayBuffer = await resp.arrayBuffer();
2948
- const mimeType = resp.headers.get("Content-Type") || "application/octet-stream";
2949
- const base64 = btoa(
2950
- new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
2951
- );
2952
- return `data:${mimeType};base64,${base64}`;
2953
- } catch {
2954
- return null;
2955
- }
2975
+ if (dataUriCache.has(url)) return dataUriCache.get(url);
2976
+ const request = (async () => {
2977
+ const controller = new AbortController();
2978
+ const timeout = setTimeout(() => controller.abort(), RESOURCE_FETCH_TIMEOUT_MS);
2979
+ try {
2980
+ const resp = await fetch(url, { mode: "cors", credentials: "same-origin", signal: controller.signal });
2981
+ if (!resp.ok) return null;
2982
+ const arrayBuffer = await resp.arrayBuffer();
2983
+ const mimeType = resp.headers.get("Content-Type") || "application/octet-stream";
2984
+ const base64 = btoa(
2985
+ new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
2986
+ );
2987
+ return `data:${mimeType};base64,${base64}`;
2988
+ } catch {
2989
+ return null;
2990
+ } finally {
2991
+ clearTimeout(timeout);
2992
+ }
2993
+ })();
2994
+ dataUriCache.set(url, request);
2995
+ return request;
2956
2996
  }
2957
2997
  async function inlineCssUrls(cssValue) {
2958
2998
  const urlPattern = /url\(["']?([^"')]+)["']?\)/g;
@@ -2960,16 +3000,20 @@ async function inlineCssUrls(cssValue) {
2960
3000
  let m;
2961
3001
  while ((m = urlPattern.exec(cssValue)) !== null) {
2962
3002
  const src = m[1];
2963
- if (!src.startsWith("data:")) {
3003
+ if (shouldInlineUrl(src)) {
2964
3004
  matches.push({ full: m[0], src });
2965
3005
  }
2966
3006
  }
2967
3007
  let result = cssValue;
2968
3008
  await Promise.all(
2969
3009
  matches.map(async ({ full, src }) => {
2970
- const absoluteSrc = new URL(src, document.baseURI).href;
2971
- const dataUri = await toDataUri(absoluteSrc);
2972
- if (dataUri) result = result.split(full).join(`url("${dataUri}")`);
3010
+ try {
3011
+ const absoluteSrc = new URL(src, document.baseURI).href;
3012
+ const dataUri = await toDataUri(absoluteSrc);
3013
+ result = result.split(full).join(dataUri ? `url("${dataUri}")` : "none");
3014
+ } catch {
3015
+ result = result.split(full).join("none");
3016
+ }
2973
3017
  })
2974
3018
  );
2975
3019
  return result;
@@ -2978,11 +3022,10 @@ async function inlineExternalResources(root) {
2978
3022
  const tasks = [];
2979
3023
  root.querySelectorAll("img").forEach((img) => {
2980
3024
  const src = img.getAttribute("src");
2981
- if (src && !src.startsWith("data:")) {
2982
- const absoluteSrc = new URL(src, document.baseURI).href;
3025
+ if (src && shouldInlineUrl(src)) {
2983
3026
  tasks.push(
2984
- toDataUri(absoluteSrc).then((dataUri) => {
2985
- if (dataUri) img.setAttribute("src", dataUri);
3027
+ Promise.resolve().then(() => toDataUri(new URL(src, document.baseURI).href)).then((dataUri) => {
3028
+ img.setAttribute("src", dataUri || TRANSPARENT_PIXEL_DATA_URI);
2986
3029
  })
2987
3030
  );
2988
3031
  }
@@ -3000,53 +3043,178 @@ async function inlineExternalResources(root) {
3000
3043
  });
3001
3044
  await Promise.all(tasks);
3002
3045
  }
3046
+ async function inlineSerializedSvgResources(svgXml) {
3047
+ const doc = new DOMParser().parseFromString(svgXml, "image/svg+xml");
3048
+ if (doc.querySelector("parsererror")) return svgXml;
3049
+ await inlineExternalResources(doc.documentElement);
3050
+ const serializer = new XMLSerializer();
3051
+ const serializedSvg = serializer.serializeToString(doc.documentElement);
3052
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3053
+ ${serializedSvg}`;
3054
+ }
3003
3055
 
3004
- // src/export/gif-exporter.ts
3005
- var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
3006
- async function rasterizeFrame(container, pixelRatio, options) {
3007
- const cloned = container.cloneNode(true);
3008
- await inlineExternalResources(cloned);
3009
- const svg = exportDiagramAsVectorSvg(cloned, options);
3010
- const url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml;charset=utf-8" }));
3011
- try {
3012
- const image = new Image();
3013
- await new Promise((resolve, reject) => {
3014
- image.onload = () => resolve();
3015
- image.onerror = () => reject(new Error("Failed to rasterize GIF frame"));
3016
- image.src = url;
3056
+ // src/export/png-exporter.ts
3057
+ function cssColorComponentToByte(value) {
3058
+ const trimmed = value.trim();
3059
+ const numeric = trimmed.endsWith("%") ? Number.parseFloat(trimmed) / 100 : Number.parseFloat(trimmed);
3060
+ if (!Number.isFinite(numeric)) return 0;
3061
+ return Math.round(Math.min(1, Math.max(0, numeric)) * 255);
3062
+ }
3063
+ function cssAlphaToNumber(value) {
3064
+ if (!value) return 1;
3065
+ const trimmed = value.trim();
3066
+ const numeric = trimmed.endsWith("%") ? Number.parseFloat(trimmed) / 100 : Number.parseFloat(trimmed);
3067
+ if (!Number.isFinite(numeric)) return 1;
3068
+ return Math.min(1, Math.max(0, numeric));
3069
+ }
3070
+ function normalizeCssColorFunctions(cssValue) {
3071
+ return cssValue.replace(/color\(\s*[-a-z0-9]+\s+([^)]*)\)/gi, (_match, rawComponents) => {
3072
+ const [rawChannels, rawAlpha] = String(rawComponents).split("/");
3073
+ const channels = rawChannels.trim().split(/\s+/);
3074
+ if (channels.length < 3) return "rgba(0, 0, 0, 0)";
3075
+ const red = cssColorComponentToByte(channels[0]);
3076
+ const green = cssColorComponentToByte(channels[1]);
3077
+ const blue = cssColorComponentToByte(channels[2]);
3078
+ const alpha = cssAlphaToNumber(rawAlpha);
3079
+ return alpha >= 1 ? `rgb(${red}, ${green}, ${blue})` : `rgba(${red}, ${green}, ${blue}, ${alpha})`;
3080
+ });
3081
+ }
3082
+ function sanitizeHtml2CanvasStyles(root) {
3083
+ [root, ...Array.from(root.querySelectorAll("*"))].forEach((element) => {
3084
+ const style = element.getAttribute("style");
3085
+ if (style?.includes("color(")) {
3086
+ element.setAttribute("style", normalizeCssColorFunctions(style));
3087
+ }
3088
+ });
3089
+ }
3090
+ async function exportDiagramAsPng(containerEl, options = {}) {
3091
+ const canvas = await rasterizeDiagramToCanvas(containerEl, options, options.pixelRatio || 2);
3092
+ return new Promise((resolve, reject) => {
3093
+ canvas.toBlob((b) => {
3094
+ if (b) resolve(b);
3095
+ else reject(new Error("Canvas toBlob failed for PNG export"));
3096
+ }, "image/png");
3097
+ });
3098
+ }
3099
+ async function rasterizeDiagramToCanvas(containerEl, options = {}, pixelRatio = 1) {
3100
+ const sceneEl = getDiagramSceneElement(containerEl);
3101
+ if (sceneEl.tagName?.toLowerCase() !== "svg") {
3102
+ const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
3103
+ await inlineExternalResources(clonedScene);
3104
+ sanitizeHtml2CanvasStyles(clonedScene);
3105
+ const host = document.createElement("div");
3106
+ Object.assign(host.style, {
3107
+ position: "fixed",
3108
+ left: "-100000px",
3109
+ top: "0",
3110
+ width: `${scaledWidth}px`,
3111
+ height: `${scaledHeight}px`,
3112
+ overflow: "hidden",
3113
+ pointerEvents: "none",
3114
+ zIndex: "-1"
3017
3115
  });
3018
- const canvas = document.createElement("canvas");
3019
- canvas.width = image.naturalWidth * pixelRatio;
3020
- canvas.height = image.naturalHeight * pixelRatio;
3021
- const context = canvas.getContext("2d");
3022
- if (!context) throw new Error("Could not create GIF canvas context");
3023
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
3024
- return context.getImageData(0, 0, canvas.width, canvas.height);
3025
- } finally {
3116
+ host.setAttribute("aria-hidden", "true");
3117
+ host.appendChild(clonedScene);
3118
+ document.body.appendChild(host);
3119
+ try {
3120
+ await document.fonts?.ready;
3121
+ return await html2canvas(clonedScene, {
3122
+ allowTaint: false,
3123
+ backgroundColor: options.transparentBackground ? null : void 0,
3124
+ height: scaledHeight,
3125
+ imageTimeout: 3e3,
3126
+ logging: false,
3127
+ onclone: (clonedDocument, clonedElement) => {
3128
+ clonedDocument.querySelectorAll("style, link[rel='stylesheet']").forEach((styleNode) => styleNode.remove());
3129
+ sanitizeHtml2CanvasStyles(clonedElement);
3130
+ },
3131
+ scale: pixelRatio,
3132
+ useCORS: true,
3133
+ width: scaledWidth
3134
+ });
3135
+ } finally {
3136
+ host.remove();
3137
+ }
3138
+ }
3139
+ const pixelRatioForSvg = pixelRatio || 1;
3140
+ const clonedContainer = containerEl.cloneNode(true);
3141
+ await inlineExternalResources(clonedContainer);
3142
+ const svgXml = await inlineSerializedSvgResources(exportDiagramAsVectorSvg(clonedContainer, options));
3143
+ const blob = new Blob([svgXml], { type: "image/svg+xml;charset=utf-8" });
3144
+ const url = URL.createObjectURL(blob);
3145
+ const img = new Image();
3146
+ await new Promise((resolve, reject) => {
3147
+ img.onload = () => resolve();
3148
+ img.onerror = () => {
3149
+ URL.revokeObjectURL(url);
3150
+ reject(new Error("Failed to rasterize SVG into Image for PNG export"));
3151
+ };
3152
+ img.src = url;
3153
+ });
3154
+ const width = img.naturalWidth || 800;
3155
+ const height = img.naturalHeight || 400;
3156
+ const canvas = document.createElement("canvas");
3157
+ canvas.width = width * pixelRatioForSvg;
3158
+ canvas.height = height * pixelRatioForSvg;
3159
+ const ctx = canvas.getContext("2d");
3160
+ if (!ctx) {
3026
3161
  URL.revokeObjectURL(url);
3162
+ throw new Error("Could not get 2D canvas context for PNG export");
3027
3163
  }
3164
+ ctx.scale(pixelRatioForSvg, pixelRatioForSvg);
3165
+ ctx.drawImage(img, 0, 0, width, height);
3166
+ URL.revokeObjectURL(url);
3167
+ return canvas;
3168
+ }
3169
+
3170
+ // src/export/gif-exporter.ts
3171
+ var nextFrame = () => new Promise((resolve) => {
3172
+ let settled = false;
3173
+ const finish = () => {
3174
+ if (settled) return;
3175
+ settled = true;
3176
+ resolve();
3177
+ };
3178
+ const fallback = setTimeout(finish, 80);
3179
+ requestAnimationFrame(() => requestAnimationFrame(() => {
3180
+ clearTimeout(fallback);
3181
+ finish();
3182
+ }));
3183
+ });
3184
+ var DEFAULT_GIF_PIXEL_RATIO = 0.3;
3185
+ var MAX_GIF_FRAMES = 24;
3186
+ async function rasterizeFrame(container, pixelRatio, options) {
3187
+ const canvas = await rasterizeDiagramToCanvas(container, options, pixelRatio);
3188
+ const context = canvas.getContext("2d");
3189
+ if (!context) throw new Error("Could not create GIF canvas context");
3190
+ return context.getImageData(0, 0, canvas.width, canvas.height);
3028
3191
  }
3029
3192
  async function exportDiagramAsGif(container, timeline, options = {}) {
3030
- const fps = Math.min(24, Math.max(1, Math.round(options.fps ?? 12)));
3193
+ const requestedFps = Math.min(24, Math.max(1, Math.round(options.fps ?? 12)));
3031
3194
  const duration = timeline.duration();
3195
+ const fps = Math.min(requestedFps, MAX_GIF_FRAMES / Math.max(duration, 1e-3));
3032
3196
  const frameCount = Math.max(1, Math.ceil(duration * fps));
3033
3197
  const delayMs = Math.max(20, Math.round(1e3 / fps));
3198
+ const pixelRatio = options.pixelRatio ?? DEFAULT_GIF_PIXEL_RATIO;
3034
3199
  const priorTime = timeline.currentTime();
3035
3200
  const wasPlaying = timeline.isPlaying();
3036
3201
  timeline.pause();
3037
3202
  try {
3038
3203
  const frames = [];
3204
+ timeline.seek(duration);
3205
+ await nextFrame();
3206
+ frames.push({ imageData: await rasterizeFrame(container, pixelRatio, options), delayMs: Math.max(delayMs, 800) });
3039
3207
  for (let frame = 0; frame < frameCount; frame++) {
3040
3208
  timeline.seek(Math.min(duration, frame / fps));
3041
3209
  await nextFrame();
3042
3210
  frames.push({
3043
- imageData: await rasterizeFrame(container, options.pixelRatio ?? 1, options),
3211
+ imageData: await rasterizeFrame(container, pixelRatio, options),
3044
3212
  delayMs
3045
3213
  });
3046
3214
  }
3047
3215
  timeline.seek(duration);
3048
3216
  await nextFrame();
3049
- frames.push({ imageData: await rasterizeFrame(container, options.pixelRatio ?? 1, options), delayMs: Math.max(delayMs, 800) });
3217
+ frames.push({ imageData: await rasterizeFrame(container, pixelRatio, options), delayMs: Math.max(delayMs, 800) });
3050
3218
  const encoded = encodeGifSequence(frames, { dither: true, loop: options.loop ?? true });
3051
3219
  const bytes = new Uint8Array(encoded.byteLength);
3052
3220
  bytes.set(encoded);
@@ -3057,44 +3225,6 @@ async function exportDiagramAsGif(container, timeline, options = {}) {
3057
3225
  }
3058
3226
  }
3059
3227
 
3060
- // src/export/png-exporter.ts
3061
- async function exportDiagramAsPng(containerEl, options = {}) {
3062
- const pixelRatio = options.pixelRatio || 2;
3063
- const clonedContainer = containerEl.cloneNode(true);
3064
- await inlineExternalResources(clonedContainer);
3065
- const svgXml = exportDiagramAsVectorSvg(clonedContainer, options);
3066
- const blob = new Blob([svgXml], { type: "image/svg+xml;charset=utf-8" });
3067
- const url = URL.createObjectURL(blob);
3068
- const img = new Image();
3069
- await new Promise((resolve, reject) => {
3070
- img.onload = () => resolve();
3071
- img.onerror = () => {
3072
- URL.revokeObjectURL(url);
3073
- reject(new Error("Failed to rasterize SVG into Image for PNG export"));
3074
- };
3075
- img.src = url;
3076
- });
3077
- const width = img.naturalWidth || 800;
3078
- const height = img.naturalHeight || 400;
3079
- const canvas = document.createElement("canvas");
3080
- canvas.width = width * pixelRatio;
3081
- canvas.height = height * pixelRatio;
3082
- const ctx = canvas.getContext("2d");
3083
- if (!ctx) {
3084
- URL.revokeObjectURL(url);
3085
- throw new Error("Could not get 2D canvas context for PNG export");
3086
- }
3087
- ctx.scale(pixelRatio, pixelRatio);
3088
- ctx.drawImage(img, 0, 0, width, height);
3089
- URL.revokeObjectURL(url);
3090
- return new Promise((resolve, reject) => {
3091
- canvas.toBlob((b) => {
3092
- if (b) resolve(b);
3093
- else reject(new Error("Canvas toBlob failed for PNG export"));
3094
- }, "image/png");
3095
- });
3096
- }
3097
-
3098
3228
  // src/presentation-controller.ts
3099
3229
  var DiagramPresentationController = class {
3100
3230
  diagram;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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,15 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@markdy/core": "1.0.0"
47
+ "html2canvas": "^1.4.1",
48
+ "@markdy/core": "1.0.2"
48
49
  },
49
50
  "devDependencies": {
50
51
  "jsdom": "^29.1.1",
51
52
  "tsup": "^8.5.1",
52
53
  "typescript": "^5.9.3",
53
54
  "vitest": "^4.1.7",
54
- "@markdy/stdlib-systems": "1.0.0"
55
+ "@markdy/stdlib-systems": "1.0.2"
55
56
  },
56
57
  "scripts": {
57
58
  "build": "tsup",